Channels
The phone numbers assigned to your application, and where each one sends its inbound calls.
The phone numbers assigned to your application, and where each one sends its inbound calls.
const { items } = await connect.channels.list();
// Claim a number for a workspace, then point it somewhere.
const channel = await connect.channels.claim({ workspaceId });
await connect.channels.setRouting(channel.id, { agentId });Claiming allocates a billable resource. A number carries a monthly rental from the moment it is claimed, attributed to the workspace that holds it - it is not a free reservation, and channels:claim is marked sensitive for that reason.
Routing decides what answers. A channel with no routing rings nothing, which is a real state and not an error - it is what a number looks like between being claimed and being configured.
A channel is the routing abstraction, not the number itself: the address is one property of it, and the same model covers kinds that are not phone numbers at all.
Endpoints
GET /api/v1/application/channels
List your assigned channels
Scope channels:read · Rate limit 30 requests / min per IP, 60 requests / min per API key
Every phone number a Connect operator has assigned to your application, across all of your subscribing workspaces.
Channel inventory is platform-owned: a number is a regulated asset bought under Connect's carrier account and reassignable between workspaces, so your application never provisions or releases one - it reads what was assigned to it.
The application is resolved from your API key and is never a parameter, so there is no request that lists another partner's channels. workspaceId only narrows within what you already hold.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
workspaceId | query | no | Narrow to one workspace. |
cursor | query | no | From a previous response's nextCursor. |
limit | query | no | 1-100, default 50. |
Responses
| Status | Meaning |
|---|---|
200 | Channels assigned to your application |
400 | validation_error - a limit outside 1-100 |
403 | forbidden - key unknown or missing the channels:read scope |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
items | array | yes | |
nextCursor | any | yes | |
hasMore | boolean | yes |
POST /api/v1/application/channels/claim
Claim a channel for a workspace
Scope channels:claim · Rate limit 30 requests / min per IP, 5 requests / min per API key
Take a number out of Connect's inventory and bind it to one of your subscribing workspaces.
You do not pick the number. You say which workspace and which kind; Connect picks from the pool. That is deliberate and not a limitation to work around: the inventory, the carrier rate and the provider reference are Connect's, and no endpoint exposes them - which is also why the response carries no telephony.
A workspace may hold exactly one number, in total, across every application it uses. A second claim answers 409. The number is then fixed for the life of the engagement: there is no re-claim and no swap. It leaves the workspace when you release it (POST /channels/{channelId}/release) or a Connect operator unassigns it, after which you may claim again and may well get a different number.
If the pool is empty the call answers 409 and Connect is notified. Retry rather than treating it as a permanent failure.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | yes | |
kind | voice | meeting | whatsapp | no |
Responses
| Status | Meaning |
|---|---|
201 | The claimed channel, without telephony details |
400 | validation_error - workspaceId missing, or an unknown kind |
403 | forbidden - key unknown or missing the channels:claim scope |
404 | not_found - no such workspace |
409 | conflict - the workspace already holds a channel of this kind, or no channel is available to claim right now |
POST /api/v1/application/channels/{channelId}/release
Release a channel back to Connect
Scope channels:claim · Rate limit 5 requests / min per API key
Give a number back. Its rental closes, its routing is cleared and it returns to Connect's inventory - the inverse of POST /channels/claim, under the same scope.
If your application answers its own calls (Application.telephony), the number is also pointed back at Connect's own carrier configuration, so it stops reaching your edge.
A channel belonging to another application, or held by a different workspace than the one you name, answers 404.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
channelId | path | yes | From GET /channels. |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | yes |
Responses
| Status | Meaning |
|---|---|
200 | The released channel, as it was in the workspace you named, with routing and handlerApplicationId now null |
400 | validation_error - workspaceId missing |
403 | forbidden - key unknown or missing the channels:claim scope |
404 | not_found - no such channel, another application's, or another workspace's |
POST /api/v1/application/telephony/calls
Place an outbound call from a handled number
Scope telephony:call · Rate limit 120 requests / min per API key, shared with hangup and browser-token
For an application that answers its own calls: ring to from a number your application handles (handlerApplicationId is your application). Connect places the call on its carrier account with your carrier-edge URLs, so when the callee answers you receive a signed telephony.carrier_event with event: "answer" and answer it with your own XML.
The answer callback names the request as params.RequestUUID - the providerRequestId returned here - so you can match it to the call you placed. Connect's do-not-call list is enforced on every dial and answers 409 do_not_call.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | yes | |
channelId | string | yes | |
to | string | yes | |
ringTimeoutSeconds | integer | yes | |
clientRef | string | no |
Responses
| Status | Meaning |
|---|---|
201 | The carrier accepted the call |
400 | validation_error - to is not E.164, or ringTimeoutSeconds outside 5-120 |
403 | forbidden - key unknown or missing the telephony:call scope |
404 | not_found - no such channel in that workspace handled by your application |
409 | conflict - the channel is suspended; do_not_call - the number asked not to be called |
502 | delivery_failed - the carrier refused the call |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
providerRequestId | string | yes |
POST /api/v1/application/telephony/calls/{providerCallUuid}/hangup
Hang up a live call
Scope telephony:call · Rate limit 120 requests / min per API key, shared with calls and browser-token
End a live call your application owns - one it dialled, one that rang a number it handles, or one a browser it issued a token to placed. Any other call, and a call that has already ended, answers 404.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
providerCallUuid | path | yes | The carrier call id - params.CallUUID on a carrier event. |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | yes |
Responses
| Status | Meaning |
|---|---|
204 | Hung up |
403 | forbidden - key unknown or missing the telephony:call scope |
404 | not_found - not a live call your application owns in that workspace |
POST /api/v1/application/telephony/browser-token
Mint a browser calling token
Scope telephony:call · Rate limit 120 requests / min per API key, shared with calls and hangup
A carrier credential a browser signs in to the phone network with, so a member can place calls from your UI. Calls it places are answered by your carrier edge: the answer event arrives with channel: null and a SIP params.From carrying the identity Connect minted.
Connect prefixes subject with a fixed 19-character tag naming your application and the workspace, so a token can never place a call another application is asked to answer. The From user part is <tag><subject>_<carrier suffix>. The workspace must hold a number your application handles.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | yes | |
subject | string | yes | |
ttlSeconds | integer | yes |
Responses
| Status | Meaning |
|---|---|
200 | The token and when it expires |
400 | validation_error - subject is not 1-48 letters and digits, or ttlSeconds outside 60-3600 |
403 | forbidden - key unknown or missing the telephony:call scope |
404 | not_found - the workspace holds no number your application handles |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
token | string | yes | |
expiresAt | string | yes |
POST /api/v1/application/telephony/recordings/{recordingId}/archive
Archive a call recording into Drive
Scope telephony:call · Rate limit 60 requests / min per API key
Move a carrier recording - from a recording carrier event - into the workspace's Drive, as a file your application uploaded, then delete it from the carrier. Idempotent on recordingId: a second call answers the first archive.
recordingUrl must be the carrier's own URL for that recording. The file counts against the workspace's Drive allowance (409 quota_exceeded when full) and is listed by GET /drive/files. Pass the returned fileId to POST /ai/call-analysis for a transcript.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
recordingId | path | yes | The carrier's recording id - params.RecordingID on a recording event. |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
workspaceId | string | yes | |
callId | string | yes | |
recordingUrl | uri | yes |
Responses
| Status | Meaning |
|---|---|
200 | The archived file |
400 | validation_error - recordingUrl does not name this recording |
403 | forbidden - key unknown or missing the telephony:call scope |
404 | not_found - the workspace holds no number your application handles, or another application archived it |
409 | quota_exceeded - the workspace's Drive is full |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
fileId | string | yes | |
sizeBytes | integer | yes | |
contentType | string | yes |
PUT /api/v1/application/channels/{channelId}/routing
Set a channel's routing
Scope channels:manage · Rate limit 30 requests / min per IP, 30 requests / min per API key
Where inbound conversations on this channel should go.
destination is an arbitrary JSON object that Connect stores verbatim and never reads into. Which agent answers, business hours, overflow, fallback - all of it is yours. Connect only enforces the binding it hangs off: the channel must be assigned to the application your key resolves to, and it must be active.
Replaces the whole record rather than merging, so send the full destination each time. Capped at 16 KB serialized.
A channel belonging to another application answers 404, not 403 - confirming it exists would let the endpoint be walked to enumerate another partner's channel ids.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
channelId | path | yes | From GET /channels. |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
destination | object | yes |
Responses
| Status | Meaning |
|---|---|
200 | The stored routing record |
400 | validation_error - destination missing, not an object, or over 16 KB |
403 | forbidden - key unknown or missing the channels:manage scope; also when the channel is unassigned |
404 | not_found - no such channel, or it belongs to another application |
409 | conflict - the channel is suspended or released |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | |
routing | any | yes |
GET /api/v1/application/channels/{channelId}
Get one channel
Scope channels:read · Rate limit 30 requests / min per IP, 60 requests / min per API key
A single channel, in exactly the shape GET /channels returns - so a webhook carrying a channel id can be resolved without paging the whole list.
Withholds the same two things the list does, and for the same reason: createdBy names a Connect operator, and the carrier-side record (which carrier, which provider resource, what Connect pays per month) is Connect's cost and Connect's credential surface, not your application's.
A channel belonging to another application answers 404, not 403. Confirming it exists would make this the cheapest possible oracle for enumerating another partner's channel ids.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
channelId | path | yes | From GET /channels, or from a call record's channelId. |
Responses
| Status | Meaning |
|---|---|
200 | The channel |
403 | forbidden - key unknown or missing the channels:read scope |
404 | not_found - no such channel, or it belongs to another application |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | |
kind | voice | meeting | whatsapp | yes | |
address | string | yes | |
label | any | yes | |
workspaceId | string | yes | |
status | active | suspended | released | yes | |
routing | any | yes | |
handlerApplicationId | any | yes |
DELETE /api/v1/application/channels/{channelId}/routing
Clear a channel's routing
Scope channels:manage · Rate limit 30 requests / min per IP, 30 requests / min per API key
Takes a number out of service without giving it up. The channel stays assigned to you and stays billable; inbound calls on it are refused until you write routing again.
This is the only way to say "stop answering". Overwriting destination can only ever point the number somewhere else, and unassigning it is a Console action that returns the number to Connect's inventory - not something your application can do, and not what you want if you intend to bring the line back.
Deliberately allowed on a suspended channel, where PUT is not: making a live number stop answering must never be the operation that is unavailable.
Idempotent - clearing already-clear routing answers 200 with routing: null, so a retry after a lost response is safe.
Parameters
| Name | In | Required | Notes |
|---|---|---|---|
channelId | path | yes | From GET /channels. |
Responses
| Status | Meaning |
|---|---|
200 | The cleared routing record - routing is always null |
403 | forbidden - key unknown or missing the channels:manage scope |
404 | not_found - no such channel, or it belongs to another application |
Response body
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | |
routing | any | yes |