Skip to content

Users ​

Generated from DialerDigital/core commit 0fc18008b3c8 — spec openapi/dialer-v1.yaml v0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.

Español

Dashboard user accounts (email + password; disabled, never deleted).

Endpoints ​

MethodPathSummary
GET/v1/usersList dashboard users and open invitations
POST/v1/usersInvite a person to the dashboard
DELETE/v1/users/{id}Disable a user (never deletes)
POST/v1/users/{id}/enableRe-enable a disabled user
POST/v1/users/{id}/mfa/reset-failuresLift a person's second-factor brake
POST/v1/users/{id}/mfa/removeRemove a person's second factor (lost authenticator and recovery codes)

List dashboard users and open invitations ​

http
GET /v1/users

Emails, names, status and login timestamps — password hashes are never exposed. invitations lists the OPEN invitations (neither accepted nor superseded) sent by POST /v1/users, newest first: pending until they expire, expired after. An invitation to an address that is already a user of another tenant is listed, and expires, exactly like one nobody opened — this list says nothing about who else the address belongs to. An accepted invitation leaves this list and its person appears in users.

Responses

200 ​

The tenant's dashboard users and its open invitations.

FieldTypeRequiredDescription
usersarray of Useryes
users[].idstring (uuid)no
users[].emailstring (email)noGlobal login identifier, stored lowercase.
users[].namestringno
users[].statusactive · disabledno
users[].rolemember · account_adminnoIntra-account privilege. Only an account_admin may create or modify the empresa-wide rows every sibling sede inherits (scope: "empresa" on carriers and SMS providers); a member gets 403. Read-only here: promotion is an operator gesture, never a request.
users[].password_updated_atstring (date-time)no
users[].last_login_atstring (date-time) | nullno
users[].mfa_enabledbooleannoWhether the person has an ACTIVE second factor (an enrolment still pending confirmation is false). Emitted ONLY when the caller is an account_admin dashboard login — the one who can act on it with POST /v1/users/{id}/mfa/remove; for a member, a dd_ key, or when the caller's role cannot be determined the field is ABSENT (not false). Wherever a User is rendered, the same rule holds: GET /v1/users, GET /v1/me, the user returned by DELETE /v1/users/{id} and POST /v1/users/{id}/enable, and the user in the session responses (POST /v1/auth/login, POST /v1/auth/sso/exchange, and the two MFA completion routes) — there, for the account_admin who just signed in.
users[].created_atstring (date-time)no
users[].updated_atstring (date-time)no
invitationsarray of UserInvitationyes
invitations[].idstring (uuid)no
invitations[].emailstring (email)noStored lowercase.
invitations[].namestringno
invitations[].statuspending · expirednopending until expires_at, expired after. Resend by inviting again.
invitations[].expires_atstring (date-time)no
invitations[].created_atstring (date-time)no
invitations[].invited_byobjectnoWho made the invitation (core#2449), immutable. kind is user (a dashboard login; user_id is that person), api_key (a dd_ key; user_id null) or operator. kind is null for an invitation that predates the record.
invitations[].invited_by.kinduser · api_key · operator · nullyes
invitations[].invited_by.user_idstring,null (uuid)yes
truncatedbooleanyestrue when there are more rows than the 500 users or 500 open invitations (it is true if either list was cut): the response is a prefix in the documented order, not the whole set.

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Invite a person to the dashboard ​

http
POST /v1/users

Invites — it does not create the user (ADR #182, a breaking change of this operation: it used to take a password and answer 201 with the created user). It records an invitation and e-mails the address a single-use link (valid 72 h) to /accept-invite; the person is created when they redeem it with the password THEY choose, through POST /v1/auth/password-reset/confirm. An address is therefore only taken by someone who proved they read its mail.

The answer is 202 with the SAME body — byte for byte — whether the address is free or already a user of ANOTHER tenant. In the second case the invitation is recorded and listed all the same, and the address receives a notice that it already has an account (with no link): the inviting tenant learns nothing about other tenants. Only an address that is already a user of THIS tenant, active or disabled, is a 409 — that row is in this tenant's own GET /v1/users anyway.

Role rules (core#2334, core#2449): once the tenant has an active account_admin, this takes an account_admin login (403 role_forbidden for a member AND for a dd_ key; per-tenant knob user_management_admin_only, default on; founder knob api_key_manages_people, default off, gives the dd_ key its old reach back). A tenant with no active account_admin keeps the old posture, dd_ key included (it invites the first person). The invitation records who made it (invited_by).

Inviting the same address again supersedes the previous link (that is how an invitation is resent). Two brakes bound the mail this route can send: per tenant (a visible 429) and per address across every tenant (invisible: over it the invitation is still recorded and the answer is the same 202, but no mail leaves — a 429 there would tell one tenant that another just invited that address).

Bootstrap runs with the dd_ machine key (it invites the FIRST user); after that any ddu_ session can manage users (all users are console admins day-1). tenant_id is never read from the body. A password in the body is a 400: the invited person chooses it.

Request body (JSON, required)

FieldTypeRequiredDescription
emailstring (email)yes
namestringno
localees · ennoLanguage the inviting admin has the console in; the invitation (and the "this address already has an account" notice) goes out in it. Absent or anything else: the cell's language (DEFAULT_LOCALE). Never an error.
json
{
  "email": "[email protected]",
  "name": "Ops Admin",
  "locale": "es"
}

Responses

202 ​

Invitation recorded. Identical for every recorded outcome (see the description).

FieldTypeRequiredDescription
statusinvitedyes
messagestringyes
json
{
  "status": "invited",
  "message": "invitation recorded"
}

400 — BadRequest ​

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — UserManagementForbidden ​

A user-management write was refused (core#2334, ADR #46 addendum). code admin_target_forbidden: a member login tried to disable or re-enable an account_admin — always, whatever the tenant's settings. code role_forbidden: the tenant has an active account_admin and the caller is a member login or (core#2449) a dd_ key (the route takes an account_admin login; founder knob api_key_manages_people, default off, restores the key); the per-tenant knob user_management_admin_only (default on) turns this one off, and a tenant with no active account_admin is not asked for one. code forbidden: the tenant is suspended or the token is a widget token.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "managing users takes a dashboard login with the account_admin role"
  }
}

409 ​

The address is already a user of THIS tenant (active or disabled), or a concurrent invitation to it from this same tenant won the race.

json
{
  "error": {
    "code": "conflict",
    "message": "that address is already a user of this tenant"
  }
}

429 ​

This tenant's invitation brake refused the request (fixed window; retryable later). code rate_limited. Keyed per tenant only — it says nothing about the address. Retry-After is the time left in the fixed window.

json
{
  "error": {
    "code": "rate_limited",
    "message": "user invitation rate limit exceeded; retry later"
  }
}

Disable a user (never deletes) ​

http
DELETE /v1/users/{id}

Sets status: disabled AND revokes every live session the user holds (their bearer dies at the next request). Idempotent. Users are audit evidence — rows never delete; re-enable via POST /v1/users/{id}/enable.

Refused with 409 when it would lock the account out (core#2328): cannot_disable_self when the caller is the target (a dashboard login; a dd_ key is no person), and last_account_admin when the target is the only active account_admin of the tenant. An already-disabled user stays idempotent.

Role rules (core#2334): once the tenant has an active account_admin, this takes a dd_ key or an account_admin login (403 role_forbidden for a member AND, since core#2449, for a dd_ key; per-tenant knob user_management_admin_only, default on; founder knob api_key_manages_people, default off). A tenant with no active account_admin keeps the old posture, dd_ key included. A member can never disable an account_admin (403 admin_target_forbidden).

Keys the person minted (core#2359): the response lists every dd_ key their session minted in issued_api_keys (live ones have revoked_at: null), so the administrator can rotate what a leaver left behind. Per-tenant knob user_disable_revokes_api_keys (default off): when on, disabling the person first revokes their live keys.

Seat and SIP credential (core#2405): per-tenant knob user_disable_retires_seat (default on). The agent linked to the person leaves the agent FSM (armed while a call is in flight), its agentcred entry and usrloc binding are evicted and its widget login is switched off; seat_retirement reports each step (null with the knob off). POST /v1/auth/agent-login refuses an agent whose linked person is not active.

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The disabled user and the API keys that person minted.

FieldTypeRequiredDescription
userobject (User)yesA dashboard user — the password hash is never exposed.
user.idstring (uuid)no
user.emailstring (email)noGlobal login identifier, stored lowercase.
user.namestringno
user.statusactive · disabledno
user.rolemember · account_adminnoIntra-account privilege. Only an account_admin may create or modify the empresa-wide rows every sibling sede inherits (scope: "empresa" on carriers and SMS providers); a member gets 403. Read-only here: promotion is an operator gesture, never a request.
user.password_updated_atstring (date-time)no
user.last_login_atstring (date-time) | nullno
user.mfa_enabledbooleannoWhether the person has an ACTIVE second factor (an enrolment still pending confirmation is false). Emitted ONLY when the caller is an account_admin dashboard login — the one who can act on it with POST /v1/users/{id}/mfa/remove; for a member, a dd_ key, or when the caller's role cannot be determined the field is ABSENT (not false). Wherever a User is rendered, the same rule holds: GET /v1/users, GET /v1/me, the user returned by DELETE /v1/users/{id} and POST /v1/users/{id}/enable, and the user in the session responses (POST /v1/auth/login, POST /v1/auth/sso/exchange, and the two MFA completion routes) — there, for the account_admin who just signed in.
user.created_atstring (date-time)no
user.updated_atstring (date-time)no
issued_api_keysarray of ApiKeyyes
issued_api_keys[].idstring (uuid)no
issued_api_keys[].labelstringno
issued_api_keys[].revoked_atstring (date-time) | nullno
issued_api_keys[].last_used_atstring (date-time) | nullnoRotation-hygiene clock: the last time this key authenticated SUCCESSFULLY. Written at most once a minute per key, so a fresh reading may lag by up to that much. null means it has not authenticated successfully since the column acquired a writer — not that the key was never presented.
issued_api_keys[].allowed_cidrsarray of stringnoNetwork allowlist (core#2450). [] = unrestricted. Outside it a request is served and counted, or refused with 401, per the tenant knob api_key_network_enforced (default off).
issued_api_keys[].stalebooleannotrue for a live key unused for 90 days (a key that never authenticated counts from its creation). Advice for the console to rotate or revoke; nothing expires on its own.
issued_api_keys[].created_by_user_idstring (uuid) | nullnoThe dashboard user whose session minted the key (core#2359). null when another dd_ key, the operator or the seeds did, and on every key that predates the field.
issued_api_keys[].created_atstring (date-time)no
seat_retirementobject,nullnoWhat happened to the linked agent's seat; null when user_disable_retires_seat is off.
seat_retirement.seatretired · armed · none · failedno
seat_retirement.sip_teardownok · skipped · failedno
seat_retirement.widget_logindisabled · none · failedno

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — UserManagementForbidden ​

A user-management write was refused (core#2334, ADR #46 addendum). code admin_target_forbidden: a member login tried to disable or re-enable an account_admin — always, whatever the tenant's settings. code role_forbidden: the tenant has an active account_admin and the caller is a member login or (core#2449) a dd_ key (the route takes an account_admin login; founder knob api_key_manages_people, default off, restores the key); the per-tenant knob user_management_admin_only (default on) turns this one off, and a tenant with no active account_admin is not asked for one. code forbidden: the tenant is suspended or the token is a widget token.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "managing users takes a dashboard login with the account_admin role"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 ​

Refused: cannot_disable_self (the caller is the target) or last_account_admin (the target is the only active account_admin).

json
{
  "error": {
    "code": "last_account_admin",
    "message": "the last active account_admin cannot be disabled; promote another one first"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Re-enable a disabled user ​

http
POST /v1/users/{id}/enable

Sessions revoked at disable STAY revoked — the user logs in fresh.

Role rules (core#2334): once the tenant has an active account_admin, this takes a dd_ key or an account_admin login (403 role_forbidden for a member AND, since core#2449, for a dd_ key; per-tenant knob user_management_admin_only, default on; founder knob api_key_manages_people, default off). A tenant with no active account_admin keeps the old posture, dd_ key included. A member can never re-enable an account_admin (403 admin_target_forbidden).

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The re-enabled user.

FieldTypeRequiredDescription
userobject (User)yesA dashboard user — the password hash is never exposed.
user.idstring (uuid)no
user.emailstring (email)noGlobal login identifier, stored lowercase.
user.namestringno
user.statusactive · disabledno
user.rolemember · account_adminnoIntra-account privilege. Only an account_admin may create or modify the empresa-wide rows every sibling sede inherits (scope: "empresa" on carriers and SMS providers); a member gets 403. Read-only here: promotion is an operator gesture, never a request.
user.password_updated_atstring (date-time)no
user.last_login_atstring (date-time) | nullno
user.mfa_enabledbooleannoWhether the person has an ACTIVE second factor (an enrolment still pending confirmation is false). Emitted ONLY when the caller is an account_admin dashboard login — the one who can act on it with POST /v1/users/{id}/mfa/remove; for a member, a dd_ key, or when the caller's role cannot be determined the field is ABSENT (not false). Wherever a User is rendered, the same rule holds: GET /v1/users, GET /v1/me, the user returned by DELETE /v1/users/{id} and POST /v1/users/{id}/enable, and the user in the session responses (POST /v1/auth/login, POST /v1/auth/sso/exchange, and the two MFA completion routes) — there, for the account_admin who just signed in.
user.created_atstring (date-time)no
user.updated_atstring (date-time)no

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — UserManagementForbidden ​

A user-management write was refused (core#2334, ADR #46 addendum). code admin_target_forbidden: a member login tried to disable or re-enable an account_admin — always, whatever the tenant's settings. code role_forbidden: the tenant has an active account_admin and the caller is a member login or (core#2449) a dd_ key (the route takes an account_admin login; founder knob api_key_manages_people, default off, restores the key); the per-tenant knob user_management_admin_only (default on) turns this one off, and a tenant with no active account_admin is not asked for one. code forbidden: the tenant is suspended or the token is a widget token.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "managing users takes a dashboard login with the account_admin role"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Lift a person's second-factor brake ​

http
POST /v1/users/{id}/mfa/reset-failures

Clears the cumulative brake of the second factor (core#1572) for one person of the tenant: writes a failures_reset event authored by the calling admin, and the refused codes before it stop counting. Nothing is deleted from the audit trail. account_admin dashboard login only (see 403).

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

204 ​

The failure count was reset.

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Remove a person's second factor (lost authenticator and recovery codes) ​

http
POST /v1/users/{id}/mfa/remove

The recovery path for a person who lost BOTH the authenticator and the recovery codes (core#1580): an account_admin of the SAME tenant removes that person's active second factor. In ONE transaction — if any step fails the factor is left intact: the factor and its unused recovery codes stop being valid, EVERY live ddu_ session of the person is revoked, and a factor_removed_by_admin event (author = the calling admin, target = the person, the written reason) is appended to the second-factor audit trail. Nothing is deleted.

It opens no door without a second factor: when the tenant requires it (mfa_required_enforced) the person's next login lands in the forced enrollment; otherwise the account simply has no second factor until they enrol again.

account_admin dashboard login only (see 403): never the person themself (they have no factor to prove) and never a dd_ key. An admin cannot use it on THEMSELVES (409): their own path is POST /v1/me/mfa/disable, which re-authenticates. A disabled person is 404 — re-enable them first.

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Request body (JSON, required)

FieldTypeRequiredDescription
reasonstringyesWhy the factor is being removed — free text, kept in the audit trail. Leading and trailing whitespace is trimmed before counting.
json
{
  "reason": "Lost phone and printed codes; identity confirmed on a video call."
}

Responses

204 ​

The factor was removed, the person's sessions revoked and the event written.

400 — BadRequest ​

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 — Forbidden ​

The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.

json
{
  "error": {
    "code": "forbidden",
    "message": "tenant is suspended"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 ​

Nothing to remove or not this route: the person has no ACTIVE second factor, or the caller is that same person (use POST /v1/me/mfa/disable).

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.