Appearance
Transfer destinations
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 registry of the external numbers a live call may be transferred to.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/transfer-destinations | The tenant's current external transfer destinations |
POST | /v1/transfer-destinations | Register an external transfer destination |
PATCH | /v1/transfer-destinations/{id} | Declare the line type of a registered external transfer destination |
DELETE | /v1/transfer-destinations/{id} | Retire an external transfer destination |
GET | /v1/transfer/targets | Where a live call can be transferred, as a picker needs it |
The tenant's current external transfer destinations
http
GET /v1/transfer-destinationsThe registered numbers a blind transfer may leave towards, oldest registration first. Retired destinations are not listed. Readable by a dd_ key and by any dashboard login; a widget token (ddw_) is 403 forbidden.
Responses
200
The current registrations.
| Field | Type | Required | Description |
|---|---|---|---|
transfer_destinations | array of TransferDestination | yes | |
transfer_destinations[].id | string (uuid) | yes | The registration; the id to retire it by. |
transfer_destinations[].number | string | yes | Strict E.164. |
transfer_destinations[].label | string | yes | |
transfer_destinations[].line_type | mobile · landline · voip · unknown | yes | What the tenant declares the line to be — the same values and default as a contact's line_type on import. The consent gate reads it for a transfer to the number: landline lifts the prior-express-consent requirement; unknown, mobile and voip keep it. |
transfer_destinations[].line_type_declared_at | string (date-time) | yes | When the current line_type was declared — registered_at until a PATCH changes it. |
transfer_destinations[].line_type_declared_by | string | yes | Who declared the current line_type (same shape as registered_by). |
transfer_destinations[].registered_at | string (date-time) | yes | When it was registered; the anchor the RND is queried with for this number. |
transfer_destinations[].registered_by | string | yes | The user or agent id of the credential that registered it, or tenant-api-key. |
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"
}
}Register an external transfer destination
http
POST /v1/transfer-destinationsRegisters number (strict E.164) with a label. From then on a blind transfer may target it; it still passes the whole GATE_PRE_DIAL chain. The registration instant is also the anchor the Reassigned Numbers Database is queried with for this number, and registering schedules that query (a transfer to it blocks with rnd.no_current_result.v1 until the answer arrives, in a cell with a real RND provider).
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 registered it is taken from the credential, never from the body. A number that is already a CURRENT registration of this tenant is 409 conflict; retire it first to register it again.
line_type is what the tenant declares the line to be, exactly as it declares it for a consumer it imports (same values, same default unknown). It is what the consent gate reads for a transfer to this number: landline lifts the prior-express-consent requirement, and nothing else — every other gate still runs. A blank or absent value is unknown. Change it later with PATCH.
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
number | string | yes | Strict E.164. |
label | string | yes | What the number is, for the operator (trimmed; blank is refused). |
line_type | mobile · landline · voip · unknown | no | What the tenant declares the line to be — the same values and default as a contact's line_type on import. The consent gate reads it for a transfer to the number: landline lifts the prior-express-consent requirement; unknown, mobile and voip keep it. |
json
{
"number": "+13125550170",
"label": "Legal department",
"line_type": "landline"
}Responses
201
Registered.
| Field | Type | Required | Description |
|---|---|---|---|
transfer_destination | object (TransferDestination) | yes | |
transfer_destination.id | string (uuid) | yes | The registration; the id to retire it by. |
transfer_destination.number | string | yes | Strict E.164. |
transfer_destination.label | string | yes | |
transfer_destination.line_type | mobile · landline · voip · unknown | yes | What the tenant declares the line to be — the same values and default as a contact's line_type on import. The consent gate reads it for a transfer to the number: landline lifts the prior-express-consent requirement; unknown, mobile and voip keep it. |
transfer_destination.line_type_declared_at | string (date-time) | yes | When the current line_type was declared — registered_at until a PATCH changes it. |
transfer_destination.line_type_declared_by | string | yes | Who declared the current line_type (same shape as registered_by). |
transfer_destination.registered_at | string (date-time) | yes | When it was registered; the anchor the RND is queried with for this number. |
transfer_destination.registered_by | string | yes | The user or agent id of the credential that registered it, or tenant-api-key. |
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 transfer destinations"
}
}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"
}
}Declare the line type of a registered external transfer destination
http
PATCH /v1/transfer-destinations/{id}Changes the line_type of the CURRENT registration id. The change is a new fact in the registration's append-only history, with who made it (taken from the credential) and when; the answer carries both as line_type_declared_by / line_type_declared_at. The registration itself does not move: same id, same registered_at, so the RND anchor of the number is unchanged. Declaring the value it already has writes nothing.
The next transfer to the number reads the new value. landline lifts the consent gate's prior-express-consent requirement and nothing else; unknown, mobile and voip keep it.
Only line_type is accepted (the number and the label are the registration: retire and register again to change them); any other key is 400. A value outside the list is 400. An id that is not a current registration of this tenant (unknown, retired, or another tenant's) is 404. Same roles as the registration.
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 |
|---|---|---|---|
line_type | mobile · landline · voip · unknown | yes | What the tenant declares the line to be — the same values and default as a contact's line_type on import. The consent gate reads it for a transfer to the number: landline lifts the prior-express-consent requirement; unknown, mobile and voip keep it. |
json
{
"line_type": "landline"
}Responses
200
The destination with its current line type.
| Field | Type | Required | Description |
|---|---|---|---|
transfer_destination | object (TransferDestination) | yes | |
transfer_destination.id | string (uuid) | yes | The registration; the id to retire it by. |
transfer_destination.number | string | yes | Strict E.164. |
transfer_destination.label | string | yes | |
transfer_destination.line_type | mobile · landline · voip · unknown | yes | What the tenant declares the line to be — the same values and default as a contact's line_type on import. The consent gate reads it for a transfer to the number: landline lifts the prior-express-consent requirement; unknown, mobile and voip keep it. |
transfer_destination.line_type_declared_at | string (date-time) | yes | When the current line_type was declared — registered_at until a PATCH changes it. |
transfer_destination.line_type_declared_by | string | yes | Who declared the current line_type (same shape as registered_by). |
transfer_destination.registered_at | string (date-time) | yes | When it was registered; the anchor the RND is queried with for this number. |
transfer_destination.registered_by | string | yes | The user or agent id of the credential that registered it, or tenant-api-key. |
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.
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 external transfer destination
http
DELETE /v1/transfer-destinations/{id}Retires the CURRENT registration id: a blind transfer to that number is refused again with transfer.destination_unregistered.v1. The history is append-only — the retirement is a new fact, and the answer is the destination that was retired. An id that is not a current registration of this tenant (unknown, already retired, or another tenant's) is 404. Same roles as the registration.
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 |
|---|---|---|---|
transfer_destination | object (TransferDestination) | yes | |
transfer_destination.id | string (uuid) | yes | The registration; the id to retire it by. |
transfer_destination.number | string | yes | Strict E.164. |
transfer_destination.label | string | yes | |
transfer_destination.line_type | mobile · landline · voip · unknown | yes | What the tenant declares the line to be — the same values and default as a contact's line_type on import. The consent gate reads it for a transfer to the number: landline lifts the prior-express-consent requirement; unknown, mobile and voip keep it. |
transfer_destination.line_type_declared_at | string (date-time) | yes | When the current line_type was declared — registered_at until a PATCH changes it. |
transfer_destination.line_type_declared_by | string | yes | Who declared the current line_type (same shape as registered_by). |
transfer_destination.registered_at | string (date-time) | yes | When it was registered; the anchor the RND is queried with for this number. |
transfer_destination.registered_by | string | yes | The user or agent id of the credential that registered it, or tenant-api-key. |
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"
}
}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"
}
}Where a live call can be transferred, as a picker needs it
http
GET /v1/transfer/targetsThe ONE read a transfer picker needs, and the only one of its kind a widget token (ddw_) may reach: the roster (GET /v1/agents) and the registry (GET /v1/transfer-destinations) stay 403 for it, because they answer more than a picker paints — emails, roles, credential state, the E.164 — and the widget lives inside third-party CRMs. Read-only, scoped to the caller's tenant. Three classes, each item carrying only what the picker shows and the value its transfer verb takes as destination:
agents: ACTIVE agents with asip_extension— send theid(or the extension).presenceis the seat state NOW and is informative: whether the seat is free is the transfer's own answer (422 destination_unavailable). A widget token does not see its own agent.extensions: roster extensions with no agent behind them — send thesip_extension. Always empty today: the roster has no extension apart from its agents'. The class is in the answer so a picker does not change shape when one appears.external_destinations: the tenant's CURRENT registered external destinations,idandlabelonly — never the number. Send theidtoPOST /v1/calls/{call_id}/transfer; core resolves the E.164 itself and the transfer passes every gate a transfer to that number passes. The attended transfer does not take them.
Fail-closed: if the roster, the seat state or the registry cannot be read the answer is 503 transfer_targets_unavailable, never an empty list that would read as "there is nowhere to transfer to".
Responses
200
The three classes (any of them may be empty).
| Field | Type | Required | Description |
|---|---|---|---|
agents | array of object | yes | |
agents[].id | string (uuid) | yes | The agent; a valid transfer destination. |
agents[].name | string | yes | |
agents[].sip_extension | string | yes | |
agents[].presence | offline · available · ringing · on_call · wrap_up · paused | yes | The seat state now, as in the roster; informative only. |
extensions | array of object | yes | Roster extensions with no agent behind them. Always empty today. |
extensions[].sip_extension | string | yes | A valid transfer destination. |
extensions[].label | string | yes | |
external_destinations | array of object | yes | The current registered external destinations. Never carries the number. |
external_destinations[].id | string (uuid) | yes | The registration; a valid blind-transfer destination, resolved by core to its E.164. |
external_destinations[].label | string | yes |
json
{
"agents": [
{
"id": "2f1c9a2e-6b3d-4c1e-9a7f-0d4e5b6c7a81",
"name": "Ana Ruiz",
"sip_extension": "1002",
"presence": "available"
}
],
"extensions": [],
"external_destinations": [
{
"id": "7b0e1f3a-2c4d-4e5f-8a9b-1c2d3e4f5a6b",
"label": "Legal department"
}
]
}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"
}
}503
transfer_targets_unavailable: the roster, the seat state or the registry could not be read. Retry; an empty class is never used to say this.
json
{
"error": {
"code": "transfer_targets_unavailable",
"message": "transfer targets could not be read, retry"
}
}