Appearance
DIDs (caller IDs)
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.
DID registry, CDR-computed health, launch checklist and carrier tickets.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/dids | DID registry |
POST | /v1/dids | Register 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}/health | CDR-computed DID health |
GET | /v1/dids/{id}/launch-checklist | Launch checklist of a number |
POST | /v1/dids/{id}/launch-checklist/{step}/confirmations | Confirm (or un-confirm) a manual launch step |
GET | /v1/dids/{id}/carrier-tickets | Carrier tickets of a number |
POST | /v1/dids/{id}/carrier-tickets | Open a carrier ticket for a number |
POST | /v1/dids/{id}/carrier-tickets/{ticket_id}/events | Add a note and/or move a carrier ticket's status |
DID registry
http
GET /v1/didsParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
status | query | active · quarantine · retired | no |
Responses
200
Registry rows ordered by e164.
| Field | Type | Required | Description |
|---|---|---|---|
dids | array of DID | yes | |
dids[].id | string (uuid) | no | |
dids[].tenant_id | string (uuid) | no | |
dids[].e164 | string | no | Immutable after creation. |
dids[].npa | string | null | no | |
dids[].us_state | string | null | no | |
dids[].attestation | A · B · C · unknown | no | STIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it (core#2415). |
dids[].status | active · quarantine · retired | no | |
dids[].verified_at | string (date-time) | null | no | Proof of ownership (core#2415, F-03). null = self-declared, never proved; house DIDs are born verified. |
dids[].verification_method | house · otp · admin | null | no | How the ownership was proved; null while verified_at is null. |
dids[].origin | house · customer | no | house rows are admin-minted platform inventory; the tenant surface only creates customer. |
dids[].labels | array of string | no | |
dids[].notes | string | null | no | |
dids[].created_at | string (date-time) | no | |
dids[].updated_at | string (date-time) | no | |
truncated | boolean | yes | true 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 (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"
}
}Register a tenant-owned number
http
POST /v1/didse164 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).
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
e164 | string | yes | E.164 (immutable after creation). |
npa | string | no | |
us_state | string | no | |
attestation | A · B · C · unknown | no | Accepted and IGNORED (core#2415, F-03): 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. |
status | active · quarantine · retired | no | |
labels | array of string | no | |
notes | string | no | |
origin | house · customer | no | Accepted 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.
| Field | Type | Required | Description |
|---|---|---|---|
did | object (DID) | yes | |
did.id | string (uuid) | no | |
did.tenant_id | string (uuid) | no | |
did.e164 | string | no | Immutable after creation. |
did.npa | string | null | no | |
did.us_state | string | null | no | |
did.attestation | A · B · C · unknown | no | STIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it (core#2415). |
did.status | active · quarantine · retired | no | |
did.verified_at | string (date-time) | null | no | Proof of ownership (core#2415, F-03). null = self-declared, never proved; house DIDs are born verified. |
did.verification_method | house · otp · admin | null | no | How the ownership was proved; null while verified_at is null. |
did.origin | house · customer | no | house rows are admin-minted platform inventory; the tenant surface only creates customer. |
did.labels | array of string | no | |
did.notes | string | null | no | |
did.created_at | string (date-time) | no | |
did.updated_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"
}
}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 (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"
}
}Fetch one DID
http
GET /v1/dids/{id}Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The DID.
| Field | Type | Required | Description |
|---|---|---|---|
did | object (DID) | yes | |
did.id | string (uuid) | no | |
did.tenant_id | string (uuid) | no | |
did.e164 | string | no | Immutable after creation. |
did.npa | string | null | no | |
did.us_state | string | null | no | |
did.attestation | A · B · C · unknown | no | STIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it (core#2415). |
did.status | active · quarantine · retired | no | |
did.verified_at | string (date-time) | null | no | Proof of ownership (core#2415, F-03). null = self-declared, never proved; house DIDs are born verified. |
did.verification_method | house · otp · admin | null | no | How the ownership was proved; null while verified_at is null. |
did.origin | house · customer | no | house rows are admin-minted platform inventory; the tenant surface only creates customer. |
did.labels | array of string | no | |
did.notes | string | null | no | |
did.created_at | string (date-time) | no | |
did.updated_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"
}
}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"
}
}Update metadata/status (e164 immutable)
http
PATCH /v1/dids/{id}A different number is a different DID; CDR history is never re-attributed.
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 |
|---|---|---|---|
npa | string | no | |
us_state | string | no | |
attestation | A · B · C · unknown | no | Accepted and IGNORED (core#2415, F-03): 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. |
status | active · quarantine · retired | no | |
labels | array of string | no | |
notes | string | no | |
origin | house · customer | no | Accepted 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.
| Field | Type | Required | Description |
|---|---|---|---|
did | object (DID) | yes | |
did.id | string (uuid) | no | |
did.tenant_id | string (uuid) | no | |
did.e164 | string | no | Immutable after creation. |
did.npa | string | null | no | |
did.us_state | string | null | no | |
did.attestation | A · B · C · unknown | no | STIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it (core#2415). |
did.status | active · quarantine · retired | no | |
did.verified_at | string (date-time) | null | no | Proof of ownership (core#2415, F-03). null = self-declared, never proved; house DIDs are born verified. |
did.verification_method | house · otp · admin | null | no | How the ownership was proved; null while verified_at is null. |
did.origin | house · customer | no | house rows are admin-minted platform inventory; the tenant surface only creates customer. |
did.labels | array of string | no | |
did.notes | string | null | no | |
did.created_at | string (date-time) | no | |
did.updated_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"
}
}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"
}
}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.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The retired DID.
| Field | Type | Required | Description |
|---|---|---|---|
did | object (DID) | yes | |
did.id | string (uuid) | no | |
did.tenant_id | string (uuid) | no | |
did.e164 | string | no | Immutable after creation. |
did.npa | string | null | no | |
did.us_state | string | null | no | |
did.attestation | A · B · C · unknown | no | STIR/SHAKEN level the carrier signs. Read-only for the tenant: only the platform admin sets it (core#2415). |
did.status | active · quarantine · retired | no | |
did.verified_at | string (date-time) | null | no | Proof of ownership (core#2415, F-03). null = self-declared, never proved; house DIDs are born verified. |
did.verification_method | house · otp · admin | null | no | How the ownership was proved; null while verified_at is null. |
did.origin | house · customer | no | house rows are admin-minted platform inventory; the tenant surface only creates customer. |
did.labels | array of string | no | |
did.notes | string | null | no | |
did.created_at | string (date-time) | no | |
did.updated_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"
}
}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"
}
}CDR-computed DID health
http
GET /v1/dids/{id}/healthHealth 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
from | query | string (date-time) | no | RFC 3339 lower bound (endpoint-specific field; default trailing 30 UTC days where noted). |
to | query | string (date-time) | no | RFC 3339 upper bound. |
Responses
200
Computed health for the period (default trailing 30 UTC days).
| Field | Type | Required | Description |
|---|---|---|---|
did_id | string (uuid) | no | |
e164 | string | no | |
status | active · quarantine · retired | no | |
period | object | no | |
period.from | string (date-time) | no | |
period.to | string (date-time) | no | |
dials | integer | no | |
answered | integer | no | |
answer_rate | number | null | no | |
short_calls | integer | no | |
short_call_rate | number | null | no | |
short_call_seconds | integer | no | |
daily | array of object | no | |
daily[].date | string (date) | no | |
daily[].dials | integer | no | |
daily[].answered | integer | no | |
daily[].short_calls | integer | no | |
reputation | object | no | Explicit nulls until an external feed is contracted — render as pending, not zero. |
reputation.hiya | number | null | no | |
reputation.tns | number | null | no | |
reputation.note | string | no |
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 (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"
}
}Launch checklist of a number
http
GET /v1/dids/{id}/launch-checklistFour 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The checklist.
| Field | Type | Required | Description |
|---|---|---|---|
did_id | string (uuid) | yes | |
e164 | string | yes | |
complete | boolean | yes | All four steps are done. |
warmup_min_active_days | integer | yes | |
steps | array of object | yes | |
steps[].step | registration · cnam · attestation · warmup | yes | |
steps[].source | derived · manual | yes | Where the state comes from; manual is the operator's word. |
steps[].state | done · pending | yes | |
steps[].evidence | object | yes | derived: {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 (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"
}
}Confirm (or un-confirm) a manual launch step
http
POST /v1/dids/{id}/launch-checklist/{step}/confirmationsAppends 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
step | path | registration · cnam · attestation · warmup | yes |
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
confirmed | boolean | yes | |
note | string | no |
Responses
201
The checklist after the confirmation.
| Field | Type | Required | Description |
|---|---|---|---|
did_id | string (uuid) | yes | |
e164 | string | yes | |
complete | boolean | yes | All four steps are done. |
warmup_min_active_days | integer | yes | |
steps | array of object | yes | |
steps[].step | registration · cnam · attestation · warmup | yes | |
steps[].source | derived · manual | yes | Where the state comes from; manual is the operator's word. |
steps[].state | done · pending | yes | |
steps[].evidence | object | yes | derived: {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 (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"
}
}Carrier tickets of a number
http
GET /v1/dids/{id}/carrier-ticketsTickets 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The tickets.
| Field | Type | Required | Description |
|---|---|---|---|
tickets | array of DidCarrierTicket | yes | |
tickets[].id | string (uuid) | yes | |
tickets[].did_id | string (uuid) | yes | |
tickets[].carrier | string | yes | |
tickets[].subject | string | yes | |
tickets[].reference | string | yes | |
tickets[].status | open · in_progress · resolved · closed | yes | |
tickets[].opened_at | string (date-time) | yes | |
tickets[].updated_at | string (date-time) | yes | |
tickets[].events | array of object | yes | The append-only thread, oldest first; the first has kind opened. |
tickets[].events[].id | string (uuid) | yes | |
tickets[].events[].kind | opened · update | yes | |
tickets[].events[].status | open · in_progress · resolved · closed | yes | |
tickets[].events[].note | string | yes | |
tickets[].events[].created_at | string (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 (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"
}
}Open a carrier ticket for a number
http
POST /v1/dids/{id}/carrier-ticketsParameters
| 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 |
|---|---|---|---|
carrier | string | yes | |
subject | string | yes | |
reference | string | no | The carrier's own ticket reference, if it gave one. |
note | string | no |
Responses
201
The opened ticket (status open).
| Field | Type | Required | Description |
|---|---|---|---|
ticket | object (DidCarrierTicket) | yes | |
ticket.id | string (uuid) | yes | |
ticket.did_id | string (uuid) | yes | |
ticket.carrier | string | yes | |
ticket.subject | string | yes | |
ticket.reference | string | yes | |
ticket.status | open · in_progress · resolved · closed | yes | |
ticket.opened_at | string (date-time) | yes | |
ticket.updated_at | string (date-time) | yes | |
ticket.events | array of object | yes | The append-only thread, oldest first; the first has kind opened. |
ticket.events[].id | string (uuid) | yes | |
ticket.events[].kind | opened · update | yes | |
ticket.events[].status | open · in_progress · resolved · closed | yes | |
ticket.events[].note | string | yes | |
ticket.events[].created_at | string (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 (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"
}
}Add a note and/or move a carrier ticket's status
http
POST /v1/dids/{id}/carrier-tickets/{ticket_id}/eventsAppends 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
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
ticket_id | path | string (uuid) | yes | Ticket UUID. It must belong to the number in the path, else 404. |
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
status | open · in_progress · resolved · closed | no | |
note | string | no |
Responses
201
The ticket with the new event appended.
| Field | Type | Required | Description |
|---|---|---|---|
ticket | object (DidCarrierTicket) | yes | |
ticket.id | string (uuid) | yes | |
ticket.did_id | string (uuid) | yes | |
ticket.carrier | string | yes | |
ticket.subject | string | yes | |
ticket.reference | string | yes | |
ticket.status | open · in_progress · resolved · closed | yes | |
ticket.opened_at | string (date-time) | yes | |
ticket.updated_at | string (date-time) | yes | |
ticket.events | array of object | yes | The append-only thread, oldest first; the first has kind opened. |
ticket.events[].id | string (uuid) | yes | |
ticket.events[].kind | opened · update | yes | |
ticket.events[].status | open · in_progress · resolved · closed | yes | |
ticket.events[].note | string | yes | |
ticket.events[].created_at | string (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 (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"
}
}