Manage teams
Read teams you administer and create individual invitations for an eligible team. Being able to see a team does not prove you can manage it or activate a membership.
| Task | Operation |
|---|---|
| Read owner/admin teams | Team list |
| Create a team | Create a team |
| Update team display fields | Update team display fields |
| Delete a team record | Delete a team record |
| Read team detail | Read team detail |
| List team invitations | List team invitations |
| Read one team invitation | Read one team invitation |
| Update invitation fields | Update invitation fields |
| Delete an invitation | Delete an invitation |
| Request invitation resend | Request invitation resend |
| Create an individual invitation | Create invite |
Member relationships
| Task | Operation |
|---|---|
| List non-owner members | List non-owner members |
| Read one member | Read one member |
| Update role/status or requested Pricing | Update role/status or requested Pricing |
| Mark a member removed | Mark a member removed |
| Mark a member suspended | Mark a member suspended |
Admin-prefixed registration
| Task / access distinction | Operation |
|---|---|
| Resend using admin-prefix session resolution; same user ACL/action guard | Admin-prefixed resend |
Example clients and synthetic values follow response conventions.
Member context
Existing member context uses custom token and resource headers; see credential transport. Team actions require an active user; initialization has no separate required-resource guard, but context resolution and action eligibility can depend on resource. Configured Firebase handling follows the selected integration. A visible team does not prove management permission.
Selected-team management eligibility
For operations that link here, the action accepts either selected-resource can-own-teams eligibility or an active owner/admin relationship for the selected team. Eligibility checks a qualifying Plan relationship in the selected resource, without active/expiration filtering. That branch can pass without ownership of the selected team. Team lookup has no resource filter; do not infer a stricter owner/resource guarantee.
The team list uses its own role/current-user selection. Team creation has a separate eligibility check described beside that operation. Neither inherits this selected-team management rule.
Resend context and effects
Use the shared member context. Resend permits selected-resource can-own-teams eligibility, active team owner/admin, or global user role admin/root. A route prefix does not substitute for those checks.
Select the intended team/invite pair. Lookup is not resource-filtered; invite.team_id must match the selected team. Resend does not revalidate invite email/domain/window/cap/used status/Pricing capacity.
Queue failure can be swallowed; result:true acknowledges this action, without confirming delivered email. Repeated calls can attempt more notifications; there is no deduplication guarantee.
Read your owner/admin teams
GET /api/v1/user/teams
List teams linked to the current user through owner/admin roles. This list has no active relationship/team or resource filter; do not use it as a write-permission or active-access decision.
Before you call
Use the shared member context.
Request
Bodyless request.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
| token, resource | headers | existing member context | Identify current member as described above. |
| filter | query | optional bracket-style model-field map | Teams metadata filter; use filter[Teams.id] for a known ID, not an arbitrary authorization selector. No resource restriction added by this filter. |
| by | query | string, default Teams.id | Direct ordering expression; no strict allowlist established. |
| sort | query | string, default DESC | Direct sort expression; no strict enum validation established. |
| limit | query | integer-filter then cast; default 10 | Outer team page size; no explicit upper bound. |
| page | query | raw value cast to integer | Send page=1 explicitly; no explicit default/strict validation. |
Result
HTTP 200 items array, empty [] possible, with common paginator. Each item is the team-list projection; invites are unpaginated/all-associated. Default order Teams.id DESC; team grouping avoids duplicate team IDs.
Example: Identify a team before checking invitation eligibility
Read the first page. This excerpt shows team 1001 and empty embedded invite/Pricing collections; additional full-team fields are omitted.
curl "${WALLKIT_API_BASE}/api/v1/user/teams?page=1&limit=10" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams?page=1&limit=10`, {
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/user/teams?page=1&limit=10",
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": 1001,
"name": "Example Media",
"users_count": 1,
"invites_count": 0,
"invites": [],
"subscription": []
}
],
"paginator": {
"current_page": 1,
"page": 1,
"total_pages": 1,
"total_items": 1,
"limit": 10
}
}
Consequential alternate
No owner/admin role relationship matches, HTTP 200 excerpt:
{
"items": []
}
Empty is not an invitation or ownership grant. A suspended relationship can still make a team visible here because status is not filtered.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | User context absent/inactive/locked/suspended. | Follow credential recovery. |
| 200 | items:[] | No selected owner/admin team rows. | Handle an empty page; check intended account/filter with integration owner. |
| Unspecified | No dedicated list error envelope | Query/filter/order failure. | Correct model-field filters/order values; no stable list-specific error code promised. |
Next task
Use the returned team ID only after reading invite creation prerequisites. See the Group-memberships (teams) management.
Create an individual team invitation
POST /api/v1/user/teams/{team_id}/invites
Save an invitation and attempt its individual_in_team_invite queue event. HTTP 201 is not delivered email or activated membership.
Before you call
Use the shared member context.
This action uses selected-team management eligibility.
Email must not already have any invite for this team, must match configured domain patterns, and the selected Pricing must pass team eligibility/seat checks. A positive linked users_limit counts active team Pricing relationships; paid Pricing additionally requires a payment customer on an active owner’s globally inspected account. This presence check does not charge or prove a usable/default source. Queue failure can be swallowed; save result is unchecked and later failure may leave partial state.
Request
JSON object required.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Team ID from intended managed team, not relationship/invite ID. |
| token, resource | headers | existing member/resource context | Context used by eligibility and stored invite.resource_id. |
| JSON | required valid email, max 60 before sanitization | Stored email/trim/lower; duplicate invite/domain checks. | |
subscription_id | JSON | required existing Pricing ID | Presence/existence and active linked-Plan/Pricing eligibility; permission query has no current-user condition. Stored integer-filter value. |
user_name | JSON | optional string | Stored string/trim; no explicit presence/length validation; supply explicitly if used. |
| title | JSON | optional nonempty string, max 128 | Default Invite for <team name> team on omission/null. |
| description | JSON | optional string | Same team-derived default on omission/null; string/trim storage. |
resource_id | JSON | optional existing resource ID | Validated when set but not used for storage; selected header resource is stored instead. |
| code | JSON | optional nonempty string, length 10–40 when set | Supplied code is validated but not assigned. Omit to use generated 40-character code; otherwise save hook generates UUID when empty. No accepted-custom-code guarantee. |
Result
HTTP 201 top-level invite record, not data/invite wrapper. Generated code and invite ID are distinct. activations_limit is set to 1; no explicit start/end window assigned. Conditional subscription/team/ticket fields follow that exact projection.
Example: Create one invite for team 1001
Choose an already eligible free Pricing 2001, so the example does not imply owner payment or paid activation. Omit code/resource_id; header resource controls storage. The synthetic code below represents the returned generated code, not a supplied custom code.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/teams/1001/invites" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"email": "reader@example.com", "user_name": "Alex Reader", "subscription_id": 2001}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/invites`, {
method: "POST", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"email": "reader@example.com", "user_name": "Alex Reader", "subscription_id": 2001})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'email': 'reader@example.com', 'user_name': 'Alex Reader', 'subscription_id': 2001}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/teams/1001/invites",
data=json.dumps(body).encode("utf-8"), headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"], "Content-Type": "application/json"}, method="POST")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 201 excerpt:
{
"id": 3001,
"email": "reader@example.com",
"user_name": "Alex Reader",
"title": "Invite for Example Media team",
"code": "SYNTHETIC-INVITE-CODE-DO-NOT-USE-0000000001",
"activations_limit": 1
}
Store the returned code for the intended invitee flow; ID 3001 is for invitation management.
Consequential alternate
An existing invite for this email/team returns HTTP 409 excerpt (other metadata omitted):
{
"error": "invalid_email",
"error_description": "This user already has an invitation to your team. Try sending an invitation again."
}
Creation does not overwrite that invite. Inspect the existing record and intended resend policy; the literal message is not proof that retry/resend is harmless.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | incorrect_data; You do not have permission to manage teams | Neither eligibility nor active team-admin/owner check passes. | Ask integration owner to check intended resource/Plan/team role. |
| 406 | incorrect_data; Body must be json format | No truthy decoded JSON body. | Submit JSON with required email/Pricing. |
| 404 | team_not_found | No team at selected ID. | Check intended returned team ID. |
| 409 | invalid_email / invalid_subscription_id / invalid_title / invalid_code / invalid_resource_id | Validator failure; includes duplicate/domain/linked seat/payment-customer checks. | Correct indicated input or inspect eligibility/capacity with integration owner; never assume invitation reserves a seat. |
| 409 | exception; Please contact to administrator | Caught save/event/refresh/serialization error. | Reconcile saved invite before another creation attempt. |
Next task
The invitee can validate the supplied code, then use their own active member context for activation. See Group-memberships (teams) management.
Create a team record
POST /api/v1/user/teams
Create a team with an active owner relationship; no membership purchase or invitation is automatic.
Before you call
Use the shared member context.
Creation requires selected-resource can-own-teams eligibility. The alternative team-admin helper is called without a team ID and checks team 0, so it does not establish a general “admin of any team” permission. The action starts a DB transaction, saves team unchecked, attempts team_owner_create_team queue event, then saves owner relationship unchecked and commits. No atomic queue/local persistence or duplicate-creation guarantee.
Team active=true, stored owner=current user, member_limit=0. Resource auto_allowed_domains_for_team can derive allowed domain from current user’s valid email. Team record has no resource_id assigned, and no partner relationship/membership is created by this action.
Request
JSON object required.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
| token, resource | headers | existing member/resource context | Eligibility and event context. |
| name | JSON | required string, max 255 before sanitization | String/trim storage. |
| description | JSON | optional string / null | Stored only when nonnull; string/trim. |
| type | JSON | optional nonempty accepted string | personal, company, company_subdivision, organization, educational_institution, multi_user; string/trim/lower storage. Omission has no explicit handler default. |
member_limit | JSON | ignored | Handler always sets 0; cannot configure seats here. |
Result
HTTP 201 top-level team detail with users/invites, not a list item/data wrapper. No team Pricing field is added by this projection.
Example: Create an explicitly typed company team
Create Example Media with type company rather than guessing an omitted type default. Response excerpt omits owner/member detail.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/teams" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"name": "Example Media", "type": "company", "description": "Example publishing team"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams`, {
method: "POST", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"name": "Example Media", "type": "company", "description": "Example publishing team"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'name': 'Example Media', 'type': 'company', 'description': 'Example publishing team'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/teams",
data=json.dumps(body).encode("utf-8"), headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"], "Content-Type": "application/json"}, method="POST")
try:
with urlopen(request) as response:
print(response.status, json.load(response))
except HTTPError as error:
print(error.code, json.load(error))
Synthetic HTTP 201 excerpt:
{
"id": 1001,
"name": "Example Media",
"type": "company",
"active": true,
"member_limit": 0,
"invites": [],
"users_count": 1
}
Consequential alternate
If owner relationship handling throws, HTTP 406 excerpt (other metadata omitted):
{
"error": "incorrect_owner",
"error_description": "Create owner team id fail"
}
Rollback is attempted, but the queue event may already have been attempted. Unchecked false saves do not necessarily enter this exception branch.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Missing/inactive/locked member context. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check selected resource/Plan/team role with integration owner. |
| 406 | incorrect_data; Body must be json format | No truthy JSON body. | Supply object with required name. |
| 409 | invalid_name / invalid_type | Length or type validation failure. | Use a valid length/type value. |
| 406 | incorrect_owner | Caught owner relationship exception. | Reconcile team/owner/event state before another create attempt. |
| Unspecified | No dedicated outer save/event error envelope | Failure before owner try block or after commit. | Ask integration owner to inspect persisted state; avoid blind creation retry. |
Next task
Use team detail and invite creation only with intended eligibility. Creation alone grants no purchased content.
Update team display fields
PUT /api/v1/user/teams/{team_id}
Change supplied name/description/type; this action does not update seat limits, team Pricing or active status.
Before you call
Use the shared member context and selected-team management eligibility.
Completion is not established by the reviewed source. The action attempts a save and refresh before calling a change-comparison helper that is missing from the controller’s included traits. A change can therefore be saved before failure. There is no dedicated error handling for this failure. The intended result below applies only if that helper is available in the deployment.
The permitted fields do not replace memberships or history. Pricing, membership end-date and lock-prevention changes are not selectable here. The save result is not checked, and no durable-save or delivered-email guarantee follows.
Request
JSON object required; omitted/null editable fields stay unchanged.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID; not user/invite/relationship ID. |
| token, resource | headers | existing member/resource context | Select member and eligibility context. |
| name | JSON | optional string, max 255 | String/trim; no presence validation when set. |
| description | JSON | optional string | String/trim; no explicit length validation. |
| type | JSON | optional nonempty accepted string | Same six values as creation. |
member_limit, subscription_id, active, never_lock | JSON | ignored | No assignment branch in this action. |
Result
Intended HTTP 200 top-level team detail with users/invites, not a list item/data wrapper. No team Pricing field is added by this projection.
Example: Rename team without changing seats
This request expresses an intended name-only update for team 1001. A reachable completed success example is N/A in the available source because the post-save comparison helper is unresolved; the conditional projection below teaches the intended response without certifying it.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/teams/1001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"name": "Example Newsroom"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001`, {
method: "PUT", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"name": "Example Newsroom"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'name': 'Example Newsroom'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/teams/1001",
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))
Intended synthetic HTTP 200 projection only, conditional on the unresolved helper being available:
{
"id": 1001,
"name": "Example Newsroom",
"type": "company",
"member_limit": 0
}
Consequential alternate
A missing selected team returns HTTP 404 excerpt (other metadata omitted):
{
"error": "team_not_found",
"error_description": "Team not found"
}
The unresolved post-save helper can instead fail after the name has been saved. A false save can also leave original fields after refresh; do not treat status alone as confirmed requested change.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Missing/inactive/locked member context. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check selected resource/Plan/team role with integration owner. |
| 404 | team_not_found | Team lookup fails. | Check intended team ID; list visibility does not grant writes. |
| 406 | incorrect_data; Body must be json format | No truthy JSON body. | Send object with intended supported fields. |
| 409 | invalid_name / invalid_type | Supplied field validation fails. | Correct length/type; ignored fields cannot configure capacity. |
| Unspecified | No dedicated save/event exception envelope | Save/refresh or unresolved post-save change helper fails. | Read intended detail/reconcile before repeating changes. |
Next task
Inspect returned/read-back display fields with team detail; do not infer membership/billing changes.
Delete the selected team record
DELETE /api/v1/user/teams/{team_id}
Attempt model deletion and return its Boolean result. This is distinct from removing or suspending a team member.
Before you call
Use the shared member context.
This action uses selected-team management eligibility.
The action calls Team.delete with no explicit relationship/invite/membership/history cleanup or refund/account-deletion sequence. Team model has no beforeDelete/afterDelete hook defining a cascade here. Database constraints/cascades may affect the outcome; no universal cascade or preserved-access promise follows. No action event is emitted. Read result even on HTTP 200.
Request
Bodyless request.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID; not user/invite/relationship ID. |
| token, resource | headers | existing member/resource context | Select member and eligibility context. |
Result
HTTP 200 JSON:
| Field | Type / presence | Meaning |
|---|---|---|
| id | integer | Selected team ID. |
| result | boolean | Return value of model delete; false is not successful deletion. |
Example: Interpret the delete result for team 1001
Only for the intended deletion task, construct this request. No user/provider account deletion or refund is implied.
curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/teams/1001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001`, {
method: "DELETE", 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/user/teams/1001",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="DELETE")
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:
{
"id": 1001,
"result": true
}
Consequential alternate
Model deletion can return false with HTTP 200:
{
"id": 1001,
"result": false
}
This indicates unsuccessful deletion, without a returned model-error detail. Do not infer account/access removal or retry blindly.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Missing/inactive/locked member context. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check selected resource/Plan/team role with integration owner. |
| 404 | team_not_found | Team lookup fails. | Check intended team ID; list visibility does not grant writes. |
| 200 | result:false | Model delete returned false. | Ask integration owner to inspect constraints/state before deciding another attempt. |
| Unspecified | No dedicated deletion catch | Delete throws or other handling fails. | Reconcile selected record/dependent state; no cascade/refund promise. |
Next task
Reconcile intended team and member/account state with integration owner. Do not equate team deletion with individual user deletion or an access decision.
Read one managed team detail
GET /api/v1/user/teams/{team_id}
Read team fields with all associated invitations and users. The result differs from the list’s embedded Pricing projection.
Before you call
Use the shared member context.
This action uses selected-team management eligibility.
After eligibility, a joined query combines an owner/admin-role condition OR current-user condition and a team-ID condition, with no explicit resource/status filter. Predicate grouping is not an independently enforced ownership guarantee; inspect returned id against your intended team. It requires a joined relationship row, unlike a simple unjoined team lookup.
Request
Bodyless request.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID; not user/invite/relationship ID. |
| token, resource | headers | existing member/resource context | Select member and eligibility context. |
| by | query | optional string, default Teams.id | Direct order expression, no strict allowlist. |
| sort | query | optional string, default DESC | Direct sort expression; ties unspecified. |
Result
HTTP 200 top-level team detail with users/invites, not a list item/data wrapper. No team Pricing field is added by this projection.
Example: Read team 1001 without treating detail as a Pricing list
Inspect id/name/counts and role/status for each returned user in the full projection. This excerpt omits sensitive nested contact/invite fields.
curl "${WALLKIT_API_BASE}/api/v1/user/teams/1001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001`, {
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/user/teams/1001",
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:
{
"id": 1001,
"name": "Example Media",
"users_count": 1,
"invites_count": 0,
"invites": [],
"users": [
{
"id": 4001,
"role": "owner",
"status": "active"
}
]
}
Consequential alternate
No joined team row selected returns HTTP 404 excerpt (other metadata omitted):
{
"error": "team_not_found",
"error_description": "Team not found"
}
A bare team record can exist without satisfying this joined selection.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Missing/inactive/locked member context. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check selected resource/Plan/team role with integration owner. |
| 404 | team_not_found | Team lookup fails. | Check intended team ID; list visibility does not grant writes. |
| Unspecified | No dedicated detail query catch | Query/order/serialization failure. | Check ID/order values and intended relationship state with integration owner. |
Next task
Use invite list for its separate pagination/projection. Team detail is no substitute for content access.
List a team’s invitation records
GET /api/v1/user/teams/{team_id}/invites
Page invitation records for the selected team, including usage. No available-only/time/email/resource filtering is automatic.
Before you call
Use the shared member context.
This action uses selected-team management eligibility.
Request
Bodyless request.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID; not user/invite/relationship ID. |
| token, resource | headers | existing member/resource context | Select member and eligibility context. |
| filter | query | optional bracket model-field map | Invites metadata filter, alongside enforced team_id. |
| by | query | string, default Invites.id | Direct ordering expression. |
| sort | query | string, default DESC | Direct sort expression, no strict enum validation. |
| limit | query | integer-filter then cast, default 10 | Page size, no explicit upper bound. |
| page | query | raw value cast integer | Use page=1; no explicit default/strict validation. |
Result
HTTP 200 items array and common paginator. Each item is invite-list projection, not the embedded team-list usage/mail projection.
Example: Inspect invite 3001 and activation count
Read first invitation page for team 1001. This excerpt omits rich record fields.
curl "${WALLKIT_API_BASE}/api/v1/user/teams/1001/invites?page=1&limit=10" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/invites?page=1&limit=10`, {
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/user/teams/1001/invites?page=1&limit=10",
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": 3001,
"email": "reader@example.com",
"activations": 0,
"used": null
}
],
"paginator": {
"current_page": 1,
"page": 1,
"total_pages": 1,
"total_items": 1,
"limit": 10
}
}
Consequential alternate
No matching invitation rows returns HTTP 200 excerpt:
{
"items": []
}
This does not describe delivered mail or active members.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Missing/inactive/locked member context. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check selected resource/Plan/team role with integration owner. |
| 404 | team_not_found | Team lookup fails. | Check intended team ID; list visibility does not grant writes. |
| 200 | items:[] | No invite matches selected team/filter. | Handle empty list; check intended team and filter. |
| Unspecified | No dedicated list error catch | Filter/query/serialization fails. | Correct model-field filters/order; ask integration owner about stored data. |
Next task
Read one invite detail using its invite ID; pass its code, not ID, to invitee validation.
Read a selected team invitation
GET /api/v1/user/teams/{team_id}/invites/{id}
Read one invite only after its team ID matches the selected team.
Before you call
Use the shared member context.
This action uses selected-team management eligibility.
Team and invite are fetched by IDs without resource filtering, then invite.team_id must equal selected team.id. This association check differs from the eligibility branch; it does not prove current user owns the team.
Request
Bodyless request.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID; not user/invite/relationship ID. |
| token, resource | headers | existing member/resource context | Select member and eligibility context. |
| id | path | required digits | Invitation record ID from list/create result; not invite code. |
Result
HTTP 200 top-level invite record. No activations/used/send_mails/available fields are added by this detail serializer.
Example: Read invite 3001 under team 1001
Use the invitation ID from creation/listing; response excerpt shows its redeemable code separately.
curl "${WALLKIT_API_BASE}/api/v1/user/teams/1001/invites/3001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/invites/3001`, {
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/user/teams/1001/invites/3001",
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:
{
"id": 3001,
"email": "reader@example.com",
"code": "SYNTHETIC-INVITE-CODE-DO-NOT-USE-0000000001",
"activations_limit": 1
}
Consequential alternate
Invite belongs to another team, HTTP 401 excerpt (other metadata omitted):
{
"error": "invite_access",
"error_description": "This invitation is not available to your team!"
}
Changing the user token does not change the record’s team association.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Missing/inactive/locked member context. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check selected resource/Plan/team role with integration owner. |
| 404 | team_not_found | Team lookup fails. | Check intended team ID; list visibility does not grant writes. |
| 404 | invite_not_found | Invite ID does not resolve. | Check record ID from intended list/create result. |
| 401 | invite_access | Invite.team_id differs from selected team. | Use intended matching team/invite pair; never probe another team’s code. |
| Unspecified | No dedicated serializer catch | Related record projection fails. | Ask integration owner to inspect invite/team/Pricing data. |
Next task
When intended, give the supplied code to the invitee’s Group-memberships (teams) management; reading detail does not send or activate it.
Update an invitation’s supplied fields
PUT /api/v1/user/teams/{team_id}/invites/{id}
Change recipient/display/Pricing fields on a saved invite. No resend event is active in this update.
Before you call
Use the shared member context and selected-team management eligibility. Team and invite are looked up by IDs without resource filtering, then invite.team_id must match team.id. This checks the selected pair, not stronger owner/resource scope.
Email is validated but this update does not repeat create’s duplicate-email/domain validator. Changed Pricing repeats existence/team eligibility/seat/payment-customer validation; unchanged supplied Pricing skips those validators but is still assigned. Save is unchecked then refreshed; failure can leave state changed. An empty existing code can be generated, but a supplied code is ignored. No activation reset/expiry/cap update is supported.
Request
JSON object required; omitted/null fields remain unchanged.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended managed team ID. |
| id | path | required digits | Invite record ID from create/list; not redeemable code. |
| token, resource | headers | existing context | Member identity and selected resource/eligibility/event context. |
| JSON | optional nonempty valid email, max 60 | Email sanitized on storage without explicit trim/lower normalization used by creation. | |
| title | JSON | optional nonempty string, max 128 | String/trim stored. |
| description | JSON | optional string | String/trim stored; no explicit length validation. |
subscription_id | JSON | optional existing Pricing ID | If changed, validates Pricing/team linked eligibility; stored int-filter value. Not membership ID. |
code, activations_limit, start_date, end_date, resource_id | JSON | ignored | No assignment branch for these inputs. |
Result
HTTP 200 top-level invite record. No usage/mail/available fields are added.
Example: Change invite 3001’s display title without resending
Update title only; the returned code remains the existing code unless it was empty before save.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/teams/1001/invites/3001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"title": "Join Example Newsroom"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/invites/3001`, {
method: "PUT", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"title": "Join Example Newsroom"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'title': 'Join Example Newsroom'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/teams/1001/invites/3001",
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:
{
"id": 3001,
"email": "reader@example.com",
"title": "Join Example Newsroom",
"code": "SYNTHETIC-INVITE-CODE-DO-NOT-USE-0000000001"
}
Consequential alternate
A mismatched invite/team pair returns HTTP 401 excerpt (other metadata omitted):
{
"error": "invite_access",
"error_description": "This invitation is not available to your team!"
}
No intended update occurs through this rejected pair.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check intended team/Plan/resource/role with integration owner. |
| 404 | team_not_found / invite_not_found | Selected record absent. | Check intended team/invite record IDs. |
| 401 | invite_access; This invitation is not available to your team! | Invite belongs to another team. | Use the intended matching team/invite pair. |
| 406 | incorrect_data; Body must be json format | No truthy JSON body. | Supply object with intended supported fields. |
| 409 | invalid_email / invalid_title / invalid_subscription_id | Supplied-field validation fails. | Correct indicated field or inspect Pricing eligibility/capacity. |
| 409 | exception; Please contact to administrator | Caught save/refresh/serialization error. | Reconcile saved invite before another change. |
Next task
Read invite detail for current fields. If sending again is intended, separately read resend.
Delete an invitation record
DELETE /api/v1/user/teams/{team_id}/invites/{id}
Attempt deletion of the saved invitation; it does not remove an already activated user or membership.
Before you call
Use the shared member context and selected-team management eligibility. Team and invite are looked up by IDs without resource filtering, then invite.team_id must match team.id. This checks the selected pair, not stronger owner/resource scope.
The action does not reject an already-used invite and performs no explicit activation-history/membership cleanup or email recall. No model deletion hook/cascade is declared for Invites; database constraints/cascades can affect result. No delete event is emitted; inspect Boolean result.
Request
Bodyless request.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended managed team ID. |
| id | path | required digits | Invite record ID from create/list; not redeemable code. |
| token, resource | headers | existing context | Member identity and selected resource/eligibility/event context. |
Result
HTTP 200 JSON:
| Field | Type / presence | Meaning |
|---|---|---|
| result | boolean | Model delete return, not membership removal. |
Example: Delete invite 3001 without treating it as member removal
To remove invitation 3001 from team 1001, use the manager context that passes this action’s eligibility check. The operation attempts invitation-record deletion; it does not remove a team member.
curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/teams/1001/invites/3001" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/invites/3001`, {
method: "DELETE", 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/user/teams/1001/invites/3001",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="DELETE")
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:
{
"result": true
}
Consequential alternate
Delete can return false with HTTP 200:
{
"result": false
}
No model-error detail is serialized. An already activated member’s relationship is not explicitly removed by this action.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check intended team/Plan/resource/role with integration owner. |
| 404 | team_not_found / invite_not_found | Selected record absent. | Check intended team/invite record IDs. |
| 401 | invite_access; This invitation is not available to your team! | Invite belongs to another team. | Use the intended matching team/invite pair. |
| 200 | result:false | Model deletion failed. | Ask integration owner to inspect dependent state/constraints before another attempt. |
| Unspecified | No dedicated deletion catch | Deletion throws or other handling fails. | Reconcile invite and dependent state; no cascade/access-removal promise. |
Next task
Inspect intended invite list; handle member relationships as a separate task. Invite deletion is not a refund or account deletion.
Request an invitation resend
POST /api/v1/user/teams/{team_id}/invites/{invite_id}/resend
Attempt the individual_in_team_invite queue event for the stored recipient, with no invitation edit or activation.
Before you call
Use the shared resend context and effects.
Request
No action body is consumed.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended managed team ID. |
invite_id | path | required digits | Invite record ID from create/list; not redeemable code. |
| token, resource | headers | existing context | Member identity and selected resource/eligibility/event context. |
Result
HTTP 200 JSON:
| Field | Type / presence | Meaning |
|---|---|---|
| result | boolean true | Action completed its queue-attempt path; not mail delivery or invite validity. |
Example: Request another notification for invite 3001
Select the intended existing team/invite pair. The stored recipient reader@example.com is used; no new body email/code is accepted.
curl -X POST "${WALLKIT_API_BASE}/api/v1/user/teams/1001/invites/3001/resend" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/invites/3001/resend`, {
method: "POST", 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/user/teams/1001/invites/3001/resend",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="POST")
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:
{
"result": true
}
Consequential alternate
Invite lookup missing gives HTTP 404 excerpt (other metadata omitted):
{
"error": "invite_not_found",
"error_description": "Invite not found"
}
A swallowed queue transmission exception can instead still produce result:true. Neither response proves delivery.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check intended team/Plan/resource/role with integration owner. |
| 404 | team_not_found / invite_not_found | Selected record absent. | Check intended team/invite record IDs. |
| 401 | invite_access; This invitation is not available to your team! | Invite belongs to another team. | Use the intended matching team/invite pair. |
| 200 | result:true despite swallowed queue failure | Queue transmission exception logged internally. | Reconcile notification handling with integration owner; avoid blind resend loops. |
| Unspecified | No dedicated resend catch | Other context/queue handling failure. | Inspect selected records/context with integration owner before another send attempt. |
Next task
Read invite detail for stored recipient/code. Invitee Group-memberships (teams) management remains separate; resend does not reserve a seat or reset usage.
Resend through the admin-prefixed registration
POST /api/v1/admin/teams/{team_id}/invites/{invite_id}/resend
Attempt the individual_in_team_invite queue event for the stored recipient, with no invitation edit or activation.
Before you call
Use the shared resend context and effects.
This admin-prefixed route uses the same api Teams.resendInvite action and user ACL grant, not a separate admin-only action. Prefix changes credential lookup: an existing token can resolve without resource-scoped session selection before controller initialization; resource-key admin context is a separate existing mechanism. Configured Firebase resolution can still apply in this api controller. Supply the intended resource header for eligibility/event context. Use only an existing authorized token; no key creation is part of this route.
Request
No action body is consumed.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended managed team ID. |
invite_id | path | required digits | Invite record ID from create/list; not redeemable code. |
| token, resource | headers | existing context | Member identity and selected resource/eligibility/event context. |
Result
HTTP 200 JSON:
| Field | Type / presence | Meaning |
|---|---|---|
| result | boolean true | Action completed its queue-attempt path; not mail delivery or invite validity. |
Example: Use the same resend action with admin-prefix context
Select the intended existing team/invite pair. The stored recipient reader@example.com is used; no new body email/code is accepted.
curl -X POST "${WALLKIT_API_BASE}/api/v1/admin/teams/1001/invites/3001/resend" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/admin/teams/1001/invites/3001/resend`, {
method: "POST", 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/admin/teams/1001/invites/3001/resend",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="POST")
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:
{
"result": true
}
Consequential alternate
Invite lookup missing gives HTTP 404 excerpt (other metadata omitted):
{
"error": "invite_not_found",
"error_description": "Invite not found"
}
A swallowed queue transmission exception can instead still produce result:true. Neither response proves delivery.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Action eligibility fails. | Check intended team/Plan/resource/role with integration owner. |
| 404 | team_not_found / invite_not_found | Selected record absent. | Check intended team/invite record IDs. |
| 401 | invite_access; This invitation is not available to your team! | Invite belongs to another team. | Use the intended matching team/invite pair. |
| 200 | result:true despite swallowed queue failure | Queue transmission exception logged internally. | Reconcile notification handling with integration owner; avoid blind resend loops. |
| Unspecified | No dedicated resend catch | Other context/queue handling failure. | Inspect selected records/context with integration owner before another send attempt. |
Next task
Read invite detail for stored recipient/code. Invitee Group-memberships (teams) management remains separate; resend does not reserve a seat or reset usage.
List non-owner team members
GET /api/v1/user/teams/{team_id}/users
Page users joined through non-owner relationships; inactive/removed/suspended relationships are not automatically excluded.
Before you call
Use the shared member context and selected-team management eligibility. The selected team is looked up by ID without resource filtering.
Owners are excluded using role != owner. There is no resource/global-user-active/relationship-status/expiry filter beyond supplied metadata filter. List items use full user without resource argument and team-scoped membership summaries; the requested team_relationship expansion is disabled, so no selected-team role/status/team_relationship expansion is returned. Conditional full-user admin role retains its separate account meaning. Do not infer those fields from team-detail users.
Request
Bodyless request.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID. |
| token, resource | headers | existing member/resource context | Manager identity and eligibility context. |
| filter | query | optional bracket map | Users/UserTeamRelationships model-field filtering; fixed team and non-owner predicates remain. |
| by | query | string, default Users.id | Direct ordering expression, no strict allowlist. |
| sort | query | string, default DESC | Direct order direction, no strict enum validation. |
| limit | query | int-filter/cast, default 10 | Page size; no explicit upper bound. |
| page | query | raw cast integer | Send 1 explicitly; no explicit default. |
Result
HTTP 200 items array and common paginator. Item projection is member-list item, empty [] possible. No active/unexpired membership restriction in team-scoped summary query.
Example: Inspect user 4002 without confusing it with a relationship ID
Read first page of non-owner users for team 1001. The excerpt deliberately has no role/status, which this list does not add.
curl "${WALLKIT_API_BASE}/api/v1/user/teams/1001/users?page=1&limit=10" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/users?page=1&limit=10`, {
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/user/teams/1001/users?page=1&limit=10",
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": 4002,
"email": "reader@example.com",
"subscriptions": []
}
],
"paginator": {
"current_page": 1,
"page": 1,
"total_pages": 1,
"total_items": 1,
"limit": 10
}
}
Consequential alternate
If only an owner exists, HTTP 200 excerpt:
{
"items": []
}
Empty non-owner list does not imply an empty team. Removed/suspended rows can still appear without a status filter.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Manager member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Neither eligibility nor active team admin/owner passes. | Check intended Plan/resource/team role with integration owner. |
| 404 | team_not_found | Selected record missing. | Use intended team ID. |
| 200 | items:[] | No selected non-owner rows. | Handle empty page; owner rows belong to team detail. |
| Unspecified | No dedicated list catch | Metadata/query/serialization failure. | Correct filter/order and inspect stored relationships through integration owner. |
Next task
Read member detail using user 4002. For the actual selected-team role/status, use team detail, not an invented list field.
Read a member linked to the selected team
GET /api/v1/user/teams/{team_id}/users/{user_id}
Read an existing person only after confirming a relationship to the selected team, regardless of that relationship’s status.
Before you call
Use the shared member context and selected-team management eligibility. The selected team is looked up by ID without resource filtering.
It fetches user by ID without a resource-registration/active check and requires any user/team relationship. A removed/suspended relationship still passes that association check. Returned resource state differs from team relationship state; do not substitute active/locked for team status.
Request
Bodyless request.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID. |
user_id | path | required digits | Person’s user ID, not team relationship ID. |
| token, resource | headers | existing member/resource context | Manager identity and eligibility context. |
Result
HTTP 200 top-level selected member with resource context. It includes that person’s resource memberships and partner-scoped active teams, not only the selected team. No auth token or last_action sign-in field is added.
Example: Read user 4002’s selected-resource account state
Use the person ID from list/team detail. This excerpt omits resource profile fields and nested membership/team objects.
curl "${WALLKIT_API_BASE}/api/v1/user/teams/1001/users/4002" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/users/4002`, {
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/user/teams/1001/users/4002",
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:
{
"id": 4002,
"email": "reader@example.com",
"subscriptions": [],
"teams": []
}
Consequential alternate
No user/team relationship yields HTTP 401 excerpt (other metadata omitted):
{
"error": "invite_access",
"error_description": "This user is not available to your team!"
}
The code name is invite_access, but this error concerns team membership association, not an invite code.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Manager member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Neither eligibility nor active team admin/owner passes. | Check intended Plan/resource/team role with integration owner. |
| 404 | team_not_found / user_not_found | Selected record missing. | Use intended team/person IDs. |
| 401 | invite_access | User exists but has no selected-team relationship. | Check intended team/person association; replacing an invite code does not repair it. |
| Unspecified | No dedicated detail catch | Query/nested account projection failure. | Ask integration owner to inspect related account data. |
Next task
Choose role/status update, removal or suspension only for the intended relationship change.
Update a team member relationship
PUT /api/v1/user/teams/{team_id}/users/{user_id}
Change supplied role/status, optionally attempt a selected Pricing change. This is not global user suspension or owner reassignment.
Before you call
Use the shared member context and selected-team management eligibility. The team lookup uses its ID without resource filtering.
- Target: any existing person/team relationship qualifies, including owner, removed or suspended relationships.
- Role and status: role can become user or admin; status can become active, removed or suspended. There is no last-owner guard, so a role edit can demote the owner. The relationship save is not checked and attempts a narrow cache update.
- Pricing: if an exact person/team/Pricing membership already exists, supplying
subscription_iddoes not replace it. Otherwise the Pricing checkout path fails with the literal message “Checkout teems is disabled ”, even for a free Pricing. Role/status may already have been saved; this failure has no explicit rollback. A successful new-Pricing grant or purchase is not established.
Without the disabled checkout branch, the transaction commits and returns account state. Role/status edits do not explicitly delete memberships, write history or change sponsorships. Renewal processing skips inactive team relationships; that does not prove immediate content-access revocation or a refund.
Request
JSON object required; omitted/null fields unchanged.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID. |
user_id | path | required digits | Person’s user ID, not team relationship ID. |
| token, resource | headers | existing member/resource context | Manager identity and eligibility context. |
| role | JSON | optional nonempty string: user or admin | Stored relationship role; owner cannot be assigned through this field. No owner-preservation guard. |
| status | JSON | optional nonempty string: active, removed, suspended | Stored relationship status, not global user state. |
subscription_id | JSON | optional existing Pricing ID | Existence/team linked eligibility/capacity/payment-customer validation; missing exact membership reaches disabled checkout. |
Result
HTTP 200 top-level selected member with resource context. It includes that person’s resource memberships and partner-scoped active teams, not only the selected team. No auth token or last_action sign-in field is added.
Read the returned person ID/state, not an assumed durable role/status result: the selected team relationship is not separately serialized.
Example: Change role/status without invoking disabled Pricing checkout
Use a status/role-only request for user 4002. No subscription_id is sent. Intended ordinary success projection can show resource account state while the selected team relationship differs.
curl -X PUT "${WALLKIT_API_BASE}/api/v1/user/teams/1001/users/4002" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}" \
-H "Content-Type: application/json" \
--data-raw '{"role": "user", "status": "active"}'
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/users/4002`, {
method: "PUT", headers: { "resource": process.env.RESOURCE_KEY, "token": process.env.USER_TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"role": "user", "status": "active"})
});
console.log(response.status, await response.json());
import os, json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
body = {'role': 'user', 'status': 'active'}
request = Request(os.environ["WALLKIT_API_BASE"] + "/api/v1/user/teams/1001/users/4002",
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:
{
"id": 4002,
"email": "reader@example.com",
"subscriptions": [],
"teams": []
}
Consequential alternate
If a supplied Pricing has no exact existing user/team membership, HTTP 406 excerpt (other metadata omitted):
{
"error": "team_error",
"error_description": "Checkout teems is disabled "
}
A reachable primary success for new Pricing assignment is N/A in this source. Role/status may already have been saved; no atomic rollback or payment guarantee.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Manager member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Neither eligibility nor active team admin/owner passes. | Check intended Plan/resource/team role with integration owner. |
| 404 | team_not_found / user_not_found | Selected record missing. | Use intended team/person IDs. |
| 401 | invite_access | No selected user/team relationship. | Check intended person/team pair. |
| 406 | incorrect_data; Body must be json format | No truthy JSON body. | Supply object with supported fields. |
| 409 | invalid_role / invalid_status / invalid_subscription_id | Supplied field or Pricing eligibility fails. | Use accepted role/status/Pricing and inspect capacity. |
| 406 | team_error; Checkout teems is disabled | Missing exact membership enters disabled checkout. | Do not retry as a purchase; reconcile role/status and intended Pricing with owner. |
| 406 | subscription_error / payment_error | Corresponding caught exception. | Reconcile local state; no explicit rollback in these catches. |
| 409 | user_team; Not caught problem | Other caught exception. | Ask owner to reconcile relationship state before repeating. |
Next task
Inspect team detail for selected role/status and member detail for account state. Do not infer payment or content access.
Mark the selected team relationship removed
DELETE /api/v1/user/teams/{team_id}/users/{user_id}
Set relationship status to removed; keep the relationship/person record rather than deleting the user.
Before you call
Use the shared member context and selected-team management eligibility. Team lookup uses its ID without resource filtering.
Owner restriction: the target must have a person/team relationship. An owner can be changed only if another owner-role relationship exists. That replacement does not have to be active, so the check does not guarantee an active replacement owner.
Save and notification: the save result is checked. The team_owner_removed_user event is attempted only after a true save result. Saving attempts a narrow cache invalidation rather than a full cache refresh.
The action does not explicitly delete membership, write history, clean sponsorships, lock the global account or refund payment. Renewal processing skips inactive team relationships, but this request does not establish immediate content-access revocation.
Request
No action body is consumed; status is fixed to removed.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID. |
user_id | path | required digits | Person’s user ID, not team relationship ID. |
| token, resource | headers | existing member/resource context | Manager identity and eligibility context. |
Result
HTTP 200 JSON:
| Field | Type / presence | Meaning |
|---|---|---|
| result | boolean | Relationship save return; true does not delete person/account or certify notification delivery. |
Example: Remove user 4002 from active team participation
As a permitted team manager, mark user 4002 removed from team 1001. Use the person ID, without a relationship ID or status body.
curl -X DELETE "${WALLKIT_API_BASE}/api/v1/user/teams/1001/users/4002" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/users/4002`, {
method: "DELETE", 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/user/teams/1001/users/4002",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="DELETE")
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:
{
"result": true
}
Consequential alternate
For the only owner-role relationship, HTTP 406 excerpt (other metadata omitted):
{
"error": "team_user_fail",
"error_description": "You can not delete the owner of the team, first change the owner."
}
The same literal message is used by suspension. A false save can instead return HTTP 200 result:false; that does not confirm the status change.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Manager member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Neither eligibility nor active team admin/owner passes. | Check intended Plan/resource/team role with integration owner. |
| 404 | team_not_found / user_not_found | Selected record missing. | Use intended team/person IDs. |
| 401 | team_user_fail; This user is not in your team. | Selected relationship absent. | Check intended team/person pair. |
| 406 | team_user_fail; You can not delete the owner of the team, first change the owner. | Owner has no other owner-role relationship. | Ask integration owner to handle intended ownership; this route does not assign owner. |
| 200 | result:false | Relationship save failed. | Reconcile stored status before any repeat. |
| Unspecified | No dedicated save/event catch | Save/event handling throws. | Inspect relationship/queue state; no safe retry or delivery guarantee. |
Next task
Use team detail to inspect stored status. If reactivation is intended, separately consider status update; global identity/content access remains a separate decision.
Suspend the selected team relationship
PATCH /api/v1/user/teams/{team_id}/users/{user_id}
Set relationship status to suspended; keep the relationship/person record rather than deleting the user.
Before you call
Use the shared member context and selected-team management eligibility. Team lookup uses its ID without resource filtering.
Owner restriction: the target must have a person/team relationship. An owner can be changed only if another owner-role relationship exists. That replacement does not have to be active, so the check does not guarantee an active replacement owner.
Save and notification: the save result is checked. The team_owner_suspended_user event is attempted only after a true save result. Saving attempts a narrow cache invalidation rather than a full cache refresh.
The action does not explicitly delete membership, write history, clean sponsorships, lock the global account or refund payment. Renewal processing skips inactive team relationships, but this request does not establish immediate content-access revocation.
Request
No action body is consumed; status is fixed to suspended.
| Name | Location | Type / requirement | Meaning / constraint |
|---|---|---|---|
team_id | path | required digits | Intended team ID. |
user_id | path | required digits | Person’s user ID, not team relationship ID. |
| token, resource | headers | existing member/resource context | Manager identity and eligibility context. |
Result
HTTP 200 JSON:
| Field | Type / presence | Meaning |
|---|---|---|
| result | boolean | Relationship save return; true does not delete person/account or certify notification delivery. |
Example: Suspend user 4002’s team participation
As a permitted team manager, suspend user 4002’s participation in team 1001. Use the person ID, without a relationship ID or status body.
curl -X PATCH "${WALLKIT_API_BASE}/api/v1/user/teams/1001/users/4002" \
-H "resource: ${RESOURCE_KEY}" \
-H "token: ${USER_TOKEN}"
const response = await fetch(`${process.env.WALLKIT_API_BASE}/api/v1/user/teams/1001/users/4002`, {
method: "PATCH", 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/user/teams/1001/users/4002",
headers={"resource": os.environ["RESOURCE_KEY"], "token": os.environ["USER_TOKEN"]}, method="PATCH")
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:
{
"result": true
}
Consequential alternate
For the only owner-role relationship, HTTP 406 excerpt (other metadata omitted):
{
"error": "team_user_fail",
"error_description": "You can not delete the owner of the team, first change the owner."
}
The same literal message is used by suspension. A false save can instead return HTTP 200 result:false; that does not confirm the status change.
Recovery
| HTTP status | API code / shape | Cause | Next action |
|---|---|---|---|
| 401 | auth_failed / auth_access_fail | Manager member context fails. | Follow credential recovery. |
| 401 | incorrect_data | Neither eligibility nor active team admin/owner passes. | Check intended Plan/resource/team role with integration owner. |
| 404 | team_not_found / user_not_found | Selected record missing. | Use intended team/person IDs. |
| 401 | team_user_fail; This user is not in your team. | Selected relationship absent. | Check intended team/person pair. |
| 406 | team_user_fail; You can not delete the owner of the team, first change the owner. | Owner has no other owner-role relationship. | Ask integration owner to handle intended ownership; this route does not assign owner. |
| 200 | result:false | Relationship save failed. | Reconcile stored status before any repeat. |
| Unspecified | No dedicated save/event catch | Save/event handling throws. | Inspect relationship/queue state; no safe retry or delivery guarantee. |
Next task
Use team detail to inspect stored status. If reactivation is intended, separately consider status update; global identity/content access remains a separate decision.