Skip to content

DIDs (caller IDs) ​

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

Español

DID registry, CDR-computed health, launch checklist and carrier tickets.

Endpoints ​

MethodPathSummary
GET/v1/port-requestsPort-in requests of the tenant
POST/v1/port-requestsPort numbers in to the platform (port-in)
GET/v1/port-requests/{id}Fetch one port-in request
GET/v1/didsDID registry
POST/v1/didsRegister a tenant-owned number
GET/v1/dids/{id}Fetch one DID
PATCH/v1/dids/{id}Update metadata/status (e164 immutable)
DELETE/v1/dids/{id}Retire a DID (rows never delete)
GET/v1/dids/{id}/healthCDR-computed DID health
GET/v1/dids/{id}/launch-checklistLaunch checklist of a number
POST/v1/dids/{id}/launch-checklist/{step}/confirmationsConfirm (or un-confirm) a manual launch step
GET/v1/dids/{id}/carrier-ticketsCarrier tickets of a number
POST/v1/dids/{id}/carrier-ticketsOpen a carrier ticket for a number
POST/v1/dids/{id}/carrier-tickets/{ticket_id}/eventsAdd a note and/or move a carrier ticket's status

Port-in requests of the tenant ​

http
GET /v1/port-requests

Newest first, at most 50. The documents are never returned, only their hash and type.

Parameters

NameInTypeRequiredDescription
statusquerypending · scheduled · rejected · completedno

Responses

200 ​

The requests.

FieldTypeRequiredDescription
port_requestsarray of PortRequestyes
port_requests[].idstring (uuid)yes
port_requests[].numbersarray of stringyes
port_requests[].account_holderstringyes
port_requests[].statuspending · scheduled · rejected · completedyes
port_requests[].reject_reasonstring,nullno
port_requests[].foc_datestring,null (date)noFirm date set by the carrier.
port_requests[].carrierstringyes
port_requests[].loaobject (PortDocumentRef)yes
port_requests[].loa.sha256stringyes
port_requests[].loa.content_typestringyes
port_requests[].invoiceobject (PortDocumentRef)yes
port_requests[].invoice.sha256stringyes
port_requests[].invoice.content_typestringyes
port_requests[].completed_atstring,null (date-time)no
port_requests[].created_atstring (date-time)yes
port_requests[].updated_atstring (date-time)yes

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

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (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"
  }
}

Port numbers in to the platform (port-in) ​

http
POST /v1/port-requests

Registers a port-in request and sends it to the house carrier. Documents travel as base64 in the JSON body (each at most 2 MiB; application/pdf, image/png or image/jpeg, checked against their magic bytes) and are stored ENCRYPTED, never returned. Fails closed before the carrier is called: inactive plan (402 plan_inactive), invalid input (400), a number already in the registry or in a live request (409 number_in_use). A test key is refused (403 test_environment). The carrier rejecting the submission is NOT an error: the request is stored with status: rejected and its reject_reason. 503 carrier_unavailable: the carrier did not answer; the request is kept as rejected (carrier_unavailable_at_submission) and its numbers are free to retry. 409 port_in_busy: another request of the tenant is being registered (retry shortly; nothing stored or sent). 429 port_request_limit: the tenant already has port_request_max_live (default 20) live requests; nothing stored or sent. 503 port_in_unavailable: this deployment has no port-in carrier configured; nothing was stored or sent.

Status follows the carrier (pending → scheduled with foc_date → completed, or rejected with reject_reason). On completed the numbers become origin: house DIDs of the tenant (they accrue the monthly rent) and the signed webhook port_request.ported is sent.

Role rules: a dd_ key or an account_admin login (403 role_forbidden otherwise).

Request body (JSON, required)

FieldTypeRequiredDescription
numbersarray of stringyes
account_holderstringyesName on the account at the current carrier.
loaobject (PortDocument)yes
loa.content_typeapplication/pdf · image/png · image/jpegyes
loa.content_base64stringyesBase64 of the file, at most 2 MiB decoded.
invoiceobject (PortDocument)yes
invoice.content_typeapplication/pdf · image/png · image/jpegyes
invoice.content_base64stringyesBase64 of the file, at most 2 MiB decoded.

Responses

201 ​

The stored request.

