Manage sponsorship records and slots
Choose the sponsor or recipient task using the intended member identity. Publisher Pricing, sponsorship, membership and slot are different records.
Sponsor member tasks
| Task | Operation |
|---|---|
| List own sponsorships | List own sponsorships |
| Read own sponsorship | Read own sponsorship |
| Update text/payer/renewal preferences | Update text/payer/renewal preferences |
| List parent sponsorship slots | List parent sponsorship slots |
| Read one owned sponsorship slot | Read one owned sponsorship slot |
| Invite or notify a slot recipient | Invite or notify a slot recipient |
| Clear a multi-seat slot | Clear a multi-seat slot |
Recipient member tasks
Use the intended recipient’s active member token and resource context. Invitation listing matches the member’s email. Activation and turning off sponsor payment use different checks; read the selected operation.
| Task | Operation |
|---|---|
| List email-matched invitation slots | List email-matched invitation slots |
| Activate a supplied slot code | Activate a supplied slot code |
| Turn off sponsor payment for a gift | Turn off sponsor payment for a gift |
Example clients and synthetic values follow response conventions.
Member and record context
All documented sponsorship actions require an active member and valid resource via token/resource headers; configured Firebase context follows the integration. Sponsor list/detail/update and slot list/detail/invitation select the current sponsor and the Pricing’s Plan resource through the parent sponsorship. These checks do not filter parent expiry/activation or slot activation. Clear and recipient actions have separate checks described with their operations. No administrator role or universal recipient-email verification follows from route prefixes.
List the current sponsor’s sponsorships
GET /api/v1/sponsor/sponsored-subscriptions
Inspect existing sponsorship records owned by the current person.
Before you call
Use member/record context. Selection joins configured Pricing/Plan, requires sponsor_id=current user and Plan.resource_id=selected resource. There is no active/expiry/activation filter. Records sort created_at descending, without a secondary tie order or pagination. No handler-specific payment/account mutation.
Request
Bodyless. No pagination/filter parameters are implemented.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource, token | headers | required existing member context | Selected resource and owning sponsor identity. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| items | array of expanded sponsor records | Empty [] possible. Mapper failures can leave partial items/[]; first associated membership summary is not every slot’s state. |
Example: Inspect sponsor 4001’s existing sponsorship 8001
Use sponsor 4001’s member context to list existing sponsorships in the selected Pricing Plan’s resource. The excerpt shows parent 8001 linked to Pricing 2001; it creates neither a sponsorship nor a slot.
curl "${WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions`, {
method: "GET", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/sponsor/sponsored-subscriptions",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="GET")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt; other defined fields omitted:
{
"items": [
{
"id": 8001,
"sponsor_id": 4001,
"subscription_id": 2001,
"slots": []
}
]
}
Consequential alternate
No selected sponsor/resource records gives HTTP 200 excerpt:
{
"items": []
}
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member context fails. | Resolve intended sponsor member identity. |
| 404 | resource_not_exists | Resource missing/unresolved. | Check supplied resource context. |
| 409 | sponsor_subscriptions_error | Query/general failure; exception message or Get sponsor subscriptions has failed. | Ask integration owner to inspect owned records and Pricing/Plan relations. |
Next task
Read one sponsorship owned by the current sponsor
GET /api/v1/sponsor/sponsored-subscriptions/{id}
Resolve an existing parent sponsorship and its nested slot/account summaries.
Before you call
Use member/record context. Integer-sanitized ID must be nonempty. Selection includes parent ID/current sponsor/Plan resource; no expiry or activation restriction. Missing/other-sponsor/wrong-resource parent returns HTTP 404. No action-specific writes.
Request
Bodyless. No pagination/filter parameters are implemented.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource, token | headers | required existing member context | Selected resource and owning sponsor identity. |
| id | path | required nonempty integer-sanitized value | Sponsorship ID, not slot ID. Route itself has no digits constraint. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| item | expanded sponsor record, possibly partial/[] | Selected sponsorship with sponsor/Pricing/slots and first-membership summary. Not an access or payment decision. |
Example: Inspect sponsorship 8001 with its separate Pricing ID
Sponsor 4001 reads parent sponsorship 8001. The returned subscription_id 2001 identifies its configured Pricing, not the parent record or an individual membership.
curl "${WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/8001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/8001`, {
method: "GET", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/sponsor/sponsored-subscriptions/8001",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="GET")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"item": {
"id": 8001,
"sponsor_id": 4001,
"subscription_id": 2001,
"autorenew": null,
"slots": []
}
}
Consequential alternate
Parent absent or outside sponsor/resource selection: HTTP 404 excerpt:
{
"error": "sponsor_subscription_error",
"error_description": "Sponsor subscription not found"
}
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member context fails. | Resolve intended sponsor member identity. |
| 404 | resource_not_exists | Resource missing/unresolved. | Check supplied resource context. |
| 409 | invalid_id | Empty/zero integer-sanitized ID. | Supply intended parent sponsorship ID. |
| 404 | sponsor_subscription_error | Parent absent/outside sponsor/resource selection. | Check owning identity and Pricing Plan resource. |
| 409 | sponsor_subscription_error | Query/general failure. | Reconcile related records with integration owner. |
Next task
Choose parent update or slot list; parent membership summary is not a slot ID.
Update sponsorship text and payment preferences
PUT /api/v1/sponsor/sponsored-subscriptions/{id}
Change supported parent fields and first-associated membership renewal preferences.
Before you call
Use member/record context. The parent must match the current sponsor and the selected Pricing Plan’s resource. Supplied title/description must pass presence/length validation before string/trim filtering. Null or empty text is not an erase instruction; omitted fields are unchanged.
Changes run in this order, without an enclosing transaction:
- Save supplied text and attempt its update event.
- Save the parent’s
is_paying_by_sponsorflag and attempt its event. - Save the first associated membership’s
autorenewflag and attempt its event. - Save that membership’s
is_allowed_next_subscriptionflag and attempt its event.
A strictly unchanged flag skips its step. The membership is selected by sponsored_subscription_id alone, without ordering or user/slot selection. These edits do not promise changes to every dependent membership.
Save results are unchecked. Membership saves attempt user/resource cache invalidation. No immediate charge, refund, membership deletion or expiry occurs. A later failure can follow earlier saved changes and events.
Request
Truthy JSON object required; Content-Type: application/json.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource, token | headers | required existing member context | Selected resource and owning sponsor identity. |
| id | path | required nonempty integer-sanitized value | Sponsorship ID, not slot ID. Route itself has no digits constraint. |
| title | JSON | optional present nonempty string, max 128 | Trimmed display title. |
| description | JSON | optional present nonempty string, max 512 | Trimmed display description. |
is_paying_by_sponsor | JSON | optional boolean-filtered value | Parent payer-selection preference, not settlement proof. |
| autorenew | JSON | optional boolean-filtered value | First associated membership renewal flag. |
is_allowed_next_subscription | JSON | optional boolean-filtered value | First associated membership next-Pricing flag. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| item | expanded sponsor record, possibly partial/[] | Selected sponsorship with sponsor/Pricing/slots and first-membership summary. Not an access or payment decision. |
Example: Update sponsorship 8001 without treating preference as a charge
Sponsor 4001 changes parent 8001’s title and turns off the first associated membership’s renewal flag. This request does not change every slot’s membership or issue a charge/refund.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/8001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"title": "Example sponsor group", "autorenew": false}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/8001`, {
method: "PUT", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"title": "Example sponsor group", "autorenew": false})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'title': 'Example sponsor group', 'autorenew': False}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/sponsor/sponsored-subscriptions/8001",
data=json.dumps(body).encode("utf-8"), headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"], "Content-Type": "application/json"}, method="PUT")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"item": {
"id": 8001,
"title": "Example sponsor group",
"autorenew": false
}
}
Consequential alternate
If supplied autorenew has no associated membership, the entity wraps its HTTP 404 lookup as HTTP 409 excerpt:
{
"error": "sponsor_subscription_update_error",
"error_description": "Update sponsor subscription relationship autorenew status has failed"
}
The earlier title save/event can already have happened; do not assume atomic rollback.
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member context fails. | Resolve intended sponsor member identity. |
| 404 | resource_not_exists | Resource missing/unresolved. | Check supplied resource context. |
| 400 | incorrect_data | Falsey/non-JSON body. | Supply supported JSON object. |
| 409 | invalid_id / invalid_title / invalid_description | ID or supported text validation fails. | Supply intended ID and nonempty bounded text. |
| 404 | sponsor_subscription_update_error | Owned parent not found. | Check sponsor/resource/parent ID. |
| 409 | sponsor_subscription_update_error | Text/payer/membership save/event or general failure. | Read current parent/membership state before repeating. |
Next task
Read parent detail and inspect intended membership flags. Content access and actual payment outcomes remain separate.
List slots for an owned sponsorship
GET /api/v1/sponsor/sponsored-subscriptions/{id}/slots
Inspect all slots belonging to one selected parent.
Before you call
Use member/record context. Parent ownership/Plan resource is checked first. Slot selection uses that parent ID and sorts created_at descending; no activation/recipient/expiry filter and no paginator. No action-specific writes.
Request
Bodyless. No pagination/filter parameters are implemented.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource, token | headers | required existing member context | Selected resource and owning sponsor identity. |
| id | path | required nonempty integer-sanitized value | Sponsorship ID, not slot ID. Route itself has no digits constraint. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| items | array of base slots | [] possible; partial mapper output possible. No expanded recipient/parent. |
Example: Find slot 9001 under sponsorship 8001
Sponsor 4001 lists slots using parent ID 8001. Slot 9001 has no bound recipient in this excerpt; keep its ID for a separate slot task.
curl "${WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/8001/slots" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/8001/slots`, {
method: "GET", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/sponsor/sponsored-subscriptions/8001/slots",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="GET")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"items": [
{
"id": 9001,
"sponsored_subscription_id": 8001,
"recipient_id": null,
"recipient_email": null,
"activated_at": null
}
]
}
Consequential alternate
Owned parent with no slots: HTTP 200 excerpt:
{
"items": []
}
An empty list does not create a slot or prove usable capacity.
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member context fails. | Resolve intended sponsor member identity. |
| 404 | resource_not_exists | Resource missing/unresolved. | Check supplied resource context. |
| 404 | sponsor_subscription_slots_error | Owned parent absent. | Check sponsor/resource and parent ID. |
| 409 | sponsor_subscription_slots_error | Empty/zero integer-sanitized ID gives generic Get sponsor subscription slots has failed; query/general failure can use the same code. | Supply intended parent ID; inspect related records if it persists. |
Next task
Use the slot ID for slot detail, keeping the parent ID separate.
Read one slot under an owned sponsorship
GET /api/v1/sponsor/sponsored-subscriptions/slots/{id}
Read base slot state after checking ownership/resource through its parent.
Before you call
Use member/record context. Slot is initially fetched by its ID, then its parent sponsorship must pass current sponsor/Plan-resource selection. No activation/expiry restriction or action-specific writes.
Request
Bodyless. No pagination/filter parameters are implemented.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource, token | headers | required existing member context | Selected resource and owning sponsor identity. |
| id | path | required nonempty integer-sanitized value | Slot ID, not sponsorship ID. Route itself has no digits constraint. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| item | base slot, possibly partial/[] | No recipient or nested sponsorship expansion. |
Example: Read slot 9001’s invitation state
Sponsor 4001 reads slot 9001 using the slot ID. The contact email and activation code are invitation state, not verified recipient identity or delivery proof.
curl "${WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/slots/9001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/slots/9001`, {
method: "GET", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/sponsor/sponsored-subscriptions/slots/9001",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="GET")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"item": {
"id": 9001,
"sponsored_subscription_id": 8001,
"recipient_email": "reader@example.com",
"activation_code": "SLOT_ACTIVATION_CODE_PLACEHOLDER",
"activated_at": null
}
}
Consequential alternate
Slot not found: HTTP 404 excerpt:
{
"error": "sponsor_subscription_slot_error",
"error_description": "Slot not found"
}
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member context fails. | Resolve intended sponsor member identity. |
| 404 | resource_not_exists | Resource missing/unresolved. | Check supplied resource context. |
| 404 | sponsor_subscription_slot_error | Slot or selected owned parent not found. | Check slot ID/current sponsor/Plan resource. |
| 409 | sponsor_subscription_slot_error | Empty/zero integer-sanitized ID gives generic Get sponsor subscription slot has failed; query/general failure can use the same code. | Supply intended slot ID; inspect related records with owner. |
Next task
Use base slot recipient/code/activation state to choose the intended invitation or clearing task; a readable slot is not proof of recipient access.
Invite or notify a sponsorship slot recipient
PUT /api/v1/sponsor/sponsored-subscriptions/slots/{id}/invitation
Store the intended recipient email and attempt configured notification events.
Before you call
Use member/record context. The slot’s parent must match the current sponsor and Pricing Plan resource. An activated slot rejects changes. recipient_email must be present and email-valid; the sanitized value is stored in lowercase without an explicit trim step.
Code behavior: changing a nonempty stored email to a different lowercase email generates a new unique 16-character activation code. An empty prior email or the same email retains the stored code. A same-email request can attempt notifications again; it is not deduplicated by a documented guarantee.
Save and notification: the save result is unchecked, then the slot is refreshed. Generic and gift/multi-seat invitation events are attempted afterward. An unsupported Pricing type or event failure can therefore follow an email change.
The action does not bind recipient_id, activate membership, set expiry or charge payment. There is no enclosing transaction or delivered-email guarantee.
Request
Truthy JSON object required, Content-Type: application/json.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| id | path | required slot ID, integer-sanitized/cast | Slot record, not parent/Pricing/membership ID; no route digits constraint. |
| token, resource | headers | required existing context | Active owning sponsor and selected resource. |
recipient_email | JSON | required email | Stored lowercase sanitized contact; not verified member ownership. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| item | base slot, partial/[] possible | Refreshed invitation state, including stored code; no recipient/parent expansion or delivery receipt. |
Example: Invite reader@example.com to slot 9001
Sponsor 4001 selects unactivated slot 9001 and stores reader@example.com as its contact. Notification events are attempted; membership activation remains a separate recipient task.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/slots/9001/invitation" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"recipient_email": "reader@example.com"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/slots/9001/invitation`, {
method: "PUT", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"recipient_email": "reader@example.com"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'recipient_email': 'reader@example.com'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/sponsor/sponsored-subscriptions/slots/9001/invitation",
data=json.dumps(body).encode("utf-8"), headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"], "Content-Type": "application/json"}, method="PUT")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"item": {
"id": 9001,
"sponsored_subscription_id": 8001,
"recipient_email": "reader@example.com",
"activation_code": "SLOT_ACTIVATION_CODE_PLACEHOLDER",
"activated_at": null
}
}
Consequential alternate
Already activated slot gives HTTP 409 excerpt:
{
"error": "sponsor_subscription_slot_error",
"error_description": "Sponsor subscription slot invitation has failed"
}
Changing recipient requires a different intended clearing task where eligible; this request does not transfer an activated membership.
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member missing. | Use intended sponsor identity/context. |
| 404 | resource_not_exists | Resource unresolved. | Check supplied resource key. |
| 404 | sponsor_subscription_slot_error | Slot or owned parent absent. | Check slot/parent ownership and resource. |
| 409 | sponsor_subscription_slot_error; Sponsor subscription slot invitation has failed | Empty/zero ID, invalid/missing email or falsey body; also store/event/general failure. | Supply valid JSON recipient_email and slot ID; inspect saved email/code before repeating after failure. |
| 409 | sponsor_subscription_slot_error; Sponsor subscription slot invitation has failed | Activation marker nonempty; the outward message is generic. | Inspect intended existing recipient/membership; invitation is not reassignment. |
Next task
Use the existing configured delivery flow for the intended recipient to obtain the activation code. Code in this sponsor response is a credential, not proof of delivery or verified recipient identity.
Clear an owned multi-seat sponsorship slot
PUT /api/v1/sponsor/sponsored-subscriptions/slots/{id}/clear
Reset slot recipient/invitation state and attempt deletion of its first linked membership.
Before you call
Use active sponsor/resource context. The slot ID is integer-cast. Inside a database transaction, the Pricing Plan’s resource and parent sponsor_id must strictly match the supplied context. Pricing must be multi-seat; gift slots are excluded. There is no explicit parent-expiry or slot-active guard.
Slot reset: the action clears recipient_id, recipient_email and activated_at, generates a fresh activation code and attempts an unchecked save.
Membership cleanup: it deletes the first membership found by sponsored_subscription_slot_id and writes history. Neither result is checked; this does not remove every matching or dependent membership. Deletion attempts user/resource cache invalidation. If the Plan resource enables auto_clear_content_views and the membership has a user ID, the deletion hook also attempts removal of all that user’s content-view records without resource filtering. It does not delete the user or issue a refund.
Completion: the action commits and prepares success/item, then attempts a slot-update event outside its error-handling block. An event failure can follow committed writes without a stable action-specific error response. Local rollback does not guarantee reversal of nested, hook or external effects.
Request
Bodyless; no action JSON fields consumed.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| id | path | required slot ID, integer-sanitized/cast | Slot record, not parent/Pricing/membership ID; no route digits constraint. |
| token, resource | headers | required existing context | Active owning sponsor and selected resource. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| success | boolean, true on completed path | Control-flow acknowledgement; unchecked writes are not certified. |
| item | base slot, partial/[] possible | Reset recipient/code state, not deleted user or refund receipt. |
Example: Clear multi-seat slot 9001 for intended reuse
Sponsor 4001 clears slot 9001 under a multi-seat Pricing. This resets recipient/code state and attempts first-linked membership cleanup; the gift branch is excluded.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/slots/9001/clear" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/sponsor/sponsored-subscriptions/slots/9001/clear`, {
method: "PUT", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/sponsor/sponsored-subscriptions/slots/9001/clear",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="PUT")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"success": true,
"item": {
"id": 9001,
"sponsored_subscription_id": 8001,
"recipient_id": null,
"recipient_email": null,
"activation_code": "NEW_SLOT_CODE_PLACEHOLDER",
"activated_at": null
}
}
Consequential alternate
Gift/non-multi-seat Pricing gives HTTP 409 excerpt:
{
"error": "sponsored_subscription_type",
"error_description": "Available to clear only multi-seat slots"
}
The transaction catch attempts rollback; this action does not offer a gift-clear branch.
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member missing. | Use intended sponsor identity/context. |
| 404 | resource_not_exists | Resource unresolved. | Check supplied resource key. |
| 409 | sponsored_subscription_type | Pricing is not multi-seat. | Use this task only for intended multi-seat slot. |
| 409 | sponsored_subscription_sponsor | Strict parent sponsor mismatch. | Use owning sponsor identity. |
| 409 | sponsor_clear_slot_error; Clear slot has failed | Slot/related records missing, resource mismatch or caught save/history/general failure. | Reconcile slot/membership/history and selected resource before repeating. |
| Unspecified | No stable action-specific event error | Postcommit update event throws outside catch. | Inspect committed state and notification attempt before repeating. |
Next task
Inspect slot detail and account/access state separately. If a new recipient is intended, invite after reconciling clearing, not as an automatic chained operation.
List invitation slots matching the current member’s email
GET /api/v1/recipient/sponsored-subscriptions/slots/invitations
Find pending stored invitations addressed to the current person’s email.
Before you call
Use member context. Selection compares recipient_email to current user.email exactly, joins Pricing Plan resource, requires activation_code IS NOT NULL, activated_at IS NULL and parent expired_at IS NULL. It does not require recipient_id=current user or check parent end dates, other active flags or future activation success. No ordering or pagination specified. No handler-specific account writes.
Request
Bodyless; no action pagination/filter parameters implemented.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource, token | headers | required existing context | Selected resource and active intended recipient member. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| items | array of recipient invitation slots | [] possible. Nested parent sponsor/Pricing/summary, no recipient expansion or nested slots. Mapper failures can return partial items/[]. |
Example: Find reader@example.com’s pending slot 9001
Use reader@example.com’s active member context to list email-matched pending invitations. Slot 9001 belongs to parent 8001; finding it does not reserve or activate it.
curl "${WALLKIT_API_BASE}/api/v1/recipient/sponsored-subscriptions/slots/invitations" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/recipient/sponsored-subscriptions/slots/invitations`, {
method: "GET", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/recipient/sponsored-subscriptions/slots/invitations",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="GET")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"items": [
{
"id": 9001,
"recipient_email": "reader@example.com",
"activation_code": "SLOT_ACTIVATION_CODE_PLACEHOLDER",
"sponsored_subscription": {
"id": 8001,
"subscription_id": 2001
}
}
]
}
Consequential alternate
No matching pending invitations: HTTP 200 excerpt:
{
"items": []
}
An existing supplied code follows a separate lookup; listing absence is not proof that code activation will reject it.
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member context missing. | Resolve intended recipient identity/context. |
| 404 | resource_not_exists | Resource unresolved. | Check supplied resource key. |
| 409 | sponsor_subscription_slot_error | Recipient query/general failure. | Ask integration owner to inspect stored email, resource and pending invitation records. |
Next task
If accepting an intended invitation, use its activation code for activation with the intended member identity; that action has different checks.
Activate a sponsorship slot using its code
PUT /api/v1/recipient/sponsored-subscriptions/slots/activate
Attempt the selected slot’s membership activation for the current active person.
Before you call
Use member context and the existing issuer-provided code. It must remain nonempty after string/trim filtering. Code lookup is exact and initially has no email/resource predicate.
Acceptance checks: the slot must not have an activation marker, and its Pricing Plan must strictly match the selected resource. recipient_id is compared with the current member only when already nonempty. The action does not compare recipient_email or reject a parent expired_at marker. An unbound slot can therefore pass the identity check for a member who holds the code; email-matched listing does not strengthen this guard.
Only gift and multi-seat Pricings are supported:
- Gift: creates or replaces recipient membership with sponsorship/slot IDs. Pricing trial settings are checked first; the shared helper applies trial only if the person’s current/history trial eligibility also passes. Parent
activated_atis then saved. - Multi-seat: requires a first parent membership with the sponsorship ID, null
parent_idand nulluser_id. Recipient membership uses that parent ID and slot ID, nullsponsored_subscription_id, the copied parent end date and trial=false.
The membership helper can replace prior memberships, write history, stop/clear other sponsorships and attempt cache, content-view, event and synchronization effects. Deletion hooks can conditionally clear user content-view records across resources.
The slot’s recipient and activation time are set, and its code is cleared, without checking save success. Gift parent save is also unchecked. Sponsor/recipient events follow. There is no transaction covering the entire action; nested helper transactions do not make later slot, parent or event writes atomic or safe to repeat.
Request
Truthy JSON object required, Content-Type: application/json.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource, token | headers | required existing context | Selected resource and active intended recipient member. |
activation_code | JSON | required nonempty string | Existing slot code; string/trim-sanitized. Not an invite/member token. |
Result
HTTP 200 JSON:
| Field | Type / presence | Meaning |
|---|---|---|
| success | boolean, true on completed path | Control-flow acknowledgement, not checked persistence/payment/access proof. |
| item | expanded recipient slot, possibly partial/[]; includes recipient plus nested parent with sponsor/Pricing/first-membership summary, no nested slots | Slot/account projection after attempted activation. |
Example: Accept slot 9001 as intended recipient 4002
Use intended recipient 4002’s active member context and the supplied slot activation code. The configured Pricing chooses the gift or multi-seat branch; email-matched discovery and code activation have different checks.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/recipient/sponsored-subscriptions/slots/activate" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"activation_code": "SLOT_ACTIVATION_CODE_PLACEHOLDER"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/recipient/sponsored-subscriptions/slots/activate`, {
method: "PUT", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"activation_code": "SLOT_ACTIVATION_CODE_PLACEHOLDER"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'activation_code': 'SLOT_ACTIVATION_CODE_PLACEHOLDER'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/recipient/sponsored-subscriptions/slots/activate",
data=json.dumps(body).encode("utf-8"), headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"], "Content-Type": "application/json"}, method="PUT")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"success": true,
"item": {
"id": 9001,
"recipient_id": 4002,
"recipient_email": "reader@example.com",
"activation_code": null,
"sponsored_subscription": {
"id": 8001,
"subscription_id": 2001
}
}
}
Consequential alternate
A consumed/cleared code that no longer matches gives HTTP 404 excerpt:
{
"error": "sponsor_subscription_slot_error",
"error_description": "Slot not found"
}
Nulling code is attempted, not checked. A retained code with a nonempty activation marker instead rejects as already activated; do not promise replay success.
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member context missing. | Resolve intended recipient identity/context. |
| 404 | resource_not_exists | Resource unresolved. | Check supplied resource key. |
| 400 | incorrect_data | Falsey/non-JSON body. | Supply supported JSON object. |
| 409 | invalid_activation_code | Empty code after sanitization. | Supply intended issuer-provided code. |
| 404 | sponsor_subscription_slot_error; Slot not found | Code lookup absent. | Check supplied code and whether it was cleared/consumed. |
| 409 | sponsor_subscription_slot_error | Activated slot, strict resource mismatch, bound recipient mismatch, unsupported type, missing parent membership or wrapped activation failure. | Inspect slot/parent/membership/current identity before repeating; prior changes may exist. |
| 409 | sponsor_subscription_slot_error; Activate sponsored subscription slot has failed | General/event failure. | Reconcile membership/slot/parent and attempted notification state. |
Next task
Inspect your memberships and obtain the separate content-access decision. If payment preference needs changing for a gift, read sponsor payment off; activation is not a charge receipt.
Turn off sponsor payment for a gift slot
PUT /api/v1/recipient/sponsored-subscriptions/slots/{id}/sponsor/turn-off
Attempt to disable the parent gift sponsorship’s payer flag.
Before you call
Use active member/resource context. The slot is fetched without an initial owner/email predicate. Pricing must be gift, and its Plan resource must strictly match. The current person is checked against recipient_id only when that field is nonempty. Email, activation time and parent expiry are not checked.
Payer change: an already-false parent is_paying_by_sponsor flag rejects the request. Otherwise the helper sets it false and attempts an unchecked save. General helper failures can be logged and swallowed.
Later effects: mapping and sponsor/recipient event attempts follow without an enclosing transaction. An unbound slot can pass the earlier identity check, then fail the recipient-empty event check after the parent mutation attempt. An error therefore does not prove unchanged state.
This action does not refund, expire or delete membership, configure a recipient payment source or charge payment. A later charging helper uses the flag to choose sponsor versus member; it does not guarantee successful future collection.
Request
Bodyless; no action JSON fields consumed.
| Name | Location | Type / requirement | Meaning |
|---|---|---|---|
| resource, token | headers | required existing context | Selected resource and active intended recipient member. |
| id | path | required slot ID, integer-cast | Gift slot ID, not parent/Pricing/membership ID; no route digits constraint. |
Result
HTTP 200 JSON:
| Field | Type | Meaning |
|---|---|---|
| item | expanded recipient slot, possibly partial/[]; includes recipient plus nested parent with sponsor/Pricing/first-membership summary, no nested slots | Parent payer preference after attempted save; no success flag or payment receipt. |
Example: Stop sponsor payment preference for recipient 4002’s gift 9001
Recipient 4002 selects gift slot 9001 with their own member context to turn off its parent’s sponsor payer flag. This does not refund or delete their membership or configure a recipient payment source.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/recipient/sponsored-subscriptions/slots/9001/sponsor/turn-off" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/recipient/sponsored-subscriptions/slots/9001/sponsor/turn-off`, {
method: "PUT", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN }
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/recipient/sponsored-subscriptions/slots/9001/sponsor/turn-off",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="PUT")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 200 excerpt:
{
"item": {
"id": 9001,
"recipient_id": 4002,
"sponsored_subscription": {
"id": 8001,
"is_paying_by_sponsor": false
}
}
}
Consequential alternate
Unbound recipient can reach a post-mutation event error: HTTP 409 excerpt:
{
"error": "sponsor_subscription_slot_error",
"error_description": "Sponsored subscription slot recipient is empty"
}
The parent flag may already have changed. Reconcile parent/account state before repeating; this is not atomic failure.
Recovery
| HTTP | API code | Cause | Recovery |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Active member context missing. | Resolve intended recipient identity/context. |
| 404 | resource_not_exists | Resource unresolved. | Check supplied resource key. |
| 404 | sponsor_subscription_slot_error; Slot not found | Slot missing. | Check intended gift slot ID. |
| 409 | sponsor_subscription_slot_error | Not gift, wrong resource/bound recipient, already-disabled payer, missing relations or event exception. | Inspect gift/recipient/parent payer state and intended member identity. |
| 409 | sponsor_subscription_slot_error; Sponsored subscription slot recipient is empty | Event sees no recipient after mutation attempt. | Reconcile parent payer flag; do not assume rollback. |
| 409 | sponsor_subscription_slot_error; Activate sponsored subscription slot has failed | General exception; message mentions activation although this is payer-off. | Inspect parent payer state and event attempt before repeating. |
Next task
Read account/membership and access state separately. Disabled sponsor payment does not delete membership or issue a refund; any future collection needs its own configured billing flow.