Skip to content

API keys ​

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

Per-tenant API keys (sha256 at rest; plaintext shown once).

Endpoints ​

MethodPathSummary
GET/v1/api_keysList API keys
POST/v1/api_keysMint an API key
PATCH/v1/api_keys/{id}Set the network allowlist of an API key
DELETE/v1/api_keys/{id}Revoke an API key

List API keys ​

http
GET /v1/api_keys

Ids, labels and revocation timestamps only — hashes are never exposed.

Responses

200 ​

The tenant's keys.

FieldTypeRequiredDescription
api_keysarray of ApiKeyyes
api_keys[].idstring (uuid)no
api_keys[].labelstringno
api_keys[].revoked_atstring (date-time) | nullno
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.
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).
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.
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.
api_keys[].created_atstring (date-time)no
json
{
  "api_keys": [
    {
      "id": "7a3f0f7e-52a1-4f3d-9f9d-27e5a1b9c001",
      "label": "onboarding",
      "revoked_at": null,
      "created_at": "2026-06-01T12:00:00Z"
    }
  ]
}

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"
  }
}

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"
  }
}

Mint an API key ​

http
POST /v1/api_keys

The plaintext token (dd_...) is returned ONCE and only its sha256 is stored. Rotate by minting a new key and revoking the old one.

Role rules (core#2359, core#2449): minting takes an account_admin login (403 role_forbidden for a member AND, once the tenant has an active account_admin, for a dd_ key; per-tenant knob api_key_admin_only, default on; founder knob api_key_manages_people, default off). A tenant with no active account_admin keeps the dd_ key minting. A key minted from a dashboard login records that person in created_by_user_id; every mint is written to the key audit trail.

allowed_cidrs (core#2450) is the key's network allowlist; omitted or [] is no restriction. What a request from outside the list does is the tenant knob api_key_network_enforced: off (the default) serves and counts it, on answers 401.

Limits (core#2503): a tenant holds at most api_keys_max_live LIVE keys (default 50; 409 api_key_limit over it, revoking a key frees a slot), and minting is rate-limited per tenant (429 rate_limited with Retry-After). The request quota is per key, so a tenant's keys together are also bounded by api_tenant_requests_per_minute (default 5x one key's).

Request body (JSON)

FieldTypeRequiredDescription
labelstringnoFree-form label (default "").
allowed_cidrsarray of stringnoCIDRs (or bare addresses) the key may be used from. A list with an entry that does not parse is rejected whole (400); a value that is not a list of strings is 422.
json
{
  "label": "postman-smoke-1751600000"
}

Responses

201 ​

Key minted; token is the only time the plaintext is visible.

FieldTypeRequiredDescription
api_keyobject (ApiKey)yesKey metadata — the token hash is never exposed.
api_key.idstring (uuid)no
api_key.labelstringno
api_key.revoked_atstring (date-time) | nullno
api_key.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.
api_key.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).
api_key.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.
api_key.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.
api_key.created_atstring (date-time)no
tokenstringyesPlaintext bearer token, shown exactly once.
json
{
  "api_key": {
    "id": "9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
    "label": "postman-smoke-1751600000",
    "revoked_at": null,
    "created_at": "2026-07-05T10:00:00Z"
  },
  "token": "dd_example_plaintext_shown_once"
}

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 — ApiKeyAdminForbidden ​

Minting or revoking an API key was refused (core#2359, ADR #46 addendum). code role_forbidden: the caller is a dashboard login without the account_admin role, or a dd_ key in a tenant that already has an active account_admin (core#2449; founder knob api_key_manages_people, default off); the per-tenant knob api_key_admin_only (default on) turns this off.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "minting or revoking an API key takes a dashboard login with the account_admin role; a dd_ key may do it only while the tenant has no account_admin"
  }
}

409 — ApiKeyLimit ​

The tenant already holds the maximum number of LIVE API keys (core#2503; per-tenant knob api_keys_max_live, default 50). code api_key_limit. Nothing was minted; revoke a key (DELETE /v1/api_keys/{id}) and retry.

json
{
  "error": {
    "code": "api_key_limit",
    "message": "this tenant already holds the maximum number of live API keys; revoke one first"
  }
}

422 — Unprocessable ​

Shape is fine, semantics are not (broken domain rule).

json
{
  "error": {
    "code": "unprocessable",
    "message": "scheduled_at is closer than the minimum notice for a callback (10 minutes unless the tenant's callback_min_notice_seconds says otherwise)"
  }
}

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"
  }
}