FieldTypeRequiredDescription
port_requestobject (PortRequest)yesA port-in request; also the data of the webhook port_request.ported.
port_request.idstring (uuid)yes
port_request.numbersarray of stringyes
port_request.account_holderstringyes
port_request.statuspending · scheduled · rejected · completedyes
port_request.reject_reasonstring,nullno
port_request.foc_datestring,null (date)noFirm date set by the carrier.
port_request.carrierstringyes
port_request.loaobject (PortDocumentRef)yes
port_request.loa.sha256stringyes
port_request.loa.content_typestringyes
port_request.invoiceobject (PortDocumentRef)yes
port_request.invoice.sha256stringyes
port_request.invoice.content_typestringyes
port_request.completed_atstring,null (date-time)no
port_request.created_atstring (date-time)yes
port_request.updated_atstring (date-time)yes

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

402 — PlanInactive ​

The tenant's plan was READ and is not effectively active, so the action is gated (code plan_inactive). On the SUPERVISION surface this status means the plan was actually read: a plan that could NOT be read passes instead of refusing. That is deliberately the opposite of the inbound gate's fail-CLOSED posture — a supervision leg goes to the tenant's own registrar and buys no carrier minutes, so the cost of a false refusal (a compliance officer blinded during a live call) exceeds the cost of a false admission.

Note which verbs can return it: the three EAVESDROP modes only. takeover is exempt and never returns plan_inactive, under the rule "gate what ADDS exposure, never what CORRECTS it" — listen/whisper/barge each open a new leg, while takeover takes the call away and kills the agent leg. A plan lapsing mid-shift must not strand a live call the supervisor is trying to end. The exemption is narrow, not a billing loophole: a dark tenant still cannot open NEW supervision, it only keeps the lever to end properly what is already running.

json
{
  "error": {
    "code": "plan_inactive",
    "message": "tenant plan is not active; traffic is gated"
  }
}

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 (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. No payment changes it.

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

409 — Conflict ​

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (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"
  }
}

503 ​

The carrier did not answer (carrier_unavailable) or this deployment has no port-in carrier configured (port_in_unavailable); nothing was stored or sent.

Fetch one port-in request ​

http
GET /v1/port-requests/{id}

Parameters

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

Responses

200 ​

The request with the carrier status.

FieldTypeRequiredDescription
port_requestobject (PortRequest)yesA port-in request; also the data of the webhook port_request.ported.
port_request.idstring (uuid)yes
port_request.numbersarray of stringyes
port_request.account_holderstringyes
port_request.statuspending · scheduled · rejected · completedyes
port_request.reject_reasonstring,nullno
port_request.foc_datestring,null (date)noFirm date set by the carrier.
port_request.carrierstringyes
port_request.loaobject (PortDocumentRef)yes
port_request.loa.sha256stringyes
port_request.loa.content_typestringyes
port_request.invoiceobject (PortDocumentRef)yes
port_request.invoice.sha256stringyes
port_request.invoice.content_typestringyes
port_request.completed_atstring,null (date-time)no
port_request.created_atstring (date-time)yes
port_request.updated_atstring (date-time)yes

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

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

DID registry ​

http
GET /v1/dids

Parameters

NameInTypeRequiredDescription
statusqueryactive · quarantine · retiredno

Responses

200 ​

Registry rows ordered by e164.

FieldTypeRequiredDescription
didsarray of DIDyes
dids[].idstring (uuid)no
dids[].tenant_idstring (uuid)no
dids[].e164stringnoImmutable after creation.
dids[].npastring | nullno
dids[].us_statestring | nullno
dids[].attestationA · B · C · unknownnoSTIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it.
dids[].statusactive · quarantine · retiredno
dids[].verified_atstring (date-time) | nullnoProof of ownership. null = self-declared, never proved; house DIDs are born verified.
dids[].verification_methodhouse · otp · admin | nullnoHow the ownership was proved; null while verified_at is null.
dids[].originhouse · customernohouse rows are admin-minted platform inventory; the tenant surface only creates customer.
dids[].labelsarray of stringno
dids[].notesstring | nullno
dids[].created_atstring (date-time)no
dids[].updated_atstring (date-time)no
truncatedbooleanyestrue when there are more rows than the 2 000 numbers the listing returns: the response is a prefix in the documented order, not the whole set.

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

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (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"
  }
}

Register a tenant-owned number ​

http
POST /v1/dids

e164 unique per tenant → 409 conflict on duplicates; npa is derived from +1 numbers when omitted. The tenant surface can only ever create origin: customer rows — house inventory is admin-minted (POST /v1/admin/tenants/{id}/dids).

Role rules: a dd_ key or an account_admin login; a member login is 403 role_forbidden (per-tenant knob telephony_config_admin_only, default on). The GET of this resource is not affected.

Request body (JSON, required)

