Appearance
Sms templates
Generated from
DialerDigital/corecommit0fc18008b3c8— specopenapi/dialer-v1.yamlv0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.
Per-tenant SMS templates with typed variables, approval and the "templates only" setting.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/sms-templates | SMS template versions |
POST | /v1/sms-templates | Create a draft (the next version when name already exists) |
GET | /v1/sms-templates/{id} | One template version |
PATCH | /v1/sms-templates/{id} | Edit a draft (body / variables) |
POST | /v1/sms-templates/{id}/approve | Approve a draft (account_admin only) |
POST | /v1/sms-templates/{id}/retire | Retire a version (a draft is discarded by retiring it) |
GET | /v1/sms/template-policy | The tenant's "templates only" setting |
PUT | /v1/sms/template-policy | Turn "templates only" on or off (account_admin only) |
SMS template versions
http
GET /v1/sms-templatesEvery version of every template of the tenant, newest version first per name.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
status | query | draft · approved · retired | no | |
name | query | string | no |
Responses
200
The tenant's template versions.
| Field | Type | Required | Description |
|---|---|---|---|
sms_templates | array of SmsTemplate | yes | |
sms_templates[].id | string (uuid) | no | |
sms_templates[].name | string | no | |
sms_templates[].version | integer | no | |
sms_templates[].status | draft · approved · retired | no | |
sms_templates[].body | string | no | |
sms_templates[].variables | array of SmsTemplateVariable | no | |
sms_templates[].variables[].name | string | yes | |
sms_templates[].variables[].type | string · number · date | yes | |
sms_templates[].variables[].required | boolean | no | An optional variable that is not sent renders empty. |
sms_templates[].approved_by | string (uuid) | null | no | |
sms_templates[].approved_at | string (date-time) | null | no | |
sms_templates[].retired_at | string (date-time) | null | no | |
sms_templates[].created_at | string (date-time) | no | |
sms_templates[].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 (core#2406, 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 (core#2251, ADR #280 addendum). 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 (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"
}
}Create a draft (the next version when name already exists)
http
POST /v1/sms-templatesThe body uses {{variable}} markers; every marker must be declared in variables and every declared variable used. The static text must already carry the opt-out notice and no carrier-prohibited term (the same rules the send enforces). Variable types: string (1-160 chars, no control characters), number, date (ISO-8601). A name that already has versions opens the next version as a new draft: an approved version is never edited. tenant_id, version and status are never body input.
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Template family; an existing name opens its next version. |
body | string | yes | |
variables | array of SmsTemplateVariable | no | |
variables[].name | string | yes | |
variables[].type | string · number · date | yes | |
variables[].required | boolean | no | An optional variable that is not sent renders empty. |
json
{
"name": "payment-reminder",
"body": "Hi {{first_name}}, your balance of {{amount}} is due {{due_date}}. Reply STOP to opt out.",
"variables": [
{
"name": "first_name",
"type": "string"
},
{
"name": "amount",
"type": "number"
},
{
"name": "due_date",
"type": "date"
}
]
}Responses
201
The new draft.
| Field | Type | Required | Description |
|---|---|---|---|
sms_template | object (SmsTemplate) | yes | |
sms_template.id | string (uuid) | no | |
sms_template.name | string | no | |
sms_template.version | integer | no | |
sms_template.status | draft · approved · retired | no | |
sms_template.body | string | no | |
sms_template.variables | array of SmsTemplateVariable | no | |
sms_template.variables[].name | string | yes | |
sms_template.variables[].type | string · number · date | yes | |
sms_template.variables[].required | boolean | no | An optional variable that is not sent renders empty. |
sms_template.approved_by | string (uuid) | null | no | |
sms_template.approved_at | string (date-time) | null | no | |
sms_template.retired_at | string (date-time) | null | no | |
sms_template.created_at | string (date-time) | no | |
sms_template.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 (core#2406, 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 (core#2251, ADR #280 addendum). 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 (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"
}
}One template version
http
GET /v1/sms-templates/{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 version.
| Field | Type | Required | Description |
|---|---|---|---|
sms_template | object (SmsTemplate) | yes | |
sms_template.id | string (uuid) | no | |
sms_template.name | string | no | |
sms_template.version | integer | no | |
sms_template.status | draft · approved · retired | no | |
sms_template.body | string | no | |
sms_template.variables | array of SmsTemplateVariable | no | |
sms_template.variables[].name | string | yes | |
sms_template.variables[].type | string · number · date | yes | |
sms_template.variables[].required | boolean | no | An optional variable that is not sent renders empty. |
sms_template.approved_by | string (uuid) | null | no | |
sms_template.approved_at | string (date-time) | null | no | |
sms_template.retired_at | string (date-time) | null | no | |
sms_template.created_at | string (date-time) | no | |
sms_template.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 (core#2406, 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 (core#2251, ADR #280 addendum). 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 (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"
}
}Edit a draft (body / variables)
http
PATCH /v1/sms-templates/{id}Only a draft can change; an approved or retired version answers 409 (version it instead).
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 |
|---|---|---|---|
body | string | no | |
variables | array of SmsTemplateVariable | no | |
variables[].name | string | yes | |
variables[].type | string · number · date | yes | |
variables[].required | boolean | no | An optional variable that is not sent renders empty. |
Responses
200
The updated draft.
| Field | Type | Required | Description |
|---|---|---|---|
sms_template | object (SmsTemplate) | yes | |
sms_template.id | string (uuid) | no | |
sms_template.name | string | no | |
sms_template.version | integer | no | |
sms_template.status | draft · approved · retired | no | |
sms_template.body | string | no | |
sms_template.variables | array of SmsTemplateVariable | no | |
sms_template.variables[].name | string | yes | |
sms_template.variables[].type | string · number · date | yes | |
sms_template.variables[].required | boolean | no | An optional variable that is not sent renders empty. |
sms_template.approved_by | string (uuid) | null | no | |
sms_template.approved_at | string (date-time) | null | no | |
sms_template.retired_at | string (date-time) | null | no | |
sms_template.created_at | string (date-time) | no | |
sms_template.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 (core#2406, 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 (core#2251, ADR #280 addendum). 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"
}
}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"
}
}Approve a draft (account_admin only)
http
POST /v1/sms-templates/{id}/approveApproves the draft exactly as it stands and records who and when (approved_by, approved_at). Only an account_admin login may approve: a member or a dd_ machine key gets 403. The database enforces it too. A version that is not a draft answers 409.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The approved version.
| Field | Type | Required | Description |
|---|---|---|---|
sms_template | object (SmsTemplate) | yes | |
sms_template.id | string (uuid) | no | |
sms_template.name | string | no | |
sms_template.version | integer | no | |
sms_template.status | draft · approved · retired | no | |
sms_template.body | string | no | |
sms_template.variables | array of SmsTemplateVariable | no | |
sms_template.variables[].name | string | yes | |
sms_template.variables[].type | string · number · date | yes | |
sms_template.variables[].required | boolean | no | An optional variable that is not sent renders empty. |
sms_template.approved_by | string (uuid) | null | no | |
sms_template.approved_at | string (date-time) | null | no | |
sms_template.retired_at | string (date-time) | null | no | |
sms_template.created_at | string (date-time) | no | |
sms_template.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 (core#2406, 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 (core#2251, ADR #280 addendum). 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"
}
}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 version (a draft is discarded by retiring it)
http
POST /v1/sms-templates/{id}/retireTerminal. A retired version can no longer be sent; an already retired one answers 409.
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 version.
| Field | Type | Required | Description |
|---|---|---|---|
sms_template | object (SmsTemplate) | yes | |
sms_template.id | string (uuid) | no | |
sms_template.name | string | no | |
sms_template.version | integer | no | |
sms_template.status | draft · approved · retired | no | |
sms_template.body | string | no | |
sms_template.variables | array of SmsTemplateVariable | no | |
sms_template.variables[].name | string | yes | |
sms_template.variables[].type | string · number · date | yes | |
sms_template.variables[].required | boolean | no | An optional variable that is not sent renders empty. |
sms_template.approved_by | string (uuid) | null | no | |
sms_template.approved_at | string (date-time) | null | no | |
sms_template.retired_at | string (date-time) | null | no | |
sms_template.created_at | string (date-time) | no | |
sms_template.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 (core#2406, 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 (core#2251, ADR #280 addendum). 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"
}
}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"
}
}The tenant's "templates only" setting
http
GET /v1/sms/template-policyOff (false) until an account_admin turns it on.
Responses
200
The current setting.
| Field | Type | Required | Description |
|---|---|---|---|
sms_template_policy | object (SmsTemplatePolicy) | yes | |
sms_template_policy.templates_only | boolean | 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"
}
}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 (core#2406, 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 (core#2251, ADR #280 addendum). No payment changes it.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}Turn "templates only" on or off (account_admin only)
http
PUT /v1/sms/template-policyWith it on, POST /v1/sms/messages refuses free text (422 sms_templates_only) and only approved templates send. Each change is an append-only ledger row; a member or a dd_ machine key gets 403. Campaign SMS (sms_body) is not covered by this setting.
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
templates_only | boolean | yes |
json
{
"templates_only": true
}Responses
200
The stored setting.
| Field | Type | Required | Description |
|---|---|---|---|
sms_template_policy | object (SmsTemplatePolicy) | yes | |
sms_template_policy.templates_only | boolean | 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"
}
}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 (core#2406, 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 (core#2251, ADR #280 addendum). No payment changes it.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}