Appearance
Inbound routes
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.
Inbound DID routing + SBC provisioning.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/inbound-routes | List inbound routes |
POST | /v1/inbound-routes | Create an inbound route |
GET | /v1/inbound-routes/{id} | Fetch one inbound route |
PATCH | /v1/inbound-routes/{id} | Update an inbound route (e164 immutable) |
POST | /v1/inbound-routes/{id}/provision | Push the route to the SIP edge |
POST | /v1/inbound-routes/{id}/disable | Disable an inbound route |
List inbound routes
http
GET /v1/inbound-routesResponses
200
The tenant's inbound routes.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_routes | array of InboundRoute | yes | |
inbound_routes[].id | string (uuid) | no | |
inbound_routes[].tenant_id | string (uuid) | no | |
inbound_routes[].e164 | string | no | Immutable after creation. |
inbound_routes[].dest_type | agent · queue · ivr · optout · ai_agent | no | ivr runs the inbound IVR menu dest_ref names (core#1319). A route saved before core#1311 without a menu is still refused at connect with CALL_REJECTED. optout is the opt-out number (core#1316): the calling number joins the tenant's own do-not-call list at connect and FreeSWITCH confirms and hangs up; such a route always reads dest_ref: null, recording: false, ai_enabled: false. agent bridges straight to the registered agent; queue anchors the leg on FreeSWITCH. Today the production FreeSWITCH image ships no queue extensions (ADR #104 D13: the park pen and dd_queue_hold are a pending freeswitch dependency, not yet delivered), so a queue leg is hung up by FreeSWITCH as UNALLOCATED_NUMBER regardless of the flag. Once they ship: with the tenant's inbound_queue_stream_enabled flag ON and its agent_session_journal_enabled ON too (core#1354: with the seat journal OFF the call is refused unanswered, since no agent could be offered it), core admits the call to the route's queue (enqueued), the extension answers with a generated tone, the owner offers it to the oldest Ready agent and bridges it (MT-CTI-17 FASE 3); flag OFF (default): the leg ends unanswered in the pen. ai_agent names a voice AI agent of the account (core#1547, ADR #200): ai_agent_id (a live agent of this account) and queue_id (the active queue the call falls back to) are REQUIRED, or the write is 400 invalid; ai_enabled reads true and recording false, both derived from the type, not chosen. The admission is decided before the call is answered (core#1548, ADR #200 decision 3): with the tenant's inbound_ai_enabled flag off (the default) or any of its checks failing, the call is treated as a call to the queue_id queue and is not answered by the AI; the reason is recorded in the call's CDR evidence (compliance_snapshot.inbound_ai). Nothing hands a call to the agent yet (the fork is a later delivery of ADR #200), so a call to this number is not answered by the AI. |
inbound_routes[].dest_ref | string | null | no | |
inbound_routes[].queue_id | string,null (uuid) | no | The queue of a queue route (core#1347); null on any other type, and on a queue route saved without one. |
inbound_routes[].ai_agent_id | string,null (uuid) | no | The AI agent of an ai_agent route (core#1547); null on any other type. |
inbound_routes[].hours | InboundRouteHours | null | no | The route's hours (core#2530), or null when it answers at all hours. A closed message reads back as the text that was sent. |
inbound_routes[].recording | boolean | no | |
inbound_routes[].ai_enabled | boolean | no | true if and only if dest_type is ai_agent (derived, ADR #200); false on every other type. A write that names the opposite is 400 (ADR #148). Routes that held true before the flag was refused were turned off by a data migration. |
inbound_routes[].status | active · disabled | no | |
inbound_routes[].notes | string | null | no | |
inbound_routes[].created_at | string (date-time) | no | |
inbound_routes[].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"
}
}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 an inbound route
http
POST /v1/inbound-routese164 is immutable after creation; tenant_id is injected by the router.
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
e164 | string | yes | E.164 (immutable after creation). It must be a number of THIS account (one registered with /v1/dids, or a house number assigned to it, core#2414): a number that is another account's or that nobody registered is 422 unprocessable (e164 not a number of this account). A PATCH that brings a route back to active is judged the same way; a route that already exists is never re-judged. |
dest_type | agent · queue · ivr · optout · ai_agent | yes | ivr runs one of the tenant's inbound IVR menus (core#1319): dest_ref is REQUIRED and must be the id of a menu published with /v1/inbound-ivr-menus in this account and not retired, or the write is 400 invalid. Each call hears the menu's version current when it connects; FreeSWITCH answers it after the plan verdict, core plays the prompts, and the key (or, after two silent windows, the menu's default) sends it to a queue, an agent, the opt-out or a hangup. optout is the opt-out number (core#1316, 47 CFR 64.1200(b)(3)): the calling number is added to the tenant's own do-not-call list when the call connects, before anything answers and whatever the tenant's plan; with an active plan FreeSWITCH then plays a platform-composed confirmation naming the tenant's seller (or a tone) and hangs up, with a dark plan the call is refused unanswered. A caller without a usable number (hidden or invalid) is refused and nothing is recorded. An optout route is bare: a non-null dest_ref, recording: true or ai_enabled: true in the same write are refused with 400 invalid; left out, they are set to none/false. agent bridges straight to the registered agent; queue anchors the leg on FreeSWITCH. Today the production FreeSWITCH image ships no queue extensions (ADR #104 D13: the park pen and dd_queue_hold are a pending freeswitch dependency, not yet delivered), so a queue leg is hung up by FreeSWITCH as UNALLOCATED_NUMBER regardless of the flag. Once they ship: with the tenant's inbound_queue_stream_enabled flag ON and its agent_session_journal_enabled ON too (core#1354: with the seat journal OFF the call is refused unanswered, since no agent could be offered it), core admits the call to the route's queue (enqueued), the extension answers with a generated tone, the owner offers it to the oldest Ready agent and bridges it (MT-CTI-17 FASE 3); flag OFF (default): the leg ends unanswered in the pen. ai_agent names a voice AI agent of the account (core#1547, ADR #200): ai_agent_id (a live agent of this account) and queue_id (the active queue the call falls back to) are REQUIRED, or the write is 400 invalid; ai_enabled reads true and recording false, both derived from the type, not chosen. The admission is decided before the call is answered (core#1548, ADR #200 decision 3): with the tenant's inbound_ai_enabled flag off (the default) or any of its checks failing, the call is treated as a call to the queue_id queue and is not answered by the AI; the reason is recorded in the call's CDR evidence (compliance_snapshot.inbound_ai). Nothing hands a call to the agent yet (the fork is a later delivery of ADR #200), so a call to this number is not answered by the AI. |
dest_ref | string (uuid) | no | Required for agent (the agent id) and for ivr (the id of a published, non-retired inbound IVR menu of this account, else 400 invalid); a non-null value is refused on an optout route. A value that is not a canonical UUID is refused with 400 invalid (dest_ref must be a UUID, core#1338). |
queue_id | string (uuid) | no | The queue of a queue route (core#1347) or the fallback queue of an ai_agent route (core#1547): an ACTIVE queue of this account created with /v1/inbound-queues; a retired one, another account's, one that does not exist or a malformed id is 400 invalid. REQUIRED on an ai_agent route (core#1547): the queue the call falls back to, held to the same rule. Refused on any other dest_type. Optional on a queue route: without it a queue route is accepted, as before, and refuses its calls at connect. A PATCH that moves the route to another type without naming it drops it. |
ai_agent_id | string (uuid) | no | REQUIRED on an ai_agent route (core#1547, ADR #200): the id of a live AI agent of this account, created with /v1/ai/agents; a draft, paused or retired one, another account's, one that does not exist or a malformed id is 400 invalid. Refused on any other dest_type; a PATCH that moves the route to another type without naming it drops it. The agent is judged AT WRITE (a PATCH that points the route at an agent or brings it back to active): pausing it later does not touch the route, and retiring it while an active route names it is 409 on PATCH /v1/ai/agents/{id} and DELETE /v1/ai/agents/{id}. |
hours | InboundRouteHours | null | no | The route's hours (core#2530, ADR #304). Absent or null = the number answers at all hours, exactly as before; on a PATCH, null REMOVES the hours and leaving the key out keeps them. Outside the hours, or on a holiday, a NEW call is treated as the route closed names (a message or another destination); a call already admitted is never cut when the closing time passes. An optout route has no hours: sending them with dest_type: optout, or moving a route that has them to optout without hours: null in the same write, is 422. A zone that does not resolve, a malformed or repeated holiday, more than 366 holidays, a closed that is not exactly one of its two shapes, or one that names a queue, menu, agent or AI agent that is not live in this account is 422 unprocessable. The closed message is rendered to audio when the hours are saved; if it cannot be, the write is 503 prompt_render_failed and nothing is saved. A route with hours is provisioned anchored on FreeSWITCH (an agent route without recording is otherwise delivered straight to the agent and core never sees the call): after writing the hours, POST /v1/inbound-routes/{id}/provision so the SIP edge learns it. |
recording | boolean | no | true is refused on an optout route and on an ai_agent route (the AI leg is not recorded, ADR #200); omitted there, it is false. Default true on every other route. |
ai_enabled | boolean | no | Derived from the destination, not chosen (ADR #148, ADR #200): true if and only if dest_type is ai_agent. Naming true on any other destination, or false on an ai_agent route, is 400 invalid (ai_enabled is derived from the destination ...); omit it, or send the value the destination implies (default false). |
notes | string | no |
json
{
"e164": "+13125550171",
"dest_type": "queue"
}Responses
201
Created route.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_route | object (InboundRoute) | yes | |
inbound_route.id | string (uuid) | no | |
inbound_route.tenant_id | string (uuid) | no | |
inbound_route.e164 | string | no | Immutable after creation. |
inbound_route.dest_type | agent · queue · ivr · optout · ai_agent | no | ivr runs the inbound IVR menu dest_ref names (core#1319). A route saved before core#1311 without a menu is still refused at connect with CALL_REJECTED. optout is the opt-out number (core#1316): the calling number joins the tenant's own do-not-call list at connect and FreeSWITCH confirms and hangs up; such a route always reads dest_ref: null, recording: false, ai_enabled: false. agent bridges straight to the registered agent; queue anchors the leg on FreeSWITCH. Today the production FreeSWITCH image ships no queue extensions (ADR #104 D13: the park pen and dd_queue_hold are a pending freeswitch dependency, not yet delivered), so a queue leg is hung up by FreeSWITCH as UNALLOCATED_NUMBER regardless of the flag. Once they ship: with the tenant's inbound_queue_stream_enabled flag ON and its agent_session_journal_enabled ON too (core#1354: with the seat journal OFF the call is refused unanswered, since no agent could be offered it), core admits the call to the route's queue (enqueued), the extension answers with a generated tone, the owner offers it to the oldest Ready agent and bridges it (MT-CTI-17 FASE 3); flag OFF (default): the leg ends unanswered in the pen. ai_agent names a voice AI agent of the account (core#1547, ADR #200): ai_agent_id (a live agent of this account) and queue_id (the active queue the call falls back to) are REQUIRED, or the write is 400 invalid; ai_enabled reads true and recording false, both derived from the type, not chosen. The admission is decided before the call is answered (core#1548, ADR #200 decision 3): with the tenant's inbound_ai_enabled flag off (the default) or any of its checks failing, the call is treated as a call to the queue_id queue and is not answered by the AI; the reason is recorded in the call's CDR evidence (compliance_snapshot.inbound_ai). Nothing hands a call to the agent yet (the fork is a later delivery of ADR #200), so a call to this number is not answered by the AI. |
inbound_route.dest_ref | string | null | no | |
inbound_route.queue_id | string,null (uuid) | no | The queue of a queue route (core#1347); null on any other type, and on a queue route saved without one. |
inbound_route.ai_agent_id | string,null (uuid) | no | The AI agent of an ai_agent route (core#1547); null on any other type. |
inbound_route.hours | InboundRouteHours | null | no | The route's hours (core#2530), or null when it answers at all hours. A closed message reads back as the text that was sent. |
inbound_route.recording | boolean | no | |
inbound_route.ai_enabled | boolean | no | true if and only if dest_type is ai_agent (derived, ADR #200); false on every other type. A write that names the opposite is 400 (ADR #148). Routes that held true before the flag was refused were turned off by a data migration. |
inbound_route.status | active · disabled | no | |
inbound_route.notes | string | null | no | |
inbound_route.created_at | string (date-time) | no | |
inbound_route.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"
}
}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
The closed message of the hours could not be rendered to audio (prompt_render_failed); the route was not saved. The request is right and may succeed once the text-to-speech answers.
Fetch one inbound route
http
GET /v1/inbound-routes/{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 route.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_route | object (InboundRoute) | yes | |
inbound_route.id | string (uuid) | no | |
inbound_route.tenant_id | string (uuid) | no | |
inbound_route.e164 | string | no | Immutable after creation. |
inbound_route.dest_type | agent · queue · ivr · optout · ai_agent | no | ivr runs the inbound IVR menu dest_ref names (core#1319). A route saved before core#1311 without a menu is still refused at connect with CALL_REJECTED. optout is the opt-out number (core#1316): the calling number joins the tenant's own do-not-call list at connect and FreeSWITCH confirms and hangs up; such a route always reads dest_ref: null, recording: false, ai_enabled: false. agent bridges straight to the registered agent; queue anchors the leg on FreeSWITCH. Today the production FreeSWITCH image ships no queue extensions (ADR #104 D13: the park pen and dd_queue_hold are a pending freeswitch dependency, not yet delivered), so a queue leg is hung up by FreeSWITCH as UNALLOCATED_NUMBER regardless of the flag. Once they ship: with the tenant's inbound_queue_stream_enabled flag ON and its agent_session_journal_enabled ON too (core#1354: with the seat journal OFF the call is refused unanswered, since no agent could be offered it), core admits the call to the route's queue (enqueued), the extension answers with a generated tone, the owner offers it to the oldest Ready agent and bridges it (MT-CTI-17 FASE 3); flag OFF (default): the leg ends unanswered in the pen. ai_agent names a voice AI agent of the account (core#1547, ADR #200): ai_agent_id (a live agent of this account) and queue_id (the active queue the call falls back to) are REQUIRED, or the write is 400 invalid; ai_enabled reads true and recording false, both derived from the type, not chosen. The admission is decided before the call is answered (core#1548, ADR #200 decision 3): with the tenant's inbound_ai_enabled flag off (the default) or any of its checks failing, the call is treated as a call to the queue_id queue and is not answered by the AI; the reason is recorded in the call's CDR evidence (compliance_snapshot.inbound_ai). Nothing hands a call to the agent yet (the fork is a later delivery of ADR #200), so a call to this number is not answered by the AI. |
inbound_route.dest_ref | string | null | no | |
inbound_route.queue_id | string,null (uuid) | no | The queue of a queue route (core#1347); null on any other type, and on a queue route saved without one. |
inbound_route.ai_agent_id | string,null (uuid) | no | The AI agent of an ai_agent route (core#1547); null on any other type. |
inbound_route.hours | InboundRouteHours | null | no | The route's hours (core#2530), or null when it answers at all hours. A closed message reads back as the text that was sent. |
inbound_route.recording | boolean | no | |
inbound_route.ai_enabled | boolean | no | true if and only if dest_type is ai_agent (derived, ADR #200); false on every other type. A write that names the opposite is 400 (ADR #148). Routes that held true before the flag was refused were turned off by a data migration. |
inbound_route.status | active · disabled | no | |
inbound_route.notes | string | null | no | |
inbound_route.created_at | string (date-time) | no | |
inbound_route.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 an inbound route (e164 immutable)
http
PATCH /v1/inbound-routes/{id}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 |
|---|---|---|---|
dest_type | agent · queue · ivr · optout · ai_agent | no | ivr runs one of the tenant's inbound IVR menus (core#1319): dest_ref is REQUIRED and must be the id of a menu published with /v1/inbound-ivr-menus in this account and not retired, or the write is 400 invalid. Each call hears the menu's version current when it connects; FreeSWITCH answers it after the plan verdict, core plays the prompts, and the key (or, after two silent windows, the menu's default) sends it to a queue, an agent, the opt-out or a hangup. optout is the opt-out number (core#1316, 47 CFR 64.1200(b)(3)): the calling number is added to the tenant's own do-not-call list when the call connects, before anything answers and whatever the tenant's plan; with an active plan FreeSWITCH then plays a platform-composed confirmation naming the tenant's seller (or a tone) and hangs up, with a dark plan the call is refused unanswered. A caller without a usable number (hidden or invalid) is refused and nothing is recorded. An optout route is bare: a non-null dest_ref, recording: true or ai_enabled: true in the same write are refused with 400 invalid; left out, they are set to none/false. agent bridges straight to the registered agent; queue anchors the leg on FreeSWITCH. Today the production FreeSWITCH image ships no queue extensions (ADR #104 D13: the park pen and dd_queue_hold are a pending freeswitch dependency, not yet delivered), so a queue leg is hung up by FreeSWITCH as UNALLOCATED_NUMBER regardless of the flag. Once they ship: with the tenant's inbound_queue_stream_enabled flag ON and its agent_session_journal_enabled ON too (core#1354: with the seat journal OFF the call is refused unanswered, since no agent could be offered it), core admits the call to the route's queue (enqueued), the extension answers with a generated tone, the owner offers it to the oldest Ready agent and bridges it (MT-CTI-17 FASE 3); flag OFF (default): the leg ends unanswered in the pen. ai_agent names a voice AI agent of the account (core#1547, ADR #200): ai_agent_id (a live agent of this account) and queue_id (the active queue the call falls back to) are REQUIRED, or the write is 400 invalid; ai_enabled reads true and recording false, both derived from the type, not chosen. The admission is decided before the call is answered (core#1548, ADR #200 decision 3): with the tenant's inbound_ai_enabled flag off (the default) or any of its checks failing, the call is treated as a call to the queue_id queue and is not answered by the AI; the reason is recorded in the call's CDR evidence (compliance_snapshot.inbound_ai). Nothing hands a call to the agent yet (the fork is a later delivery of ADR #200), so a call to this number is not answered by the AI. |
dest_ref | string (uuid) | no | Required for agent (the agent id) and for ivr (the id of a published, non-retired inbound IVR menu of this account, else 400 invalid); a non-null value is refused on an optout route. A value that is not a canonical UUID is refused with 400 invalid (dest_ref must be a UUID, core#1338). |
queue_id | string (uuid) | no | The queue of a queue route (core#1347) or the fallback queue of an ai_agent route (core#1547): an ACTIVE queue of this account created with /v1/inbound-queues; a retired one, another account's, one that does not exist or a malformed id is 400 invalid. REQUIRED on an ai_agent route (core#1547): the queue the call falls back to, held to the same rule. Refused on any other dest_type. Optional on a queue route: without it a queue route is accepted, as before, and refuses its calls at connect. A PATCH that moves the route to another type without naming it drops it. |
ai_agent_id | string (uuid) | no | REQUIRED on an ai_agent route (core#1547, ADR #200): the id of a live AI agent of this account, created with /v1/ai/agents; a draft, paused or retired one, another account's, one that does not exist or a malformed id is 400 invalid. Refused on any other dest_type; a PATCH that moves the route to another type without naming it drops it. The agent is judged AT WRITE (a PATCH that points the route at an agent or brings it back to active): pausing it later does not touch the route, and retiring it while an active route names it is 409 on PATCH /v1/ai/agents/{id} and DELETE /v1/ai/agents/{id}. |
hours | InboundRouteHours | null | no | The route's hours (core#2530, ADR #304). Absent or null = the number answers at all hours, exactly as before; on a PATCH, null REMOVES the hours and leaving the key out keeps them. Outside the hours, or on a holiday, a NEW call is treated as the route closed names (a message or another destination); a call already admitted is never cut when the closing time passes. An optout route has no hours: sending them with dest_type: optout, or moving a route that has them to optout without hours: null in the same write, is 422. A zone that does not resolve, a malformed or repeated holiday, more than 366 holidays, a closed that is not exactly one of its two shapes, or one that names a queue, menu, agent or AI agent that is not live in this account is 422 unprocessable. The closed message is rendered to audio when the hours are saved; if it cannot be, the write is 503 prompt_render_failed and nothing is saved. A route with hours is provisioned anchored on FreeSWITCH (an agent route without recording is otherwise delivered straight to the agent and core never sees the call): after writing the hours, POST /v1/inbound-routes/{id}/provision so the SIP edge learns it. |
recording | boolean | no | true is refused on an optout route and on an ai_agent route (the AI leg is not recorded, ADR #200); omitted there, it is false. Default true on every other route. |
ai_enabled | boolean | no | Derived from the destination, not chosen (ADR #148, ADR #200): true if and only if dest_type is ai_agent. Naming true on any other destination, or false on an ai_agent route, is 400 invalid (ai_enabled is derived from the destination ...); omit it, or send the value the destination implies (default false). |
status | active · disabled | no | |
notes | string | no |
json
{
"recording": false
}Responses
200
Updated route.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_route | object (InboundRoute) | yes | |
inbound_route.id | string (uuid) | no | |
inbound_route.tenant_id | string (uuid) | no | |
inbound_route.e164 | string | no | Immutable after creation. |
inbound_route.dest_type | agent · queue · ivr · optout · ai_agent | no | ivr runs the inbound IVR menu dest_ref names (core#1319). A route saved before core#1311 without a menu is still refused at connect with CALL_REJECTED. optout is the opt-out number (core#1316): the calling number joins the tenant's own do-not-call list at connect and FreeSWITCH confirms and hangs up; such a route always reads dest_ref: null, recording: false, ai_enabled: false. agent bridges straight to the registered agent; queue anchors the leg on FreeSWITCH. Today the production FreeSWITCH image ships no queue extensions (ADR #104 D13: the park pen and dd_queue_hold are a pending freeswitch dependency, not yet delivered), so a queue leg is hung up by FreeSWITCH as UNALLOCATED_NUMBER regardless of the flag. Once they ship: with the tenant's inbound_queue_stream_enabled flag ON and its agent_session_journal_enabled ON too (core#1354: with the seat journal OFF the call is refused unanswered, since no agent could be offered it), core admits the call to the route's queue (enqueued), the extension answers with a generated tone, the owner offers it to the oldest Ready agent and bridges it (MT-CTI-17 FASE 3); flag OFF (default): the leg ends unanswered in the pen. ai_agent names a voice AI agent of the account (core#1547, ADR #200): ai_agent_id (a live agent of this account) and queue_id (the active queue the call falls back to) are REQUIRED, or the write is 400 invalid; ai_enabled reads true and recording false, both derived from the type, not chosen. The admission is decided before the call is answered (core#1548, ADR #200 decision 3): with the tenant's inbound_ai_enabled flag off (the default) or any of its checks failing, the call is treated as a call to the queue_id queue and is not answered by the AI; the reason is recorded in the call's CDR evidence (compliance_snapshot.inbound_ai). Nothing hands a call to the agent yet (the fork is a later delivery of ADR #200), so a call to this number is not answered by the AI. |
inbound_route.dest_ref | string | null | no | |
inbound_route.queue_id | string,null (uuid) | no | The queue of a queue route (core#1347); null on any other type, and on a queue route saved without one. |
inbound_route.ai_agent_id | string,null (uuid) | no | The AI agent of an ai_agent route (core#1547); null on any other type. |
inbound_route.hours | InboundRouteHours | null | no | The route's hours (core#2530), or null when it answers at all hours. A closed message reads back as the text that was sent. |
inbound_route.recording | boolean | no | |
inbound_route.ai_enabled | boolean | no | true if and only if dest_type is ai_agent (derived, ADR #200); false on every other type. A write that names the opposite is 400 (ADR #148). Routes that held true before the flag was refused were turned off by a data migration. |
inbound_route.status | active · disabled | no | |
inbound_route.notes | string | null | no | |
inbound_route.created_at | string (date-time) | no | |
inbound_route.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"
}
}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
The closed message of the hours could not be rendered to audio (prompt_render_failed); the route was not saved. The request is right and may succeed once the text-to-speech answers.
Push the route to the SIP edge
http
POST /v1/inbound-routes/{id}/provision409 when the route is disabled; 422 when the destination agent cannot be resolved for this sede or has no SIP extension (seat) to deliver to; 200 {"status": "sbc_disabled"} when no KAMAILIO_RPC_URL is configured.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
Provisioned (or SBC disabled in this environment).
| Field | Type | Required | Description |
|---|---|---|---|
status | provisioned · sbc_disabled | yes | sbc_disabled = no KAMAILIO_RPC_URL in this environment; the row's intent is recorded, not pushed. |
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"
}
}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"
}
}502 — SbcError
The SIP edge rejected the provisioning request.
json
{
"error": {
"code": "sbc_error",
"message": "the SIP edge rejected the provisioning request"
}
}Disable an inbound route
http
POST /v1/inbound-routes/{id}/disableParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The disabled route.
| Field | Type | Required | Description |
|---|---|---|---|
inbound_route | object (InboundRoute) | yes | |
inbound_route.id | string (uuid) | no | |
inbound_route.tenant_id | string (uuid) | no | |
inbound_route.e164 | string | no | Immutable after creation. |
inbound_route.dest_type | agent · queue · ivr · optout · ai_agent | no | ivr runs the inbound IVR menu dest_ref names (core#1319). A route saved before core#1311 without a menu is still refused at connect with CALL_REJECTED. optout is the opt-out number (core#1316): the calling number joins the tenant's own do-not-call list at connect and FreeSWITCH confirms and hangs up; such a route always reads dest_ref: null, recording: false, ai_enabled: false. agent bridges straight to the registered agent; queue anchors the leg on FreeSWITCH. Today the production FreeSWITCH image ships no queue extensions (ADR #104 D13: the park pen and dd_queue_hold are a pending freeswitch dependency, not yet delivered), so a queue leg is hung up by FreeSWITCH as UNALLOCATED_NUMBER regardless of the flag. Once they ship: with the tenant's inbound_queue_stream_enabled flag ON and its agent_session_journal_enabled ON too (core#1354: with the seat journal OFF the call is refused unanswered, since no agent could be offered it), core admits the call to the route's queue (enqueued), the extension answers with a generated tone, the owner offers it to the oldest Ready agent and bridges it (MT-CTI-17 FASE 3); flag OFF (default): the leg ends unanswered in the pen. ai_agent names a voice AI agent of the account (core#1547, ADR #200): ai_agent_id (a live agent of this account) and queue_id (the active queue the call falls back to) are REQUIRED, or the write is 400 invalid; ai_enabled reads true and recording false, both derived from the type, not chosen. The admission is decided before the call is answered (core#1548, ADR #200 decision 3): with the tenant's inbound_ai_enabled flag off (the default) or any of its checks failing, the call is treated as a call to the queue_id queue and is not answered by the AI; the reason is recorded in the call's CDR evidence (compliance_snapshot.inbound_ai). Nothing hands a call to the agent yet (the fork is a later delivery of ADR #200), so a call to this number is not answered by the AI. |
inbound_route.dest_ref | string | null | no | |
inbound_route.queue_id | string,null (uuid) | no | The queue of a queue route (core#1347); null on any other type, and on a queue route saved without one. |
inbound_route.ai_agent_id | string,null (uuid) | no | The AI agent of an ai_agent route (core#1547); null on any other type. |
inbound_route.hours | InboundRouteHours | null | no | The route's hours (core#2530), or null when it answers at all hours. A closed message reads back as the text that was sent. |
inbound_route.recording | boolean | no | |
inbound_route.ai_enabled | boolean | no | true if and only if dest_type is ai_agent (derived, ADR #200); false on every other type. A write that names the opposite is 400 (ADR #148). Routes that held true before the flag was refused were turned off by a data migration. |
inbound_route.status | active · disabled | no | |
inbound_route.notes | string | null | no | |
inbound_route.created_at | string (date-time) | no | |
inbound_route.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"
}
}