FieldTypeRequiredDescription
e164stringyesE.164 (immutable after creation).
npastringno
us_statestringno
attestationA · B · C · unknownnoAccepted and IGNORED: the STIR/SHAKEN attestation is a claim only the platform admin records (POST /v1/admin/tenants/{id}/dids/{did_id}/verify); it never changes through this route, which answers as if the key were absent.
statusactive · quarantine · retiredno
labelsarray of stringno
notesstringno
originhouse · customernoAccepted and IGNORED FOR SAFETY: a tenant can never mint origin: house, so any value here is overridden with customer. The enum mirrors the response schema because a GET of a house DID returns house — sending that value back does not fail, it has no effect. Declared so that THIS key does not become a 400 now that unknown keys are. It does NOT make the whole rendered object round-trip: id, tenant_id, created_at and updated_at are not accepted and DO get the 400.
json
{
  "e164": "+13125550142",
  "us_state": "IL"
}

Responses

201 ​

Registered DID.

FieldTypeRequiredDescription
didobject (DID)yes
did.idstring (uuid)no
did.tenant_idstring (uuid)no
did.e164stringnoImmutable after creation.
did.npastring | nullno
did.us_statestring | nullno
did.attestationA · B · C · unknownnoSTIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it.
did.statusactive · quarantine · retiredno
did.verified_atstring (date-time) | nullnoProof of ownership. null = self-declared, never proved; house DIDs are born verified.
did.verification_methodhouse · otp · admin | nullnoHow the ownership was proved; null while verified_at is null.
did.originhouse · customernohouse rows are admin-minted platform inventory; the tenant surface only creates customer.
did.labelsarray of stringno
did.notesstring | nullno
did.created_atstring (date-time)no
did.updated_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"
  }
}

409 — Conflict ​

Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.

json
{
  "error": {
    "code": "conflict",
    "message": "invalid state transition"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (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"
  }
}

Fetch one DID ​

http
GET /v1/dids/{id}

Parameters

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

Responses

200 ​

The DID.

FieldTypeRequiredDescription
didobject (DID)yes
did.idstring (uuid)no
did.tenant_idstring (uuid)no
did.e164stringnoImmutable after creation.
did.npastring | nullno
did.us_statestring | nullno
did.attestationA · B · C · unknownnoSTIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it.
did.statusactive · quarantine · retiredno
did.verified_atstring (date-time) | nullnoProof of ownership. null = self-declared, never proved; house DIDs are born verified.
did.verification_methodhouse · otp · admin | nullnoHow the ownership was proved; null while verified_at is null.
did.originhouse · customernohouse rows are admin-minted platform inventory; the tenant surface only creates customer.
did.labelsarray of stringno
did.notesstring | nullno
did.created_atstring (date-time)no
did.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"
  }
}

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

Update metadata/status (e164 immutable) ​

http
PATCH /v1/dids/{id}

A different number is a different DID; CDR history is never re-attributed.

Role rules: a dd_ key or an account_admin login; a member login is 403 role_forbidden (per-tenant knob telephony_config_admin_only, default on). The GET of this resource is not affected.

Parameters

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

Request body (JSON, required)

FieldTypeRequiredDescription
npastringno
us_statestringno
attestationA · B · C · unknownnoAccepted and IGNORED: the STIR/SHAKEN attestation is a claim only the platform admin records (POST /v1/admin/tenants/{id}/dids/{did_id}/verify); it never changes through this route, which answers as if the key were absent.
statusactive · quarantine · retiredno
labelsarray of stringno
notesstringno
originhouse · customernoAccepted and IGNORED FOR SAFETY, same as on POST /v1/dids: origin is immutable and never cast, so any value here has no effect. Sending back the value a GET returned does not fail; the rest of the rendered object still does (id, tenant_id, e164, created_at, updated_at are a 400).
json
{
  "attestation": "A",
  "labels": [
    "postman"
  ],
  "notes": "postman smoke"
}

Responses

200 ​

Updated DID.

FieldTypeRequiredDescription
didobject (DID)yes
did.idstring (uuid)no
did.tenant_idstring (uuid)no
did.e164stringnoImmutable after creation.
did.npastring | nullno
did.us_statestring | nullno
did.attestationA · B · C · unknownnoSTIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it.
did.statusactive · quarantine · retiredno
did.verified_atstring (date-time) | nullnoProof of ownership. null = self-declared, never proved; house DIDs are born verified.
did.verification_methodhouse · otp · admin | nullnoHow the ownership was proved; null while verified_at is null.
did.originhouse · customernohouse rows are admin-minted platform inventory; the tenant surface only creates customer.
did.labelsarray of stringno
did.notesstring | nullno
did.created_atstring (date-time)no
did.updated_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"
  }
}

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

