Manage workspaces, members, and invites. Workspaces are called teams in the API: endpoints live under /teams and requests carry team_id. Every API key belongs to a workspace, and apps, tasks, secrets, and billing are scoped to it.
Requires API key scopes teams:read (list, get, members, invites) or teams:write (create, update, delete, member changes, invite management). Create keys in settings → workspace → api keys.
→ Authentication · Entitlements: plan limits per workspace
Get current user
GET /me
Returns the authenticated user and the current workspace (team) resolved from the API key (or guest context). Requires a valid API key.
Response
1{2 "user": {3 "id": "usr_abc123",4 "email": "[email protected]",5 "name": "you",6 "full_name": "Alex Example",7 "default_team_id": "team_personal",8 "avatar_url": "https://...",9 "metadata": {10 "completed_onboarding": true,11 "use_case": "agents",12 "signup_source": "github",13 "terms_accepted_at": "2026-01-15T10:30:00Z",14 "terms_version": "2026-01"15 }16 },17 "team": {18 "id": "team_xyz",19 "type": "team",20 "name": "Acme",21 "username": "acme",22 "setup_completed": true,23 "status": "active"24 },25 "org": {26 "id": "org_abc",27 "slug": "acme",28 "name": "Acme Inc",29 "avatar_url": "https://..."30 },31 "team_view": {32 "team_id": "team_xyz",33 "kind": "org_member",34 "governance": {35 "billing": { "by": "org", "team_id": "org_abc" },36 "policy": { "by": "org", "team_id": "org_abc" }37 },38 "org": {39 "id": "org_abc",40 "name": "Acme Inc",41 "slug": "acme",42 "avatar_url": "https://...",43 "can": ["manage_org", "manage_billing", "view_billing"]44 },45 "can": ["edit_profile", "manage_members", "view_billing"]46 },47 "personal_team_id": "team_personal",48 "needs_username": false49}team is omitted when no workspace context is attached to the request.
org is present when the current workspace belongs to an organization. org.is_admin is not set on /me — use team_view (or GET /teams/{id}/view) for what the caller may do.
team_view is the same object as GET /teams/{id}/view: workspace kind, who governs billing and usage policy, and the capability strings the API enforces on settings routes. Omitted when there is no workspace context.
The top-level JSON object is typed as MeResponse in the JavaScript, Python, and Swift SDKs (team is a full TeamDTO, not the slimmer relation object from GET /teams). See Python SDK — GET /me and JavaScript SDK — generated types.
| Field | Description |
|---|---|
personal_team_id | The caller's personal workspace ID. Omitted or empty for managed accounts, which have no personal workspace. |
needs_username | true when the personal workspace has not finished setup (username not chosen yet). Always false for managed accounts. These fields describe the personal workspace, not the session workspace in team — for example, someone who accepts an invite may land in the inviter's workspace while needs_username is still true until they complete setup on their personal workspace. |
When needs_username is true, pick a username with GET /teams/check-username, then call POST /teams/{personal_team_id}/complete-setup (use the personal_team_id value from this response as {id}).
The nested user.metadata object (UserMetadataDTO) tracks onboarding and consent state:
| Field | Description |
|---|---|
completed_onboarding | Whether the user finished workspace onboarding |
use_case, use_case_reason, use_case_privacy | Onboarding survey answers |
signup_source | How the account was created (e.g. github, google) |
terms_accepted_at | ISO timestamp when the user accepted the current terms (optional) |
terms_version | Terms version string the user accepted |
JavaScript SDK:
1// me() returns Response<MeResponse>; the body is on `data`2const { data: { user, team, team_view, org, personal_team_id, needs_username } } =3 await client.teams.me();4// team is TeamDTO; use team_view.can for settings gatesExample:
1curl https://api.inference.sh/me \2 -H "Authorization: Bearer inf_your_key"Survey
Customer-discovery and open feedback submitted from the CLI (belt feedback) or other clients. Structured prompts are driven by active bounty programs named survey-<question_id>; the CLI reads question text and options from the platform form infsh/<question_id> (first question in the schema). When programs cannot be loaded, the CLI uses built-in fallback wording for the classic IDs below.
List your responses
GET /me/survey
Returns survey answers for the authenticated user. The response lists every structured answer first (discovery, use_case, need, testimonial, and legacy feedback), then the most recent freeform open:* rows, up to 100 rows in total. Open feedback never pushes structured answers off the list. The CLI uses this list to decide which onboarding questions are still pending.
Response:
1{2 "responses": [3 {4 "id": "srv_abc123",5 "question_id": "discovery",6 "response": "github",7 "agent": "cursor",8 "source": "cli",9 "context": "belt login, belt app run, belt task get abc123"10 }11 ]12}Submit a response
POST /me/survey
| Field | Type | Required | Description |
|---|---|---|---|
question_id | string | Yes | discovery, use_case, need, testimonial, feedback (legacy), or open (freeform) |
response | string | Yes | Answer text |
agent | string | No | Agent runtime that submitted the answer (e.g. cursor, claude-code) |
source | string | No | Client identifier (CLI sends cli) |
context | string | No | Recent commands that led to submission. The CLI sends the last five belt commands (comma-separated) from ~/.belt/history. Other clients may send a page path or other freeform string. |
Each question_id is the platform form infsh/<question_id>, and the form's submit policy decides what a repeat answer does. The structured questions (discovery, use_case, need, testimonial) take one answer per user: a second answer returns 409 already_submitted. The legacy feedback question is closed and returns 409 form_closed. Open feedback (open) takes any number of answers. Which questions the CLI asks depends on bounty configuration (survey-<id> programs); it walks them in program order.
Under the hood, each structured answer is also stored as a form submission to infsh/<question_id>. For general form APIs (create forms, submit JSON answers, list submissions), see Forms API.
Rewards: POST /me/survey records the answer only; it grants nothing, and neither does belt feedback. The response may still include granted_amount and reward_blocked_reason for older clients; they are always 0 and empty. Credits come from the matching survey-<question_id> bounty program through POST /me/bounty. The seeded survey-* programs use proof_type: text, which belt bounty claim does not handle, so claim them with a direct POST /me/bounty call. A program with proof_type: form and proof_form: infsh/<question_id> takes the response.id from this call as proof_id.
Fill-in placeholder text (for example your answer here or <your answer>, copied from CLI help) is rejected with 422 validation_error and nothing is stored.
Example:
1curl -X POST https://api.inference.sh/me/survey \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "question_id": "discovery",6 "response": "github",7 "source": "cli",8 "context": "belt login, belt app run, belt task get abc123"9 }'Response:
1{2 "response": {3 "id": "srv_abc123",4 "question_id": "discovery",5 "response": "github",6 "source": "cli",7 "context": "belt login, belt app run, belt task get abc123"8 }9}Claim the matching bounty (when configured):
1curl -X POST https://api.inference.sh/me/bounty \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "bounty_id": "survey-discovery",6 "proof_id": "discovery",7 "source": "cli"8 }'For a proof_type: text program, proof_id is a key you choose (one claim per key and program). For a proof_type: form program, use the response.id from POST /me/survey.
Errors:
| HTTP | Code | Cause |
|---|---|---|
400 | validation_error | unknown question: <id>: no infsh/<id> form |
409 | already_submitted | You already answered this one-answer question |
409 | form_closed | The question no longer takes answers (legacy feedback) |
422 | validation_error | The answer fails the question's schema (empty or too short), or is fill-in placeholder text |
Bounties
Incentive programs that grant credits when you complete qualifying actions (for example, deploying a useful app or answering survey questions). Requires authentication.
List active programs
GET /bounties
Returns active bounty programs sorted by notice_priority (ascending). Each program includes its stable name (used as bounty_id when claiming), reward amount, proof requirements, and CLI notice configuration.
The belt CLI calls this endpoint to populate stderr bounty notices. Responses are cached locally for 6 hours (~/.belt/cache/bounty-programs.json); admin edits to notice fields propagate on the next cache refresh without a CLI release. Programs with proof_type: form surface in the notice block and in belt feedback as belt bounty claim <program> prompts until the bounty is claimed; survey-<id> programs stay on the single-question belt feedback path. Reward amounts are per program and may change; belt bounty list shows the live values. See Forms API and CLI setup — Feedback.
Response — array of BountyProgramDTO objects:
| Field | Description |
|---|---|
name | Program ID (for example app-builder, survey-discovery) |
description | Rules shown to users and agents |
amount_microcents | Reward per claim, in microcents |
grant_type | Credit grant type (bounty or survey) |
expiry_days | Days until granted credits expire |
max_per_user | Total claims per user (0 = unlimited). Independent of max_per_day — surveys default to 1. |
max_per_day | Daily claim cap (0 = no daily cap, unique proof only) |
proof_type | Required proof: app, form, text, or none |
proof_form | When proof_type is form, the form ref (namespace/name) the submission must answer |
requires_payment_method | When true, claims return 402 until the team saves a payment method (no claim row is stored, so you can retry after adding a card) |
status | active, paused, or ended |
notice_text | Message shown in CLI stderr notices |
notice_cooldown_hours | Hours between notice displays |
notice_priority | Lower values surface first when multiple notices are due |
The default app-builder program (seeded on new deployments) pays $2 per claim (amount_microcents: 200000000), with max_per_day: 1 (one successful claim per calendar day) and unlimited total claims across different apps (max_per_user: 0). CLI stderr notices use the program's notice_text (for example, pointing at belt bounty claim app-builder <namespace/app>).
Claim a bounty
POST /me/bounty
| Field | Type | Required | Description |
|---|---|---|---|
bounty_id | string | Yes | Program name (for example app-builder) |
proof_id | string | Yes | Proof identifier — see below by proof_type |
agent | string | No | Agent runtime that submitted the claim (CLI sends detected runtime) |
source | string | No | Client identifier (CLI sends cli) |
proof_type | proof_id meaning | Stored proof_ref |
|---|---|---|
app | App ID you own with a deployed version | App namespace/name |
form | Your submission id to the program's proof_form | That form's namespace/name |
text, none | Freeform proof key (for example a question slug) | Same as proof_id |
For proof_type: app, resource_id in the response is the app ID. For proof_type: form, resource_id is the form ID and proof_id is the submission ID.
Response:
1{2 "submission": {3 "id": "bsm_abc123",4 "bounty_id": "app-builder",5 "resource_id": "01abc123def456",6 "proof_id": "01abc123def456",7 "proof_ref": "you/my-app",8 "agent": "cursor",9 "source": "cli"10 },11 "granted_amount": 20000000012}submission field | Description |
|---|---|
resource_id | Linked resource ID when applicable — for proof_type: app, the app ID |
proof_id | Stable proof key — app ID, form submission ID, or text key |
proof_ref | Human-readable reference (namespace/name for apps and form proofs) |
granted_amount is in microcents (200000000 = $2.00 for the default app-builder reward). Omitted or 0 when no credits were granted (for example, duplicate claim).
Errors:
| HTTP | Code | Message | Cause |
|---|---|---|---|
400 | validation_error | proof_id is required | Missing proof_id |
400 | validation_error | app must be deployed before claiming a bounty | App has no deployed version |
400 | validation_error | submission … answers …; bounty … takes a submission to … | Form submission is not for this program's proof_form |
402 | payment_method_required | The program sets requires_payment_method and the team has no saved card; nothing is recorded | |
403 | forbidden | form submission … is not yours | proof_id names another user's form submission |
404 | not_found | unknown bounty: <id> | No program with that name |
404 | not_found | app not found: <id> | App does not exist or is not yours |
404 | not_found | form submission not found: … | Unknown submission id |
409 | conflict | bounty program <id> is not active | Program paused or ended |
409 | conflict | daily bounty limit reached — try again tomorrow | max_per_day reached |
409 | already_claimed | bounty already claimed for this proof | Same proof_id already claimed for this program |
409 | already_claimed | bounty limit reached — you've already claimed this N time(s) | max_per_user reached |
When the team has no saved payment method and the program sets requires_payment_method, the API returns 402 with code payment_method_required and meta.billing_page set to /settings/team/billing/payments. The claim is rejected and not stored, so you can call POST /me/bounty again with the same proof_id after adding a card.
Example:
1curl -X POST https://api.inference.sh/me/bounty \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "bounty_id": "app-builder",6 "proof_id": "01abc123def456",7 "source": "cli"8 }'Survey rewards use the same bounty system (bounty_id = survey-<question_id>) but are claimed explicitly via POST /me/bounty after the answer is stored — see Survey and Forms API.
Bounty program administration (creating programs, pausing, viewing claims) is internal and not exposed on the public REST API.
SDKs: No client.bounties namespace yet in either SDK. Import BountyProgramDTO, SubmitBountyRequest, and SubmitBountyResponse from inferencesh.types (Python) or @inferencesh/sdk (JavaScript) when calling these endpoints directly — see Python SDK — generated bounty types and JavaScript SDK — generated bounty types.
→ CLI setup — App bounties · Billing — credit grants
Data export
Request a ZIP archive of your account and workspace data (GDPR Articles 15 and 20: right of access and data portability). Requires authentication as the account owner (session token or API key).
Request export
POST /me/export
Starts an asynchronous export job. Returns 202 Accepted with the export ID and initial status.
Response:
1{2 "id": "uex_abc123",3 "status": "pending"4}If you already have a pending or processing export from the last 24 hours, the API returns that existing job instead of starting a duplicate.
Get export status
GET /me/export/{id}
Poll until status is completed or failed. When complete, the response includes a presigned download_url (valid for one hour).
| Field | Description |
|---|---|
id | Export job ID |
status | pending, processing, completed, or failed |
download_url | Presigned URL to the ZIP (when completed) |
completed_at | ISO timestamp when the archive was ready |
expires_at | When the download link expires (seven days after completion) |
error | Failure message (when failed) |
Example:
1# Start export2curl -X POST https://api.inference.sh/me/export \3 -H "Authorization: Bearer $(belt auth token)"45# Poll for download URL6curl https://api.inference.sh/me/export/uex_abc123 \7 -H "Authorization: Bearer $(belt auth token)"The ZIP contains JSON files for your user profile, sessions (tokens redacted), API keys (masked), workspace resources (apps, agents, flows, knowledge, projects, chats, tasks, files, secrets, integrations, triggers), billing, notifications, and survey responses. Processing may take several minutes; the server enforces a 10-minute timeout per job.
List workspaces
GET /teams
Returns all workspaces the authenticated user belongs to (personal workspace, shared workspaces, and organization workspaces you administer).
Requires teams:read.
Response
Array of workspace objects:
| Field | Description |
|---|---|
id | Workspace ID |
type | personal, team, org, or system. See Workspace types. |
name | Display name |
username | Unique workspace username (used in namespaces and belt auth switch) |
avatar_url | Avatar URL |
setup_completed | Whether the workspace finished onboarding (username chosen) |
status | Workspace status |
email | Contact email (when set) |
org_id | Org this workspace belongs to. An org workspace points at itself. Omitted for standalone workspaces. |
max_concurrency | Platform-managed field (usually 0). Your workspace's concurrent task limit comes from the concurrency entitlement on your subscription plan. See Entitlements: concurrent task limits |
Workspace types
type | Description |
|---|---|
personal | Personal workspace, one per account |
team | Shared workspace for collaborators. When attached to an org, org_id is the org's ID. |
org | An organization's own workspace. Created with the org, with the same ID as the org, and org_id points at itself. Its members are the org's admins, so it has owners and admins only. Holds org billing and org-scope credentials. |
system | Internal platform workspace |
Example:
1curl https://api.inference.sh/teams \2 -H "Authorization: Bearer inf_your_key"JavaScript SDK: client.teams.list()
Get workspace
GET /teams/{id}
Returns a single workspace by ID. Requires teams:read and membership (or admin access).
JavaScript SDK: client.teams.get(teamId)
Workspace settings view
GET /teams/{id}/view
Returns how the authenticated caller sees a workspace in settings: workspace kind, billing/policy governance, optional parent organization summary, and the can capability list the API uses to gate POST/PUT/DELETE routes under /teams/{id} and related org routes.
Requires teams:read. A caller with no role on the workspace (directly or through org standing) gets 404. This is the source of truth for UI permission checks — clients should not re-derive capabilities from roles alone.
Response (TeamViewDTO)
| Field | Description |
|---|---|
team_id | Workspace ID (same as path parameter) |
kind | Caller-relative kind: personal, team (standalone shared workspace), org_member (workspace inside an org), or org (the organization's own workspace) |
governance.billing | Who decides billing: by is self or org; team_id is the workspace that holds the balance |
governance.policy | Who decides usage policy: same shape as governance.billing |
org | When the workspace belongs to an org: id, name, slug, avatar_url, and can — capabilities on the organization workspace (billing, org admin actions, org credentials). Omitted for standalone workspaces |
can | Capability strings the caller holds on this workspace (for example manage_members, manage_billing, manage_org) |
Capability strings
The can arrays use the same identifiers the API evaluates on settings routes:
| Capability | Typical meaning |
|---|---|
edit_profile | Rename workspace, avatar, username setup |
manage_members | Invite, remove, and change roles for plain members (admins and owners) |
manage_admins | Grant, change, or remove admin and owner roles (owners only; omitted on personal workspaces) |
view_members | Read the member roster |
manage_keys | Workspace API keys: create workspace keys, list and revoke every key of the workspace |
create_keys | Create, list, and revoke your own personal keys. Absent for service accounts |
manage_vault | Vault membership and secrets layout |
view_billing | Read balance, usage, invoices |
manage_billing | Top-ups, payment methods, subscription checkout |
view_policy | Read effective usage policy |
manage_policy | Edit usage policy (org-wide on the org workspace; per-workspace on standalone teams) |
manage_org | Org admin actions (list workspaces, attach/create teams, managed users, org SSO) |
manage_sso | Team SAML connection |
archive | Delete/archive workspace |
create_team | POST /teams — create a standalone workspace. Absent on managed accounts (use POST /orgs/{id}/teams/create instead) |
create_org | POST /orgs — create an organization from a standalone workspace you own |
connect_org_credential | Connect org-scoped integrations |
connect_platform_credential | Platform admin: platform-scoped credentials |
On an org member workspace, can is the capability table for this workspace: your roster role, or — when you administer the org — your role on the organization workspace raised to that level (org owners act as owners on every member team; org admins as admins, without joining the roster). org.can carries rights on the organization workspace itself (for example manage_billing when you may change org payment settings). Do not use org.can to decide admin invites or role changes on a member workspace. Member-team owners and org owners acting through standing see view_billing on the member team but not manage_billing there when the org pays. Clients should use can from this endpoint (or from GET /orgs/{id}/teams per workspace) instead of inferring permissions from membership alone. Last-owner checks still count only listed owners on the workspace, not org standing.
Example:
1curl "https://api.inference.sh/teams/team_xyz/view" \2 -H "Authorization: Bearer inf_your_key" \3 -H "X-Team-ID: team_xyz"GET /me embeds the same payload as team_view for the API key's current workspace.
→ Organizations API: create orgs, list member workspaces, org admins
Workspace governance rules (usage policy)
A workspace's usage policy is its governance layer in the rules API: GET, PUT, and DELETE /teams/{id}/rules, single-rule routes under /teams/{id}/rules/{ruleId}, member policies under /teams/{id}/members/{userId}/rules, the blocked-attempts feed, and effective access on /teams/{id}/rules/access. Reads need view_policy; writes need manage_policy, which in an organization belongs to the org admins. See the Usage policy API.
Create workspace
POST /teams
Requires teams:write and the create_team capability on your current workspace context (see GET /teams/{id}/view). Managed accounts do not receive create_team; org admins create workspaces inside an org with POST /orgs/{id}/teams/create.
Request
| Field | Required | Description |
|---|---|---|
name | yes | Display name |
username | yes | Unique workspace username (kebab-case) |
email | no | Contact email |
1{2 "name": "Acme Engineering",3 "username": "acme-eng",4 "email": "[email protected]"5}Returns the created workspace object. New workspaces may need Complete setup before all features are available.
JavaScript SDK: client.teams.create({ name, username, email })
Update workspace
POST /teams/{id}
Updates workspace metadata. Requires teams:write and appropriate role (owner or admin).
JavaScript SDK: client.teams.update(teamId, { name, username, email })
Delete workspace
DELETE /teams/{id}
Permanently deletes a workspace. Requires teams:write and owner role. Personal workspaces cannot be deleted.
JavaScript SDK: client.teams.delete(teamId)
Check username availability
GET /teams/check-username?username={name}
Returns an AvailabilityResponse. Use before create or rename.
| Query | Required | Description |
|---|---|---|
username | Yes | Proposed username (normalized to lowercase server-side) |
exclude_team_id | No | Workspace ID to ignore. Use when updating so the workspace's current username does not count as a conflict |
Requires teams:read.
Response:
| Field | Type | Description |
|---|---|---|
value | string | Normalized username (lowercased) |
available | boolean | true if the username can be used |
reason | string | When available is false: taken (username in use) or reserved (on the platform reserved-name list). Omitted when available. |
Use reason for user-facing messages instead of maintaining a client-side reserved-name list — the server owns the full reserved namespace.
1{2 "value": "acme-eng",3 "available": false,4 "reason": "taken"5}JavaScript SDK: client.teams.checkUsername('acme-eng')
Complete setup
POST /teams/{id}/complete-setup
Chooses the workspace username during onboarding. For a new self-serve account, {id} is personal_team_id from GET /me while needs_username is true.
Request
1{2 "username": "acme"3}Requires teams:write.
Members
List members
GET /teams/{id}/members
Returns workspace members with roles. Requires teams:read.
| Field | Description |
|---|---|
id | Membership row ID |
user_id | Member user ID |
team_id | Team ID |
role | owner, admin, or member |
user | Nested user profile (email, name, avatar_url) |
assignable_roles | Roles the authenticated caller may set on this member, including their current role. Populated only when the caller may manage members. Empty or omitted when the caller cannot change anyone (for example, a member), when an admin views an owner they cannot demote, or when the sole owner cannot step down until another owner exists. Only owners may include owner in this list. |
removable | Whether the caller may remove this member. true only when removal is allowed (the last owner on a team is never removable). Omitted when the caller cannot manage members. |
These fields mirror the same checks enforced by POST /teams/{id}/members/{userId}/role and DELETE /teams/{id}/members/{userId}.
Use them to drive member-management UIs without reimplementing server rules:
- Admins may add, change, and remove plain members (
manage_members). Granting, changing, or removing admin or owner requiresmanage_admins(owners only). - A team must keep at least one owner. Demoting or removing the last owner returns 400 with code
last_owner. - The organization workspace accepts
ownerandadminonly —memberis not assignable there.
Member mutations require teams:write and route-level capability checks (manage_members for roster changes; the service enforces manage_admins when the target role is admin or owner). Refusals return 403 with code team_role_required (often with meta.capability set). See Workspace settings view instead of inferring rights from role names alone.
JavaScript SDK: client.teams.getMembers(teamId) — typed as TeamMemberDTO with assignable_roles and removable.
Python SDK: import TeamMemberDTO from inferencesh.types when calling this endpoint with httpx or requests (no client.teams namespace yet).
Add member
POST /teams/{id}/members
1{2 "email": "[email protected]",3 "role": "member"4}Requires teams:write and manage_members (and manage_admins when role is admin or owner). Adds an existing platform user by email, or creates a pending invite when the email is not registered. The email value is trimmed and lowercased before lookup; matching is case-insensitive. For Gmail and Googlemail, the API also treats addresses that deliver to the same mailbox as one address when checking for an existing member (see REST overview: Signup email restrictions).
JavaScript SDK: client.teams.addMember(teamId, { email, role })
Remove member
DELETE /teams/{id}/members/{userId}
Requires teams:write and manage_members (or manage_admins when removing an admin or owner). Also revokes the removed user's personal API keys on that workspace; workspace keys they created keep working. See API keys — when someone leaves.
JavaScript SDK: client.teams.removeMember(teamId, userId)
Update member role
POST /teams/{id}/members/{userId}/role
1{2 "role": "admin"3}Requires teams:write and manage_members / manage_admins as for Add member.
JavaScript SDK: client.teams.updateMemberRole(teamId, userId, 'admin')
Invites
List workspace invites
GET /teams/{id}/invites
Returns pending invites for a workspace. Requires teams:read.
JavaScript SDK: client.teams.listInvites(teamId)
Create invite
POST /teams/{id}/invites
1{2 "email": "[email protected]",3 "role": "member"4}Requires teams:write. Pending invites store the invited address trimmed and lowercased. A second invite to the same address replaces the earlier pending invite for that workspace. Invite emails are limited to 3 per hour per address (letter case and + aliases count as one address) and 100 per workspace per day; past either limit the API returns 429 rate_limited.
JavaScript SDK: client.teams.createInvite(teamId, { email, role })
Revoke invite
DELETE /teams/{id}/invites/{inviteId}
Requires teams:write.
JavaScript SDK: client.teams.revokeInvite(teamId, inviteId)
List my pending invites
GET /invites/me
Returns invites addressed to the authenticated user's email. Requires teams:read.
Get invite info (public)
GET /invites/info?token={token}
Returns a TeamInviteInfoDTO for the accept page. No API key required — the invite token is the credential.
Response is a TeamInviteInfoDTO:
| Field | Description |
|---|---|
id | Invite ID |
email | Address the invite was sent to |
role | Role the invitee receives on accept |
status | Standing now: a pending invite past expires_at reads expired; an invite to a workspace that no longer admits members reads revoked |
expires_at | ISO timestamp when a pending invite stops being valid |
team | Workspace summary: id, type (personal, team, or org), name, avatar_url |
org_name | Organization name when the workspace is a member team inside an org. Omitted for standalone workspaces and for the org's own workspace |
invited_by | Display name of the inviter, as the invite email names them |
| HTTP status | When | Client behavior |
|---|---|---|
400 | token query parameter is missing | Fix the link — it must include ?token=… |
404 | No invite matches the token | Treat the link as invalid (truncated URL, revoked invite, or wrong token) |
5xx | Server error while loading invite info | The token may still be valid; retry the lookup |
Only 404 means the link does not work; other failures say nothing about whether the invite stands, so the workspace invite page offers try again for them.
Accept invite
POST /invites/accept
Requires authentication. Pass the invite token in the JSON body or token query parameter:
1{2 "token": "invite_token_from_email"3}Returns the new TeamMemberDTO.
Decline invite
POST /invites/decline
Pass the same token shape. Does not require the invitee to be logged in.
Roles
| Role | Typical permissions |
|---|---|
owner | Full workspace control, billing, delete workspace |
admin | Manage members, settings, and workspace resources |
member | Use workspace resources within plan limits |
Owners and admins can list and manage other members' private resources, for example after a member leaves. Pass include_private: true on cursor-list requests, or access an individual resource by ID. Both paths are audited. See REST overview: Workspace admin visibility.
Related
- REST overview: authentication and response format
- Organizations API: org CRUD, member workspaces, org admins
- Entitlements: per-workspace plan limits
- Billing · Subscription: workspace balance and plans
- SDK overview:
client.teamsnamespace (JavaScript)