Appearance
Inbound ivr menus
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.
The tenant's inbound IVR menus: published versions, append-only history, rendered to audio when published. An inbound route with dest_type: ivr runs one of them.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/inbound-ivr-menus | The tenant's current inbound IVR menus |
POST | /v1/inbound-ivr-menus | Publish an inbound IVR menu, or the next version of one |
GET | /v1/inbound-ivr-menus/{id} | One inbound IVR menu, its current version and its history |
POST | /v1/inbound-ivr-menus/{id}/retire | Retire an inbound IVR menu |
The tenant's current inbound IVR menus
http
GET /v1/inbound-ivr-menusEvery menu that is not retired, in its current (highest) version, ordered by name. A retired menu is not listed; GET /v1/inbound-ivr-menus/{id} still answers it. Readable by a dd_ key and by any dashboard login; a widget token (ddw_) is 403 forbidden.
An inbound route with dest_type: ivr names one of these menus in its dest_ref; each call to it hears the version current when it connects (core#1319).
Responses
200
The current menus.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_ivr_menus | array of InboundIvrMenu | yes | |
inbound_ivr_menus[].id | string (uuid) | yes | The menu; stable across its versions. |
inbound_ivr_menus[].status | published · retired | yes | |
inbound_ivr_menus[].version | integer | yes | The current version, or the one the menu was retired at. |
inbound_ivr_menus[].name | string | yes | |
inbound_ivr_menus[].menu | object (InboundIvrMenuNode) | yes | |
inbound_ivr_menus[].menu.prompt | string | yes | What the caller hears on entering the node, in the tenant's words (at most 2000 bytes; blank is refused). Rendered to audio when the menu is published. |
inbound_ivr_menus[].menu.default | any | yes | MANDATORY. With no key pressed the prompt plays again and, the second time, this action runs — no menu is a dead end. Never optout: pressing nothing is not asking to leave the list. |
inbound_ivr_menus[].menu.entries | object | yes | One DTMF key (0-9, *, #) → a terminal action or a submenu. |
inbound_ivr_menus[].published_at | string (date-time) | yes | When this version was published. |
inbound_ivr_menus[].published_by | string | yes | The user or agent id of the credential that published this version, or tenant-api-key. |
inbound_ivr_menus[].retired_at | string,null (date-time) | yes | |
inbound_ivr_menus[].retired_by | string,null | 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"
}
}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"
}
}Publish an inbound IVR menu, or the next version of one
http
POST /v1/inbound-ivr-menusPublishes menu under name. Without menu_id it is a new menu, version 1, whose id is the menu's from then on; with menu_id it is the next version of that menu, which becomes the current one. Nothing is ever edited in place: the history keeps every version.
Publishing, in order, and nothing is written unless all pass:
- the tree is validated (
InboundIvrMenuNode): every node has a prompt and adefaultaction, keys are single DTMF keys, at most 5 levels and 64 nodes; - every
queue:<id>must be an ACTIVE (not retired) inbound queue of this tenant (/v1/inbound-queues) and everyagent:<id>an ACTIVE agent of this tenant, else422naming them (another tenant's, a non-existent one and a retired queue read the same); - every prompt is rendered to audio NOW, not when a call arrives. If one does not render the menu is not published:
503 prompt_render_failed, and the same request may be retried.
The prompts are the tenant's words: the platform adds no text of its own. A menu_id that is not a menu of this tenant is 404; a retired one is 409 conflict (a retired menu stays retired — publish a new one).
Tenant management only: a dd_ key or an account_admin login. A member login is 403 role_forbidden; a widget token (ddw_) is 403 forbidden. Who published is taken from the credential.
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | The menu's name, for the operator (trimmed; blank is refused). |
menu | object (InboundIvrMenuNode) | yes | |
menu.prompt | string | yes | What the caller hears on entering the node, in the tenant's words (at most 2000 bytes; blank is refused). Rendered to audio when the menu is published. |
menu.default | any | yes | MANDATORY. With no key pressed the prompt plays again and, the second time, this action runs — no menu is a dead end. Never optout: pressing nothing is not asking to leave the list. |
menu.entries | object | yes | One DTMF key (0-9, *, #) → a terminal action or a submenu. |
menu_id | string (uuid) | no | The menu to publish the next version of. Absent ⇒ a new menu. |
json
{
"name": "Main line",
"menu": {
"prompt": "Thanks for calling. For sales press 1. For billing press 2. To stop receiving our calls press 9.",
"default": "queue:6f1c1b7e-2d7a-4c1e-9d3a-0b5e8f7a1c21",
"entries": {
"1": {
"action": "agent:0d9e2b4a-8c3f-4e6b-a1d2-3c4b5a6f7e80"
},
"2": {
"submenu": {
"prompt": "For a copy of your invoice press 1.",
"default": "hangup",
"entries": {
"1": {
"action": "queue:6f1c1b7e-2d7a-4c1e-9d3a-0b5e8f7a1c21"
}
}
}
},
"9": {
"action": "optout"
}
}
}
}Responses
201
Published.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_ivr_menu | object (InboundIvrMenu) | yes | |
inbound_ivr_menu.id | string (uuid) | yes | The menu; stable across its versions. |
inbound_ivr_menu.status | published · retired | yes | |
inbound_ivr_menu.version | integer | yes | The current version, or the one the menu was retired at. |
inbound_ivr_menu.name | string | yes | |
inbound_ivr_menu.menu | object (InboundIvrMenuNode) | yes | |
inbound_ivr_menu.menu.prompt | string | yes | What the caller hears on entering the node, in the tenant's words (at most 2000 bytes; blank is refused). Rendered to audio when the menu is published. |
inbound_ivr_menu.menu.default | any | yes | MANDATORY. With no key pressed the prompt plays again and, the second time, this action runs — no menu is a dead end. Never optout: pressing nothing is not asking to leave the list. |
inbound_ivr_menu.menu.entries | object | yes | One DTMF key (0-9, *, #) → a terminal action or a submenu. |
inbound_ivr_menu.published_at | string (date-time) | yes | When this version was published. |
inbound_ivr_menu.published_by | string | yes | The user or agent id of the credential that published this version, or tenant-api-key. |
inbound_ivr_menu.retired_at | string,null (date-time) | yes | |
inbound_ivr_menu.retired_by | string,null | 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
role_forbidden: a dashboard login without the account_admin role; forbidden: a widget token or a suspended tenant.
json
{
"error": {
"code": "role_forbidden",
"message": "a dashboard login needs the account_admin role to manage inbound IVR menus"
}
}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 (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"
}
}503
A prompt did not render to audio; nothing was published. Retryable.
json
{
"error": {
"code": "prompt_render_failed",
"message": "a prompt could not be rendered to audio; the menu was not published"
}
}One inbound IVR menu, its current version and its history
http
GET /v1/inbound-ivr-menus/{id}The menu's current version — or, once retired, the version it was retired at, with status: retired — plus history, every fact from the first version to the retirement. A menu that is not this tenant's (or does not exist) is 404. Same readers as the list.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The menu and its history.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_ivr_menu | any | 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"
}
}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 an inbound IVR menu
http
POST /v1/inbound-ivr-menus/{id}/retireRetires the menu with a new fact that names its current version; the history is append-only. A retired menu stays retired: it cannot be published again. An id that is not a current menu of this tenant (unknown, already retired, or another tenant's) is 404. A menu that an ACTIVE inbound route still uses is 409 conflict naming the routes: disable them or point them at another menu first (core#1319). Same roles as publishing. No body.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
Retired.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_ivr_menu | object (InboundIvrMenu) | yes | |
inbound_ivr_menu.id | string (uuid) | yes | The menu; stable across its versions. |
inbound_ivr_menu.status | published · retired | yes | |
inbound_ivr_menu.version | integer | yes | The current version, or the one the menu was retired at. |
inbound_ivr_menu.name | string | yes | |
inbound_ivr_menu.menu | object (InboundIvrMenuNode) | yes | |
inbound_ivr_menu.menu.prompt | string | yes | What the caller hears on entering the node, in the tenant's words (at most 2000 bytes; blank is refused). Rendered to audio when the menu is published. |
inbound_ivr_menu.menu.default | any | yes | MANDATORY. With no key pressed the prompt plays again and, the second time, this action runs — no menu is a dead end. Never optout: pressing nothing is not asking to leave the list. |
inbound_ivr_menu.menu.entries | object | yes | One DTMF key (0-9, *, #) → a terminal action or a submenu. |
inbound_ivr_menu.published_at | string (date-time) | yes | When this version was published. |
inbound_ivr_menu.published_by | string | yes | The user or agent id of the credential that published this version, or tenant-api-key. |
inbound_ivr_menu.retired_at | string,null (date-time) | yes | |
inbound_ivr_menu.retired_by | string,null | 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
role_forbidden: a dashboard login without the account_admin role; forbidden: a widget token or a suspended tenant.
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"
}
}