Retire a DID (rows never delete) ​

http
DELETE /v1/dids/{id}

Sets status retired, idempotent. Rows never leave the registry (DELETE is revoked in Postgres): CDR from_number provenance must keep resolving for the Defense Packet.

Role rules: a dd_ key or an account_admin login; a member login is 403 role_forbidden (per-tenant knob telephony_config_admin_only, default on). The GET of this resource is not affected.

Parameters

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

Responses

200 ​

The retired DID.

FieldTypeRequiredDescription
didobject (DID)yes
did.idstring (uuid)no
did.tenant_idstring (uuid)no
did.e164stringnoImmutable after creation.
did.npastring | nullno
did.us_statestring | nullno
did.attestationA · B · C · unknownnoSTIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it.
did.statusactive · quarantine · retiredno
did.verified_atstring (date-time) | nullnoProof of ownership. null = self-declared, never proved; house DIDs are born verified.
did.verification_methodhouse · otp · admin | nullnoHow the ownership was proved; null while verified_at is null.
did.originhouse · customernohouse rows are admin-minted platform inventory; the tenant surface only creates customer.
did.labelsarray of stringno
did.notesstring | nullno
did.created_atstring (date-time)no
did.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"
  }
}

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

CDR-computed DID health ​

http
GET /v1/dids/{id}/health

Health is COMPUTED from the append-only CDR at read time — every number is a row count, never a cached score. short_call_rate (answered calls ended in under 15 s / answered) is the strongest CDR-side "spam likely" signal. Rates are null when the denominator is zero. reputation.hiya/reputation.tns are explicit nulls until an external feed is contracted. A retired DID still serves health (provenance outlives rotation).

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.
fromquerystring (date-time)noRFC 3339 lower bound (endpoint-specific field; default trailing 30 UTC days where noted).
toquerystring (date-time)noRFC 3339 upper bound.

Responses

200 ​

Computed health for the period (default trailing 30 UTC days).

FieldTypeRequiredDescription
did_idstring (uuid)no
e164stringno
statusactive · quarantine · retiredno
periodobjectno
period.fromstring (date-time)no
period.tostring (date-time)no
dialsintegerno
answeredintegerno
answer_ratenumber | nullno
short_callsintegerno
short_call_ratenumber | nullno
short_call_secondsintegerno
dailyarray of objectno
daily[].datestring (date)no
daily[].dialsintegerno
daily[].answeredintegerno
daily[].short_callsintegerno
reputationobjectnoExplicit nulls until an external feed is contracted — render as pending, not zero.
reputation.hiyanumber | nullno
reputation.tnsnumber | nullno
reputation.notestringno
json
{
  "did_id": "4e3d2c1b-0a9f-4e8d-7c6b-5a4f3e2d1c0b",
  "e164": "+13125550184",
  "status": "active",
  "period": {
    "from": "2026-06-05T00:00:00Z",
    "to": "2026-07-05T00:00:00Z"
  },
  "dials": 412,
  "answered": 67,
  "answer_rate": 0.1626,
  "short_calls": 24,
  "short_call_rate": 0.3582,
  "short_call_seconds": 15,
  "daily": [
    {
      "date": "2026-06-01",
      "dials": 80,
      "answered": 14,
      "short_calls": 5
    }
  ],
  "reputation": {
    "hiya": null,
    "tns": null,
    "note": "external reputation feeds not integrated yet"
  }
}

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

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

Launch checklist of a number ​

http
GET /v1/dids/{id}/launch-checklist

Four steps, each stating its source. attestation is derived from the registry (A/B/C = done; the value is what the tenant declared, not a carrier verdict). warmup is derived from the number's own CDR: done once it has dialled on warmup_min_active_days distinct UTC days of the trailing 30. registration and cnam are manual: the platform has no data for them, so they are what the operator confirmed. No external reputation feed is consulted.

Parameters

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

Responses

200 ​

The checklist.

FieldTypeRequiredDescription
did_idstring (uuid)yes
e164stringyes
completebooleanyesAll four steps are done.
warmup_min_active_daysintegeryes
stepsarray of objectyes
steps[].stepregistration · cnam · attestation · warmupyes
steps[].sourcederived · manualyesWhere the state comes from; manual is the operator's word.
steps[].statedone · pendingyes
steps[].evidenceobjectyesderived: {origin, ...} with the values behind the state (dids.attestation + value; cdr + active_days, required_days, dials). manual: the latest {confirmed_at, note}, empty if never confirmed.

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

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

