API reference

People, invites and workspaces

The routes behind Team and Workspaces and environments. Schemas, every field and every status are in the OpenAPI document (/v1/docs-json, browsable at /v1/docs under Workspaces and Team — people); this page is the map.

Retrying writes

The writes that create or change something take Idempotency-Key: every /v1/team/* write, POST /v1/workspaces, DELETE /v1/workspaces/{id}, POST /v1/me/workspaces/{id}/leave and the workspace settings. Four routes take none, because a retry is already safe:

  • POST /v1/auth/switch-workspace: switching twice to the same workspace lands in the same place.
  • POST /v1/auth/invites/accept and POST /v1/me/invites/{id}/accept: an invite is used once, so a retried accept answers already_a_member (409) and changes nothing.
  • POST /v1/auth/signup: a second signup with the same username or e-mail never creates a second account; when the answer was lost, sign in instead.

Idempotency keys are kept per workspace, so POST /v1/workspaces from a session without a workspace (after signing up from an invite, or after leaving the last one) has none: before retrying a create whose answer you did not get, read GET /v1/me/workspaces. A replayed create answers idempotency_secret_not_replayable (409, its answer carries tokens) with the workspace in details.resource_id.

People and invites (owners)

RouteWhoWhat
GET /v1/team/membersteam.readPeople of the workspace, never synthetic accounts; cursor paginated ({data, pagination}).
PATCH /v1/team/members/{id}team.writeChange a role (role). Demoting an owner takes synthetic_accounts dispositions.
GET /v1/team/members/{id}/removal-previewteam.writeWhat removing or demoting the person affects: synthetic accounts mailed to them, what they created, whom they invited, their key count.
DELETE /v1/team/members/{id}team.writeRemove, with synthetic_accounts (move_to_me | rotate | archive; accounts whose password they only know default to rotate) and created_items (disable | keep).
POST /v1/team/invitesteam.writeInvite email with role. 201 with the invite, never its link.
GET /v1/team/invitesteam.readPending, expired and bounced invites (30 days), newest first; cursor paginated (pagination), plus inviting_paused_until.
POST /v1/team/invites/{id}/resendteam.writeMail a new link; the old one stops working.
DELETE /v1/team/invites/{id}team.writeCancel a pending invite. 204.

A person is id wm_…, an invite id wi_…. Inviting or promoting someone to Owner, and demoting or removing an owner, need a second factor from the last 5 minutes; inviting or promoting to Full access, the last 15 (step_up_required).

Accepting

RouteWhoWhat
POST /v1/auth/invites/inspectPublicWhat a link is for: workspace, role, inviter_email, email_masked, expires_at. Uses nothing up.
POST /v1/auth/invites/acceptSigned inJoin with the link's token. Answers workspace, role and a session switched into it.
GET /v1/me/invitesSigned inPending invites to any verified address of your account, newest first; cursor paginated (pagination).
POST /v1/me/invites/{id}/acceptSigned inAccept one of them by id, under the same rules as the link.
POST /v1/auth/signupPublicTakes an optional invite_token: the account gets no sandbox, and accepts after verifying its address.

Workspaces

RouteWhoWhat
GET /v1/me/workspacesSigned inYour workspaces with environment_label, role, is_current, member_count, owner_emails, and your limits; cursor paginated (pagination), one page at the default limit.
POST /v1/workspacesSigned-in personCreate one: name, environment_label, environment_color, company, copy_settings_from. 201, switched into it.
POST /v1/auth/switch-workspaceSigned-in personTokens for workspace_id.
POST /v1/me/workspaces/{id}/leaveMemberLeave; synthetic_accounts takes archive for accounts mailed to you; accounts whose password you know are reset unless you send archive.
GET /v1/workspaces/{id}/deletion-previewOwnerCounts of what goes, and step_up_required.
DELETE /v1/workspaces/{id}OwnerDelete with confirm_name. 202 with purge_after.

GET /v1/auth/me (a session) and GET /v1/whoami (a session or a key) name the workspace a credential acts in, with its label (workspace, workspace), and say whether it is a person (is_human, is_human). auth/me also carries features (multi_workspace); its workspace is null for a session without a workspace.

X-DM-Workspace

An optional request header on every authenticated route: X-DM-Workspace: tnt_…, the workspace the request is meant for. When the session or key acts in another one the API answers workspace_mismatch (409) and does nothing, on reads as well as writes. An empty header counts as absent; any other value that is not the current workspace is a mismatch. An API key always acts in its own workspace, so for a key the header is a check, never a way to reach another workspace.

Error codes

StatuscodeMeaning
400synthetic_addressYou invited the sign-in address of one of this workspace’s synthetic accounts.
400disposition_requiredRemoving, demoting or leaving needs a choice for each synthetic account mailed to the person (listed in details).
400disposition_not_allowedmove_to_me was sent for a synthetic account whose mail does not go to the person (listed in details): use rotate or archive.
400confirm_name_mismatchconfirm_name is not the workspace’s name (case counts; surrounding spaces do not).
401account_revokedYou are no longer a member of the workspace this token acts in. A person keeps their other workspaces: refresh, then switch.
401session_revokedThe token was issued before your current membership began (you were removed and invited again). Refresh or sign in again.
401api_key_not_allowedThese routes take a signed-in session, never an API key.
403step_up_requiredProve a second factor first (details.max_age_minutes: 5 for owner changes and deletion, 15 for Full access), then refresh the token and retry.
403synthetic_account_not_allowedSynthetic accounts cannot create workspaces or accept invites.
403workspace_switch_not_allowedSynthetic accounts cannot switch workspaces.
403invite_email_mismatchThe invited address is not a verified address of your account. Add and verify it (/v1/me/emails), then accept.
403email_not_verifiedYour own e-mail must be verified to invite or create workspaces (POST /v1/workspaces and a bootstrap that makes one).
404workspace_not_foundNo such workspace, or you are not a member of it (the same answer for both).
404invite_invalidNot an invite link, or not a pending invite to one of your verified addresses.
409last_ownerThe change would leave the workspace without an owner.
409already_a_memberThe address or account is already a person in this workspace.
409invite_pendingThat address already has a pending invite (details.invite_id): resend it.
409invite_not_pendingThe invite was accepted or cancelled.
409invite_undeliverableWe can’t deliver mail to this address; nothing was sent.
409invite_pausedA recipient marked one of your invites as spam: inviting is paused for 24 hours (details.retry_after_s).
409invite_pending_limit25 pending invites in this workspace.
409member_limit_reached50 people in this workspace.
409membership_limit_reachedYou belong to 50 workspaces.
409workspace_limit_reachedYou created and still own 10 workspaces (POST /v1/workspaces and POST /v1/auth/bootstrap alike).
409workspace_create_in_progressAnother create of yours is running; retry after details.retry_after_s.
409cannot_remove_selfUse leave for yourself.
409member_changedThe person’s role changed while you were deciding; read it again.
409synthetic_mail_ownerSynthetic accounts are still mailed to you: archive them, or ask another owner to move their mail first.
409mail_owner_departedThis synthetic account’s mail goes to someone who is no longer an owner: move it first.
409workspace_mismatchX-DM-Workspace names another workspace than the one this credential acts in (details.current_workspace_id). Nothing was done.
409no_active_workspaceThe session has no workspace: switch or create one. Only account and workspace routes work without one.
410invite_expiredThe invite expired. Ask an owner for a new one.
410invite_usedThe invite was already accepted.
410invite_revokedThe invite was cancelled or replaced by a resend.
429invite_rate_limitedAn invite limit was reached; retry after Retry-After.
429workspace_create_rate_limited5 workspaces created for you in 24 hours by any route (deleted ones count); retry after details.retry_after_s.
502auth_unavailableOur sign-in service failed mid-way; nothing changed. Retry.
503invite_unavailableInvite mail is paused platform-wide; try later.

In the audit trail

Invites, acceptances, role changes, removals, leaving, creating, deleting and switching are audit actions (workspace_invite.*, workspace_member.*, tenant.create, tenant.delete, auth.switch_workspace); see the audit catalogue.