Appearance
API keys
Generated from
DialerDigital/corecommit0fc18008b3c8— specopenapi/dialer-v1.yamlv0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.
Per-tenant API keys (sha256 at rest; plaintext shown once).
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/api_keys | List API keys |
POST | /v1/api_keys | Mint 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_keysIds, labels and revocation timestamps only — hashes are never exposed.
Responses
200
The tenant's keys.
| Field | Type | Required | Description |
|---|---|---|---|
api_keys | array of ApiKey | yes | |
api_keys[].id | string (uuid) | no | |
api_keys[].label | string | no | |
api_keys[].revoked_at | string (date-time) | null | no | |
api_keys[].last_used_at | string (date-time) | null | no | Rotation-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_cidrs | array of string | no | Network 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[].stale | boolean | no | true 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_id | string (uuid) | null | no | The 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_at | string (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_keysThe 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)
| Field | Type | Required | Description |
|---|---|---|---|
label | string | no | Free-form label (default ""). |
allowed_cidrs | array of string | no | CIDRs (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.
| Field | Type | Required | Description |
|---|---|---|---|
api_key | object (ApiKey) | yes | Key metadata — the token hash is never exposed. |
api_key.id | string (uuid) | no | |
api_key.label | string | no | |
api_key.revoked_at | string (date-time) | null | no | |
api_key.last_used_at | string (date-time) | null | no | Rotation-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_cidrs | array of string | no | Network 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.stale | boolean | no | true 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_id | string (uuid) | null | no | The 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_at | string (date-time) | no | |
token | string | yes | Plaintext 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
allowed_cidrs | array of string | yes |
json
{
"allowed_cidrs": [
"203.0.113.0/24",
"2001:db8::/32"
]
}Responses
200
The key with its new list.
| Field | Type | Required | Description |
|---|---|---|---|
api_key | object (ApiKey) | yes | Key metadata — the token hash is never exposed. |
api_key.id | string (uuid) | no | |
api_key.label | string | no | |
api_key.revoked_at | string (date-time) | null | no | |
api_key.last_used_at | string (date-time) | null | no | Rotation-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_cidrs | array of string | no | Network 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.stale | boolean | no | true 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_id | string (uuid) | null | no | The 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_at | string (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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The revoked key.
| Field | Type | Required | Description |
|---|---|---|---|
api_key | object (ApiKey) | yes | Key metadata — the token hash is never exposed. |
api_key.id | string (uuid) | no | |
api_key.label | string | no | |
api_key.revoked_at | string (date-time) | null | no | |
api_key.last_used_at | string (date-time) | null | no | Rotation-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_cidrs | array of string | no | Network 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.stale | boolean | no | true 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_id | string (uuid) | null | no | The 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_at | string (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"
}
}