Confirm (or un-confirm) a manual launch step ​

http
POST /v1/dids/{id}/launch-checklist/{step}/confirmations

Appends a confirmation for a manual step (registration or cnam); the latest one is the step's state and none is ever edited or deleted. A derived step (attestation, warmup) is 422: the platform computes it. An unknown step is 404. Returns the fresh checklist.

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.
steppathregistration · cnam · attestation · warmupyes

Request body (JSON, required)

FieldTypeRequiredDescription
confirmedbooleanyes
notestringno

Responses

201 ​

The checklist after the confirmation.

FieldTypeRequiredDescription
did_idstring (uuid)yes
e164stringyes
completebooleanyesAll four steps are done.
warmup_min_active_daysintegeryes
stepsarray of objectyes
steps[].stepregistration · cnam · attestation · warmupyes
steps[].sourcederived · manualyesWhere the state comes from; manual is the operator's word.
steps[].statedone · pendingyes
steps[].evidenceobjectyesderived: {origin, ...} with the values behind the state (dids.attestation + value; cdr + active_days, required_days, dials). manual: the latest {confirmed_at, note}, empty if never confirmed.

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

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

Carrier tickets of a number ​

http
GET /v1/dids/{id}/carrier-tickets

Tickets the tenant keeps about this number with its carrier, most recently active first, each with its full append-only thread. A retired number still serves its tickets.

Parameters

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

Responses

200 ​

The tickets.

FieldTypeRequiredDescription
ticketsarray of DidCarrierTicketyes
tickets[].idstring (uuid)yes
tickets[].did_idstring (uuid)yes
tickets[].carrierstringyes
tickets[].subjectstringyes
tickets[].referencestringyes
tickets[].statusopen · in_progress · resolved · closedyes
tickets[].opened_atstring (date-time)yes
tickets[].updated_atstring (date-time)yes
tickets[].eventsarray of objectyesThe append-only thread, oldest first; the first has kind opened.
tickets[].events[].idstring (uuid)yes
tickets[].events[].kindopened · updateyes
tickets[].events[].statusopen · in_progress · resolved · closedyes
tickets[].events[].notestringyes
tickets[].events[].created_atstring (date-time)yes

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

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

Open a carrier ticket for a number ​

http
POST /v1/dids/{id}/carrier-tickets

Parameters

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

Request body (JSON, required)

FieldTypeRequiredDescription
carrierstringyes
subjectstringyes
referencestringnoThe carrier's own ticket reference, if it gave one.
notestringno

Responses

201 ​

The opened ticket (status open).

FieldTypeRequiredDescription
ticketobject (DidCarrierTicket)yes
ticket.idstring (uuid)yes
ticket.did_idstring (uuid)yes
ticket.carrierstringyes
ticket.subjectstringyes
ticket.referencestringyes
ticket.statusopen · in_progress · resolved · closedyes
ticket.opened_atstring (date-time)yes
ticket.updated_atstring (date-time)yes
ticket.eventsarray of objectyesThe append-only thread, oldest first; the first has kind opened.
ticket.events[].idstring (uuid)yes
ticket.events[].kindopened · updateyes
ticket.events[].statusopen · in_progress · resolved · closedyes
ticket.events[].notestringyes
ticket.events[].created_atstring (date-time)yes

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

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

Add a note and/or move a carrier ticket's status ​

http
POST /v1/dids/{id}/carrier-tickets/{ticket_id}/events

Appends to the ticket's thread; nothing is edited or deleted. status defaults to the current one. An event with neither a note nor a status change is 400.

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.
ticket_idpathstring (uuid)yesTicket UUID. It must belong to the number in the path, else 404.

Request body (JSON, required)

FieldTypeRequiredDescription
statusopen · in_progress · resolved · closedno
notestringno

Responses

201 ​

The ticket with the new event appended.

FieldTypeRequiredDescription
ticketobject (DidCarrierTicket)yes
ticket.idstring (uuid)yes
ticket.did_idstring (uuid)yes
ticket.carrierstringyes
ticket.subjectstringyes
ticket.referencestringyes
ticket.statusopen · in_progress · resolved · closedyes
ticket.opened_atstring (date-time)yes
ticket.updated_atstring (date-time)yes
ticket.eventsarray of objectyesThe append-only thread, oldest first; the first has kind opened.
ticket.events[].idstring (uuid)yes
ticket.events[].kindopened · updateyes
ticket.events[].statusopen · in_progress · resolved · closedyes
ticket.events[].notestringyes
ticket.events[].created_atstring (date-time)yes

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

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 (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.