Route a Phone number to an Agent
PATCH /v1/phone-numbers/{phone_number_id}
| Property | Value |
|---|---|
| Operation | update_phone_number |
| Group | Phone numbers |
| Scope | telephony:write |
| Route class | control |
| Idempotency | No Idempotency-Key |
| Audit | Audited phone_number.update (mutation) |
| Released in | 1.22.0 |
| Phase 0 host | Served (the hosted api pins 1.65.0) |
Replaces the routing at the revision If-Match names and writes the Audit event phone_number.update with the Agent and the pinned version before and after, never the number (1.22.0, FR-telephony-2). A Phone number or an Agent of another Organization answers 404 not_found, so an Organization can never route a number it does not hold; an archived Agent, or a pinned version that is not published, answers 409 agent_version_unavailable. The api refuses the route to a test key with 403 forbidden, because a Phone number carries live Calls only. A Call in progress keeps its version; the next inbound Call runs the new routing.
Request
Set VOCAL_API to the base URL of your API and VOCAL_API_KEY to an API key with the telephony:write scope (Authentication and keys). Replace each placeholder, such as agt_... or <name>, with your own value.
curl -X PATCH "$VOCAL_API/v1/phone-numbers/pn_..." \
-H "Authorization: Bearer $VOCAL_API_KEY" \
-H 'If-Match: "1"' \
-H "Content-Type: application/json" \
-d '{ "agent_id": "agt_..." }'const response = await fetch(`${process.env.VOCAL_API}/v1/phone-numbers/pn_...`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.VOCAL_API_KEY}`,
'If-Match': '"1"',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"agent_id": "agt_..."
}),
});
console.log(response.status, await response.text());import os
import requests
response = requests.patch(
f"{os.environ['VOCAL_API']}/v1/phone-numbers/pn_...",
headers={
"Authorization": f"Bearer {os.environ['VOCAL_API_KEY']}",
"If-Match": '"1"',
},
json={
"agent_id": "agt_...",
},
)
print(response.status_code, response.text)Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
phone_number_id | path | PhoneNumberId | yes | The id of a Phone number: pn_ and the row's UUIDv7 in 26 Crockford base32 characters. Opaque and safe to log. |
if-match | header | string | yes | The revision the change was made against, quoted as the ETag returns it, or * to overwrite. |
Request body
Content type application/json, required: PhoneNumberUpdateRequest.
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | AgentId or null | yes | The Agent the number routes to, or null to route it to none (1.22.0). An Agent of another Organization is not found. |
agent_version | integer or null | no | The published version of the Agent that every inbound Call to the number runs, or null to run the Agent's active version as it stands when each Call is admitted. Absent means null (1.22.0). It must be a published version of agent_id. |
Responses
| Status | Content type | Shape | Error codes |
|---|---|---|---|
200 | application/json | PhoneNumber | |
400 | application/json | ErrorEnvelope | invalid_request |
401 | application/json | ErrorEnvelope | unauthenticated |
403 | application/json | ErrorEnvelope | forbidden, org_suspended |
404 | application/json | ErrorEnvelope | not_found |
409 | application/json | ErrorEnvelope | agent_version_unavailable |
412 | application/json | ErrorEnvelope | revision_mismatch |
428 | application/json | ErrorEnvelope | precondition_required |
429 | application/json | ErrorEnvelope | rate_limited |
500 | application/json | ErrorEnvelope | internal_error |
Fields of the 200 answer
The shape is PhoneNumber.
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | AgentId or null | yes | The Agent that inbound Calls to the number run (1.22.0). Null when the number routes to no Agent: every inbound Call to it is then refused and recorded with the ended reason refused_no_route. |
agent_version | integer or null | yes | The published version of the Agent that every inbound Call to the number runs, or null to run the Agent's active version as it stands when each Call is admitted. (1.22.0) |
capabilities | array of PhoneNumberCapability | yes | What the number may carry, each capability once (1.22.0). |
created_at | Timestamp | yes | |
display | string | yes | The number masked: the country calling code, a space-separated elision and the last four digits, such as +1 ... 2486, the form of a Call's destination_hint (1.22.0). Show it wherever the full number is not needed. |
e164 | string | yes | The full number in E.164 form (1.22.0). Only the routes that need telephony:read answer a Phone number, so only a holder of that permission receives it. It is never logged and never written to an Audit event. |
id | PhoneNumberId | yes | |
inbound_media_encryption | SipMediaEncryption or null | no | The media encryption the inbound LiveKit SIP trunk holds now (1.34.0), read-only: an operator changes it by provisioning again, never through the api. Null when the api could not read the trunk; a reader treats its absence as null, as from an api before 1.34.0. |
object | "phone_number" | yes | |
outbound_media_encryption | SipMediaEncryption or null | no | The media encryption the number's outbound trunk holds now (1.34.0), read-only. Null when the number has no outbound trunk, which a number without the outbound capability does not, or when the api could not read it; a reader treats its absence as null. |
outbound_transport | SipTransport or null | no | The SIP transport the number's outbound trunk holds now (1.34.0), read-only; null as outbound_media_encryption is. The inbound trunk has none, because the carrier chooses it. |
revision | integer | yes | Counts writes to the Phone number (1.22.0); returned as the ETag and required in the If-Match of the update. |
sip_trunk_id | SipTrunkId | yes | The SIP trunk the number is reached on (1.22.0). |
status | PhoneNumberStatus | yes | |
updated_at | Timestamp | yes |