Skip to content

Inbound routes ​

Generated from DialerDigital/core commit 0fc18008b3c8 — spec openapi/dialer-v1.yaml v0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.

Español

Inbound DID routing + SBC provisioning.

Endpoints ​

MethodPathSummary
GET/v1/inbound-routesList inbound routes
POST/v1/inbound-routesCreate 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}/provisionPush the route to the SIP edge
POST/v1/inbound-routes/{id}/disableDisable an inbound route

List inbound routes ​

http
GET /v1/inbound-routes

Responses

200 ​

The tenant's inbound routes.

FieldTypeRequiredDescription
inbound_routesarray of InboundRouteyes
inbound_routes[].idstring (uuid)no
inbound_routes[].tenant_idstring (uuid)no
inbound_routes[].e164stringnoImmutable after creation.
inbound_routes[].dest_typeagent · queue · ivr · optout · ai_agentnoivr 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_refstring | nullno
inbound_routes[].queue_idstring,null (uuid)noThe queue of a queue route (core#1347); null on any other type, and on a queue route saved without one.
inbound_routes[].ai_agent_idstring,null (uuid)noThe AI agent of an ai_agent route (core#1547); null on any other type.
inbound_routes[].hoursInboundRouteHours | nullnoThe 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[].recordingbooleanno
inbound_routes[].ai_enabledbooleannotrue 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[].statusactive · disabledno
inbound_routes[].notesstring | nullno
inbound_routes[].created_atstring (date-time)no
inbound_routes[].updated_atstring (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-routes

e164 is immutable after creation; tenant_id is injected by the router.

Request body (JSON, required)

FieldTypeRequiredDescription
e164stringyesE.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_typeagent · queue · ivr · optout · ai_agentyesivr 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_refstring (uuid)noRequired 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_idstring (uuid)noThe 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_idstring (uuid)noREQUIRED 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}.
hoursInboundRouteHours | nullnoThe 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.
recordingbooleannotrue 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_enabledbooleannoDerived 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).
notesstringno
json
{
  "e164": "+13125550171",
  "dest_type": "queue"
}

Responses

201 ​

Created route.

FieldTypeRequiredDescription
inbound_routeobject (InboundRoute)yes
inbound_route.idstring (uuid)no
inbound_route.tenant_idstring (uuid)no
inbound_route.e164stringnoImmutable after creation.
inbound_route.dest_typeagent · queue · ivr · optout · ai_agentnoivr 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_refstring | nullno
inbound_route.queue_idstring,null (uuid)noThe queue of a queue route (core#1347); null on any other type, and on a queue route saved without one.
inbound_route.ai_agent_idstring,null (uuid)noThe AI agent of an ai_agent route (core#1547); null on any other type.
inbound_route.hoursInboundRouteHours | nullnoThe 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.recordingbooleanno
inbound_route.ai_enabledbooleannotrue 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.statusactive · disabledno
inbound_route.notesstring | nullno
inbound_route.created_atstring (date-time)no
inbound_route.updated_atstring (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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The route.

FieldTypeRequiredDescription
inbound_routeobject (InboundRoute)yes
inbound_route.idstring (uuid)no
inbound_route.tenant_idstring (uuid)no
inbound_route.e164stringnoImmutable after creation.
inbound_route.dest_typeagent · queue · ivr · optout · ai_agentnoivr 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_refstring | nullno
inbound_route.queue_idstring,null (uuid)noThe queue of a queue route (core#1347); null on any other type, and on a queue route saved without one.
inbound_route.ai_agent_idstring,null (uuid)noThe AI agent of an ai_agent route (core#1547); null on any other type.
inbound_route.hoursInboundRouteHours | nullnoThe 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.recordingbooleanno
inbound_route.ai_enabledbooleannotrue 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.statusactive · disabledno
inbound_route.notesstring | nullno
inbound_route.created_atstring (date-time)no
inbound_route.updated_atstring (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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Request body (JSON, required)

FieldTypeRequiredDescription
dest_typeagent · queue · ivr · optout · ai_agentnoivr 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_refstring (uuid)noRequired 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_idstring (uuid)noThe 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_idstring (uuid)noREQUIRED 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}.
hoursInboundRouteHours | nullnoThe 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.
recordingbooleannotrue 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_enabledbooleannoDerived 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).
statusactive · disabledno
notesstringno
json
{
  "recording": false
}

Responses

200 ​

Updated route.

FieldTypeRequiredDescription
inbound_routeobject (InboundRoute)yes
inbound_route.idstring (uuid)no
inbound_route.tenant_idstring (uuid)no
inbound_route.e164stringnoImmutable after creation.
inbound_route.dest_typeagent · queue · ivr · optout · ai_agentnoivr 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_refstring | nullno
inbound_route.queue_idstring,null (uuid)noThe queue of a queue route (core#1347); null on any other type, and on a queue route saved without one.
inbound_route.ai_agent_idstring,null (uuid)noThe AI agent of an ai_agent route (core#1547); null on any other type.
inbound_route.hoursInboundRouteHours | nullnoThe 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.recordingbooleanno
inbound_route.ai_enabledbooleannotrue 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.statusactive · disabledno
inbound_route.notesstring | nullno
inbound_route.created_atstring (date-time)no
inbound_route.updated_atstring (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}/provision

409 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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

Provisioned (or SBC disabled in this environment).

FieldTypeRequiredDescription
statusprovisioned · sbc_disabledyessbc_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}/disable

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The disabled route.

FieldTypeRequiredDescription
inbound_routeobject (InboundRoute)yes
inbound_route.idstring (uuid)no
inbound_route.tenant_idstring (uuid)no
inbound_route.e164stringnoImmutable after creation.
inbound_route.dest_typeagent · queue · ivr · optout · ai_agentnoivr 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_refstring | nullno
inbound_route.queue_idstring,null (uuid)noThe queue of a queue route (core#1347); null on any other type, and on a queue route saved without one.
inbound_route.ai_agent_idstring,null (uuid)noThe AI agent of an ai_agent route (core#1547); null on any other type.
inbound_route.hoursInboundRouteHours | nullnoThe 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.recordingbooleanno
inbound_route.ai_enabledbooleannotrue 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.statusactive · disabledno
inbound_route.notesstring | nullno
inbound_route.created_atstring (date-time)no
inbound_route.updated_atstring (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"
  }
}

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.