Set the network allowlist of an API key ​

http
PATCH /v1/api_keys/{id}

Replaces allowed_cidrs of a LIVE key (core#2450); [] lifts the restriction. Takes the same roles as minting (403 role_forbidden for a member while the knob api_key_admin_only is on). The change is written to the key audit trail as network_changed and binds the next request: the key is forgotten by the authentication cache. A revoked or unknown key is 404.

Parameters

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

Request body (JSON, required)

FieldTypeRequiredDescription
allowed_cidrsarray of stringyes
json
{
  "allowed_cidrs": [
    "203.0.113.0/24",
    "2001:db8::/32"
  ]
}

Responses

200 ​

The key with its new list.

FieldTypeRequiredDescription
api_keyobject (ApiKey)yesKey metadata — the token hash is never exposed.
api_key.idstring (uuid)no
api_key.labelstringno
api_key.revoked_atstring (date-time) | nullno
api_key.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.
api_key.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).
api_key.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.
api_key.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.
api_key.created_atstring (date-time)no

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 — ApiKeyAdminForbidden ​

Minting or revoking an API key was refused (core#2359, ADR #46 addendum). code role_forbidden: the caller is a dashboard login without the account_admin role, or a dd_ key in a tenant that already has an active account_admin (core#2449; founder knob api_key_manages_people, default off); the per-tenant knob api_key_admin_only (default on) turns this off.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "minting or revoking an API key takes a dashboard login with the account_admin role; a dd_ key may do it only while the tenant has no account_admin"
  }
}

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"
  }
}

422 — Unprocessable ​

Shape is fine, semantics are not (broken domain rule).

json
{
  "error": {
    "code": "unprocessable",
    "message": "scheduled_at is closer than the minimum notice for a callback (10 minutes unless the tenant's callback_min_notice_seconds says otherwise)"
  }
}

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"
  }
}

Revoke an API key ​

http
DELETE /v1/api_keys/{id}

Sets revoked_at; keys are never deleted. Idempotent revocation semantics.

Role rules (core#2359, core#2449): revoking takes an account_admin login (403 role_forbidden for a member AND, once the tenant has an active account_admin, for a dd_ key; per-tenant knob api_key_admin_only, default on; founder knob api_key_manages_people, default off). A tenant with no active account_admin keeps the dd_ key revoking. Every revocation is written to the key audit trail.

Parameters

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

Responses

200 ​

The revoked key.

FieldTypeRequiredDescription
api_keyobject (ApiKey)yesKey metadata — the token hash is never exposed.
api_key.idstring (uuid)no
api_key.labelstringno
api_key.revoked_atstring (date-time) | nullno
api_key.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.
api_key.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).
api_key.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.
api_key.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.
api_key.created_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 — ApiKeyAdminForbidden ​

Minting or revoking an API key was refused (core#2359, ADR #46 addendum). code role_forbidden: the caller is a dashboard login without the account_admin role, or a dd_ key in a tenant that already has an active account_admin (core#2449; founder knob api_key_manages_people, default off); the per-tenant knob api_key_admin_only (default on) turns this off.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "minting or revoking an API key takes a dashboard login with the account_admin role; a dd_ key may do it only while the tenant has no account_admin"
  }
}

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"
  }
}

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