Appearance
Messaging
Generated from
DialerDigital/corecommitbbb8e373d900— specopenapi/dialer-v1.yamlv0.151.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.
10DLC brand and campaign registration through the SMS provider, and the numbers of an approved campaign. Provider fees land on the balance at cost.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/messaging/brands | List the tenant's 10DLC brands |
POST | /v1/messaging/brands | Register a 10DLC brand with the SMS provider |
GET | /v1/messaging/brands/{id} | Get a 10DLC brand |
POST | /v1/messaging/brands/{id}/refresh | Ask the provider for the current status of a pending brand |
GET | /v1/messaging/campaigns | List the tenant's 10DLC campaigns |
POST | /v1/messaging/campaigns | Register a 10DLC campaign for an approved brand |
GET | /v1/messaging/campaigns/{id} | Get a 10DLC campaign |
POST | /v1/messaging/campaigns/{id}/refresh | Ask the provider for the current status of a pending campaign |
GET | /v1/messaging/campaigns/{id}/numbers | List the numbers of a campaign |
POST | /v1/messaging/campaigns/{id}/numbers | Assign a number to an approved campaign |
List the tenant's 10DLC brands
http
GET /v1/messaging/brandsResponses
200
The brands, newest first.
| Field | Type | Required | Description |
|---|---|---|---|
brands | array of MessagingBrand | yes | |
brands[].id | string (uuid) | yes | |
brands[].legal_name | string | yes | |
brands[].display_name | string | yes | |
brands[].entity_type | private_profit · public_profit · non_profit · government · sole_proprietor | yes | |
brands[].ein_last4 | string | no | |
brands[].vertical | string | no | |
brands[].website | string | no | |
brands[].email | string | no | |
brands[].phone | string | no | |
brands[].street | string | no | |
brands[].city | string | no | |
brands[].state | string | no | |
brands[].postal_code | string | no | |
brands[].country | string | no | |
brands[].vetting_requested | boolean | no | |
brands[].status | pending · approved · rejected | yes | |
brands[].rejection_reason | string,null | no | |
brands[].provider_brand_id | string | no | |
brands[].registry | mock · telnyx | yes | |
brands[].created_at | string (date-time) | no | |
brands[].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"
}
}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"
}
}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 10DLC brand with the SMS provider
http
POST /v1/messaging/brandsRegisters the tenant's brand with The Campaign Registry through the SMS provider and stores its status: pending, approved, or rejected with rejection_reason. The EIN travels to the provider and is not stored; the answer carries ein_last4. The provider's brand fee (and the vetting fee, when vetting_requested) is loaded to the balance at cost as messaging_fee ledger lines. registry says whether the registration was mock (simulated: nothing registered, nothing charged) or telnyx (real, gated by the flag messaging_10dlc_live, off by default). 503 fee_schedule_unset if the real provider has no fee schedule configured; nothing is registered then. 422 vetting_unsupported for vetting_requested: true with the real provider: the external vetting order is not implemented yet, so it is refused before any call or charge (the simulated registry accepts it).
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
legal_name | string | yes | |
display_name | string | yes | |
entity_type | private_profit · public_profit · non_profit · government · sole_proprietor | yes | |
ein | string | no | 9 digits; write-only, never stored in full. |
vertical | string | yes | |
website | string | no | |
email | string | yes | |
phone | string | yes | |
street | string | yes | |
city | string | yes | |
state | string | yes | |
postal_code | string | yes | |
country | string | no | |
vetting_requested | boolean | no |
Responses
201
The registered brand.
| Field | Type | Required | Description |
|---|---|---|---|
brand | object (MessagingBrand) | yes | |
brand.id | string (uuid) | yes | |
brand.legal_name | string | yes | |
brand.display_name | string | yes | |
brand.entity_type | private_profit · public_profit · non_profit · government · sole_proprietor | yes | |
brand.ein_last4 | string | no | |
brand.vertical | string | no | |
brand.website | string | no | |
brand.email | string | no | |
brand.phone | string | no | |
brand.street | string | no | |
brand.city | string | no | |
brand.state | string | no | |
brand.postal_code | string | no | |
brand.country | string | no | |
brand.vetting_requested | boolean | no | |
brand.status | pending · approved · rejected | yes | |
brand.rejection_reason | string,null | no | |
brand.provider_brand_id | string | no | |
brand.registry | mock · telnyx | yes | |
brand.created_at | string (date-time) | no | |
brand.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"
}
}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"
}
}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"
}
}502 — BadGateway
An upstream dependency (the AI runtime sandbox) returned an unexpected/failed response.
json
{
"error": {
"code": "bad_gateway",
"message": "the AI runtime sandbox could not be reached"
}
}Get a 10DLC brand
http
GET /v1/messaging/brands/{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 brand.
| Field | Type | Required | Description |
|---|---|---|---|
brand | object (MessagingBrand) | yes | |
brand.id | string (uuid) | yes | |
brand.legal_name | string | yes | |
brand.display_name | string | yes | |
brand.entity_type | private_profit · public_profit · non_profit · government · sole_proprietor | yes | |
brand.ein_last4 | string | no | |
brand.vertical | string | no | |
brand.website | string | no | |
brand.email | string | no | |
brand.phone | string | no | |
brand.street | string | no | |
brand.city | string | no | |
brand.state | string | no | |
brand.postal_code | string | no | |
brand.country | string | no | |
brand.vetting_requested | boolean | no | |
brand.status | pending · approved · rejected | yes | |
brand.rejection_reason | string,null | no | |
brand.provider_brand_id | string | no | |
brand.registry | mock · telnyx | yes | |
brand.created_at | string (date-time) | no | |
brand.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"
}
}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"
}
}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"
}
}Ask the provider for the current status of a pending brand
http
POST /v1/messaging/brands/{id}/refreshA brand that is no longer pending is returned as it is; the provider is not called.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The brand with its current status.
| Field | Type | Required | Description |
|---|---|---|---|
brand | object (MessagingBrand) | yes | |
brand.id | string (uuid) | yes | |
brand.legal_name | string | yes | |
brand.display_name | string | yes | |
brand.entity_type | private_profit · public_profit · non_profit · government · sole_proprietor | yes | |
brand.ein_last4 | string | no | |
brand.vertical | string | no | |
brand.website | string | no | |
brand.email | string | no | |
brand.phone | string | no | |
brand.street | string | no | |
brand.city | string | no | |
brand.state | string | no | |
brand.postal_code | string | no | |
brand.country | string | no | |
brand.vetting_requested | boolean | no | |
brand.status | pending · approved · rejected | yes | |
brand.rejection_reason | string,null | no | |
brand.provider_brand_id | string | no | |
brand.registry | mock · telnyx | yes | |
brand.created_at | string (date-time) | no | |
brand.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"
}
}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"
}
}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"
}
}502 — BadGateway
An upstream dependency (the AI runtime sandbox) returned an unexpected/failed response.
json
{
"error": {
"code": "bad_gateway",
"message": "the AI runtime sandbox could not be reached"
}
}List the tenant's 10DLC campaigns
http
GET /v1/messaging/campaignsResponses
200
The campaigns, newest first.
| Field | Type | Required | Description |
|---|---|---|---|
campaigns | array of MessagingCampaign | yes | |
campaigns[].id | string (uuid) | yes | |
campaigns[].brand_id | string (uuid) | yes | |
campaigns[].use_case | string | yes | |
campaigns[].description | string | no | |
campaigns[].message_flow | string | no | |
campaigns[].sample_messages | array of string | no | |
campaigns[].help_message | string | no | |
campaigns[].optin_keywords | string | no | |
campaigns[].optout_keywords | string | no | |
campaigns[].status | pending · approved · rejected | yes | |
campaigns[].rejection_reason | string,null | no | |
campaigns[].provider_campaign_id | string | no | |
campaigns[].monthly_fee_usd | string | no | Decimal string; the provider's monthly fee, at cost. |
campaigns[].registry | mock · telnyx | yes | |
campaigns[].created_at | string (date-time) | no | |
campaigns[].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"
}
}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"
}
}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 10DLC campaign for an approved brand
http
POST /v1/messaging/campaignsRegisters a campaign for a brand whose status is approved (409 brand_not_approved otherwise) and stores its status. The provider's monthly campaign fee for the first month is loaded to the balance at cost as a messaging_fee line; later months are billed exactly once per month. A text from a US local number leaves only through a number assigned to an approved campaign (422 sms_10dlc_campaign_required on POST /v1/sms/messages otherwise).
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
brand_id | string (uuid) | yes | |
use_case | string | yes | |
description | string | yes | |
message_flow | string | yes | |
sample_messages | array of string | yes | |
help_message | string | no | |
optin_keywords | string | no | |
optout_keywords | string | no |
Responses
201
The registered campaign.
| Field | Type | Required | Description |
|---|---|---|---|
campaign | object (MessagingCampaign) | yes | |
campaign.id | string (uuid) | yes | |
campaign.brand_id | string (uuid) | yes | |
campaign.use_case | string | yes | |
campaign.description | string | no | |
campaign.message_flow | string | no | |
campaign.sample_messages | array of string | no | |
campaign.help_message | string | no | |
campaign.optin_keywords | string | no | |
campaign.optout_keywords | string | no | |
campaign.status | pending · approved · rejected | yes | |
campaign.rejection_reason | string,null | no | |
campaign.provider_campaign_id | string | no | |
campaign.monthly_fee_usd | string | no | Decimal string; the provider's monthly fee, at cost. |
campaign.registry | mock · telnyx | yes | |
campaign.created_at | string (date-time) | no | |
campaign.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"
}
}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"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}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"
}
}502 — BadGateway
An upstream dependency (the AI runtime sandbox) returned an unexpected/failed response.
json
{
"error": {
"code": "bad_gateway",
"message": "the AI runtime sandbox could not be reached"
}
}Get a 10DLC campaign
http
GET /v1/messaging/campaigns/{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 campaign.
| Field | Type | Required | Description |
|---|---|---|---|
campaign | object (MessagingCampaign) | yes | |
campaign.id | string (uuid) | yes | |
campaign.brand_id | string (uuid) | yes | |
campaign.use_case | string | yes | |
campaign.description | string | no | |
campaign.message_flow | string | no | |
campaign.sample_messages | array of string | no | |
campaign.help_message | string | no | |
campaign.optin_keywords | string | no | |
campaign.optout_keywords | string | no | |
campaign.status | pending · approved · rejected | yes | |
campaign.rejection_reason | string,null | no | |
campaign.provider_campaign_id | string | no | |
campaign.monthly_fee_usd | string | no | Decimal string; the provider's monthly fee, at cost. |
campaign.registry | mock · telnyx | yes | |
campaign.created_at | string (date-time) | no | |
campaign.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"
}
}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"
}
}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"
}
}Ask the provider for the current status of a pending campaign
http
POST /v1/messaging/campaigns/{id}/refreshA campaign that is no longer pending is returned as it is; the provider is not called.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The campaign with its current status.
| Field | Type | Required | Description |
|---|---|---|---|
campaign | object (MessagingCampaign) | yes | |
campaign.id | string (uuid) | yes | |
campaign.brand_id | string (uuid) | yes | |
campaign.use_case | string | yes | |
campaign.description | string | no | |
campaign.message_flow | string | no | |
campaign.sample_messages | array of string | no | |
campaign.help_message | string | no | |
campaign.optin_keywords | string | no | |
campaign.optout_keywords | string | no | |
campaign.status | pending · approved · rejected | yes | |
campaign.rejection_reason | string,null | no | |
campaign.provider_campaign_id | string | no | |
campaign.monthly_fee_usd | string | no | Decimal string; the provider's monthly fee, at cost. |
campaign.registry | mock · telnyx | yes | |
campaign.created_at | string (date-time) | no | |
campaign.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"
}
}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"
}
}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"
}
}502 — BadGateway
An upstream dependency (the AI runtime sandbox) returned an unexpected/failed response.
json
{
"error": {
"code": "bad_gateway",
"message": "the AI runtime sandbox could not be reached"
}
}List the numbers of a campaign
http
GET /v1/messaging/campaigns/{id}/numbersParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The numbers.
| Field | Type | Required | Description |
|---|---|---|---|
numbers | array of MessagingCampaignNumber | yes | |
numbers[].id | string (uuid) | yes | |
numbers[].campaign_id | string (uuid) | yes | |
numbers[].e164 | string | yes | |
numbers[].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 — 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"
}
}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"
}
}Assign a number to an approved campaign
http
POST /v1/messaging/campaigns/{id}/numbersThe number must be an active number of the tenant (422 number_not_owned) and the campaign approved (409 campaign_not_approved). Repeating the same assignment returns the existing row; a number of another campaign is 409 number_assigned_elsewhere.
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 |
|---|---|---|---|
e164 | string | yes |
Responses
201
The assignment.
| Field | Type | Required | Description |
|---|---|---|---|
number | object (MessagingCampaignNumber) | yes | |
number.id | string (uuid) | yes | |
number.campaign_id | string (uuid) | yes | |
number.e164 | string | yes | |
number.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 — 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"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}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"
}
}502 — BadGateway
An upstream dependency (the AI runtime sandbox) returned an unexpected/failed response.
json
{
"error": {
"code": "bad_gateway",
"message": "the AI runtime sandbox could not be reached"
}
}