Handset API (2026-08-11)

Download OpenAPI specification:

Embedded business communications for your platform.

Handset lets your SaaS product provision phone numbers, send compliant two-way SMS, and handle inbound calls (routing, voicemail-to-text, recording) for each of your customers — via API, under your brand.

Object hierarchy

  • Account — you, the platform. Implicit in your API key.
  • Tenant — one of your customers (e.g. "Bob's Plumbing"). Almost every resource belongs to a tenant.
  • Numbers, campaigns, conversations, calls, and usage all hang off tenants.

Conventions

  • IDs are prefixed and opaque: tnt_…, num_…, msg_…, call_….
  • All timestamps are RFC 3339 UTC.
  • List endpoints use cursor pagination (limit, after → next_cursor).
  • Mutating requests accept an Idempotency-Key header; retries with the same key return the original result.
  • Errors carry a machine code, a human message, and a docs_url.

Rate limits

Each API key may sustain 20 requests/second with bursts up to 60. Beyond that, requests return 429 with code rate_limited and a Retry-After header — back off for that many seconds and retry. Limits are per key, so busy tenants can be isolated with per-tenant keys.

Tenants

Your customers — the businesses whose communications you embed.

Create a tenant

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Retries with the same key within 24 h return the original response instead of repeating the action.

Request Body schema: application/json
required
name
required
string

Display name, e.g. the business name.

external_ref
string or null

Your own identifier for this customer.

timezone
string
Default: "America/Phoenix"

IANA timezone; used for business-hours routing.

object (Metadata) <= 20 properties

Your own key–value data, returned unchanged on the object and its events.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "external_ref": "string",
  • "timezone": "America/Phoenix",
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "name": "string",
  • "external_ref": "string",
  • "timezone": "America/Phoenix",
  • "metadata": {
    },
  • "id": "tnt_01j8x2z",
  • "created_at": "2019-08-24T14:15:22Z"
}

List tenants

Authorizations:
apiKey
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

external_ref
string

Filter by your own identifier for the tenant.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a tenant

Authorizations:
apiKey
path Parameters
tenant_id
required
string

Responses

Response samples

Content type
application/json
{
  • "name": "string",
  • "external_ref": "string",
  • "timezone": "America/Phoenix",
  • "metadata": {
    },
  • "id": "tnt_01j8x2z",
  • "created_at": "2019-08-24T14:15:22Z"
}

Update a tenant

Authorizations:
apiKey
path Parameters
tenant_id
required
string
Request Body schema: application/json
required
name
string
external_ref
string or null
timezone
string
object (Metadata) <= 20 properties

Your own key–value data, returned unchanged on the object and its events.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "external_ref": "string",
  • "timezone": "string",
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "name": "string",
  • "external_ref": "string",
  • "timezone": "America/Phoenix",
  • "metadata": {
    },
  • "id": "tnt_01j8x2z",
  • "created_at": "2019-08-24T14:15:22Z"
}

Delete a tenant

Releases the tenant's phone numbers and deactivates its campaigns. Message and call history is retained per your data-retention settings.

Authorizations:
apiKey
path Parameters
tenant_id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Phone Numbers

Search, purchase, configure, and release numbers per tenant.

Search purchasable numbers

Authorizations:
apiKey
query Parameters
area_code
string^[0-9]{3}$
locality
string

City name, e.g. Phoenix.

contains
string

Digit pattern the number should contain.

limit
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Purchase a number for a tenant

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Retries with the same key within 24 h return the original response instead of repeating the action.

Request Body schema: application/json
required
tenant_id
required
string (TenantIdField)

The tenant this resource belongs to.

phone_number
required
string

E.164 number from a search result.

routing_config_id
string or null

Routing to apply to inbound calls on this number.

campaign_id
string or null

10DLC campaign to attach. Outbound SMS is blocked until the number is attached to an approved campaign.

Responses

Request samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "phone_number": "+16025550134",
  • "routing_config_id": "string",
  • "campaign_id": "string"
}

Response samples

Content type
application/json
{
  • "id": "num_01j8x31",
  • "tenant_id": "tnt_01j8x2z",
  • "phone_number": "string",
  • "status": "active",
  • "capabilities": [
    ],
  • "routing_config_id": "string",
  • "campaign_id": "string",
  • "e911_address_id": "string",
  • "messaging_ready": true,
  • "created_at": "2019-08-24T14:15:22Z"
}

List phone numbers

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a phone number

Authorizations:
apiKey
path Parameters
number_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "num_01j8x31",
  • "tenant_id": "tnt_01j8x2z",
  • "phone_number": "string",
  • "status": "active",
  • "capabilities": [
    ],
  • "routing_config_id": "string",
  • "campaign_id": "string",
  • "e911_address_id": "string",
  • "messaging_ready": true,
  • "created_at": "2019-08-24T14:15:22Z"
}

Update routing, campaign, or E911 for a number

Authorizations:
apiKey
path Parameters
number_id
required
string
Request Body schema: application/json
required
routing_config_id
string or null
campaign_id
string or null
e911_address_id
string or null

Responses

Request samples

Content type
application/json
{
  • "routing_config_id": "string",
  • "campaign_id": "string",
  • "e911_address_id": "string"
}

Response samples

Content type
application/json
{
  • "id": "num_01j8x31",
  • "tenant_id": "tnt_01j8x2z",
  • "phone_number": "string",
  • "status": "active",
  • "capabilities": [
    ],
  • "routing_config_id": "string",
  • "campaign_id": "string",
  • "e911_address_id": "string",
  • "messaging_ready": true,
  • "created_at": "2019-08-24T14:15:22Z"
}

Release a phone number

Releases the number back to inventory. Irreversible.

Authorizations:
apiKey
path Parameters
number_id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Messaging

Two-way SMS/MMS with conversation threading and opt-out enforcement.

Send an SMS/MMS

Sends from a tenant-owned number. Fails with campaign_not_approved if the number lacks an approved 10DLC campaign, and with recipient_opted_out if the recipient previously sent STOP.

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Retries with the same key within 24 h return the original response instead of repeating the action.

Request Body schema: application/json
required
from
required
string

A tenant number ID (num_…) or its E.164.

to
required
string

Destination in E.164.

body
string <= 1600 characters
media_urls
Array of strings <uri> <= 10 items [ items <uri > ]

Attach up to 10 publicly reachable https:// URLs (images, PDFs, vCards — carrier limits apply, ~1 MB total is safe). Any media makes the message an MMS: billed per message instead of per segment, and the sending number must be MMS-capable. The carrier fetches each URL once at send time.

object (Metadata) <= 20 properties

Your own key–value data, returned unchanged on the object and its events.

Responses

Request samples

Content type
application/json
{
  • "from": "string",
  • "to": "+14805550199",
  • "body": "string",
  • "media_urls": [],
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "id": "msg_01j8x3a",
  • "tenant_id": "tnt_01j8x2z",
  • "conversation_id": "string",
  • "direction": "inbound",
  • "from": "string",
  • "to": "string",
  • "body": "string",
  • "media_urls": [],
  • "status": "queued",
  • "error_code": "string",
  • "segments": 0,
  • "metadata": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "status_history": [
    ]
}

List messages

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

conversation_id
string
direction
string
Enum: "inbound" "outbound"
limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a message

Authorizations:
apiKey
path Parameters
message_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "msg_01j8x3a",
  • "tenant_id": "tnt_01j8x2z",
  • "conversation_id": "string",
  • "direction": "inbound",
  • "from": "string",
  • "to": "string",
  • "body": "string",
  • "media_urls": [],
  • "status": "queued",
  • "error_code": "string",
  • "segments": 0,
  • "metadata": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "status_history": [
    ]
}

List conversations

A conversation is the thread between one tenant number and one external number, ordered by most recent activity.

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

phone_number_id
string
limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a conversation

Authorizations:
apiKey
path Parameters
conversation_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "cnv_01j8x3f",
  • "tenant_id": "tnt_01j8x2z",
  • "phone_number_id": "string",
  • "external_number": "string",
  • "last_activity_at": "2019-08-24T14:15:22Z",
  • "last_message_preview": "string",
  • "opted_out": true
}

List opted-out recipients

Recipients who sent STOP to a tenant's numbers. Handset blocks sends to them automatically; this endpoint exists so your UI can show why.

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Compliance

10DLC brands and campaigns, E911 addresses.

Register a 10DLC brand

Registers your platform (or a tenant, for tenants with their own EIN) with The Campaign Registry. Vetting typically takes minutes to days; track via brand.status_changed.

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Retries with the same key within 24 h return the original response instead of repeating the action.

Request Body schema: application/json
required
legal_name
required
string
dba
string or null
ein
required
string

US tax ID.

entity_type
required
string
Enum: "private_company" "public_company" "non_profit" "sole_proprietor"
website
string or null <uri>
contact_email
required
string <email>
phone
required
string

Business contact number, E.164 — carrier registration requires it.

street
required
string
city
required
string
state
required
string

Two-letter US state code.

postal_code
required
string

5-digit ZIP (ZIP+4 allowed).

tenant_id
string or null

Set when the brand belongs to a tenant with its own EIN; omit for your platform-level brand.

Responses

Request samples

Content type
application/json
{
  • "legal_name": "string",
  • "dba": "string",
  • "ein": "string",
  • "entity_type": "private_company",
  • "website": "http://example.com",
  • "contact_email": "user@example.com",
  • "phone": "string",
  • "street": "string",
  • "city": "string",
  • "state": "string",
  • "postal_code": "string",
  • "tenant_id": "string"
}

Response samples

Content type
application/json
{
  • "legal_name": "string",
  • "dba": "string",
  • "ein": "string",
  • "entity_type": "private_company",
  • "website": "http://example.com",
  • "contact_email": "user@example.com",
  • "phone": "string",
  • "street": "string",
  • "city": "string",
  • "state": "string",
  • "postal_code": "string",
  • "tenant_id": "string",
  • "id": "brd_01j8x3k",
  • "status": "pending_vetting",
  • "rejection_reason": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

List brands

Authorizations:
apiKey
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a brand

Authorizations:
apiKey
path Parameters
brand_id
required
string

Responses

Response samples

Content type
application/json
{
  • "legal_name": "string",
  • "dba": "string",
  • "ein": "string",
  • "entity_type": "private_company",
  • "website": "http://example.com",
  • "contact_email": "user@example.com",
  • "phone": "string",
  • "street": "string",
  • "city": "string",
  • "state": "string",
  • "postal_code": "string",
  • "tenant_id": "string",
  • "id": "brd_01j8x3k",
  • "status": "pending_vetting",
  • "rejection_reason": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Register a 10DLC campaign

Registers a messaging use case under an approved brand for a tenant. Carrier review typically takes 1–3 business days; sending is blocked until status is approved. Track via campaign.status_changed.

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Retries with the same key within 24 h return the original response instead of repeating the action.

Request Body schema: application/json
required
tenant_id
required
string (TenantIdField)

The tenant this resource belongs to.

brand_id
required
string
use_case
required
string
Enum: "customer_care" "appointment_reminders" "marketing" "two_factor" "mixed"
description
required
string

What the tenant sends and why recipients expect it.

sample_messages
required
Array of strings [ 2 .. 5 ] items
opt_in_description
required
string >= 40 characters

How recipients consent to receive these messages. Carriers review this text; at least 40 characters describing the consent flow.

Responses

Request samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "brand_id": "string",
  • "use_case": "customer_care",
  • "description": "string",
  • "sample_messages": [
    ],
  • "opt_in_description": "stringstringstringstringstringstringstri"
}

Response samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "brand_id": "string",
  • "use_case": "customer_care",
  • "description": "string",
  • "sample_messages": [
    ],
  • "opt_in_description": "stringstringstringstringstringstringstri",
  • "id": "cmp_01j8x3p",
  • "status": "draft",
  • "rejection_reason": "string",
  • "throughput": {
    },
  • "created_at": "2019-08-24T14:15:22Z"
}

List campaigns

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

status
string (CampaignStatus)
Enum: "draft" "pending_review" "approved" "rejected" "suspended"
limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a campaign

Authorizations:
apiKey
path Parameters
campaign_id
required
string

Responses

Response samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "brand_id": "string",
  • "use_case": "customer_care",
  • "description": "string",
  • "sample_messages": [
    ],
  • "opt_in_description": "stringstringstringstringstringstringstri",
  • "id": "cmp_01j8x3p",
  • "status": "draft",
  • "rejection_reason": "string",
  • "throughput": {
    },
  • "created_at": "2019-08-24T14:15:22Z"
}

Validate and register an E911 address

Validates a dispatchable location and registers it for use with a tenant's numbers. Returns e911_address_invalid with correction suggestions when validation fails.

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Retries with the same key within 24 h return the original response instead of repeating the action.

Request Body schema: application/json
required
tenant_id
required
string (TenantIdField)

The tenant this resource belongs to.

street
required
string
unit
string or null
city
required
string
state
required
string = 2 characters
postal_code
required
string

Responses

Request samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "street": "123 N Central Ave",
  • "unit": "Suite 400",
  • "city": "string",
  • "state": "st",
  • "postal_code": "string"
}

Response samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "street": "123 N Central Ave",
  • "unit": "Suite 400",
  • "city": "string",
  • "state": "st",
  • "postal_code": "string",
  • "id": "e911_01j8x3t",
  • "status": "validated",
  • "created_at": "2019-08-24T14:15:22Z"
}

List E911 addresses

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Porting

Bring numbers from another carrier into a tenant.

Check portability

Ask, per number, whether it can be ported in. Free and side-effect-free.

Authorizations:
apiKey
Request Body schema: application/json
required
phone_numbers
required
Array of strings

US numbers in E.164, up to 100.

Responses

Request samples

Content type
application/json
{
  • "phone_numbers": [
    ]
}

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Create a port-in

Opens a draft port-in carrying the numbers and the account details as they appear at the losing carrier. Fails with numbers_not_portable if any number can't be ported. Call submit to start carrier review.

Authorizations:
apiKey
Request Body schema: application/json
required
tenant_id
required
string

The tenant the ported numbers will belong to.

phone_numbers
required
Array of strings

US numbers in E.164, up to 100, all from one losing carrier.

entity_name
required
string

Account holder name exactly as the losing carrier has it.

authorized_person
required
string

Person authorized to move the numbers.

billing_phone_number
required
string

The losing account's main billing number, E.164.

account_number
required
string

Account number at the losing carrier. Encrypted at rest.

pin
string or null

Port-out PIN, when the losing carrier uses one. Encrypted at rest.

required
object (PortInAddress)

Responses

Request samples

Content type
application/json
{
  • "tenant_id": "string",
  • "phone_numbers": [
    ],
  • "entity_name": "string",
  • "authorized_person": "string",
  • "billing_phone_number": "string",
  • "account_number": "string",
  • "pin": "string",
  • "service_address": {
    }
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "tenant_id": "string",
  • "phone_numbers": [
    ],
  • "status": "draft",
  • "status_detail": "string",
  • "entity_name": "string",
  • "authorized_person": "string",
  • "billing_phone_number": "string",
  • "account_number": "string",
  • "service_address": {
    },
  • "foc_date": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z"
}

List port-ins

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

status
string
Enum: "draft" "in_review" "action_needed" "foc_confirmed" "completed" "cancelled"
limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Get a port-in

Authorizations:
apiKey
path Parameters
port_in_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "tenant_id": "string",
  • "phone_numbers": [
    ],
  • "status": "draft",
  • "status_detail": "string",
  • "entity_name": "string",
  • "authorized_person": "string",
  • "billing_phone_number": "string",
  • "account_number": "string",
  • "service_address": {
    },
  • "foc_date": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z"
}

Submit a port-in for carrier review

Moves a draft (or corrected action_needed) port-in into in_review. Status changes arrive as port_in.status_changed webhooks. In test mode the simulated carrier completes the whole lifecycle in under a minute.

Authorizations:
apiKey
path Parameters
port_in_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "tenant_id": "string",
  • "phone_numbers": [
    ],
  • "status": "draft",
  • "status_detail": "string",
  • "entity_name": "string",
  • "authorized_person": "string",
  • "billing_phone_number": "string",
  • "account_number": "string",
  • "service_address": {
    },
  • "foc_date": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z"
}

Cancel a port-in

Authorizations:
apiKey
path Parameters
port_in_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "tenant_id": "string",
  • "phone_numbers": [
    ],
  • "status": "draft",
  • "status_detail": "string",
  • "entity_name": "string",
  • "authorized_person": "string",
  • "billing_phone_number": "string",
  • "account_number": "string",
  • "service_address": {
    },
  • "foc_date": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z"
}

Voice

Inbound call records, routing configuration, voicemail, recordings.

Create a routing config

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Retries with the same key within 24 h return the original response instead of repeating the action.

Request Body schema: application/json
required
tenant_id
required
string (TenantIdField)

The tenant this resource belongs to.

name
required
string
object or null

Weekly schedule in the tenant's timezone. Omit for 24/7 open_behavior.

required
object (RingBehavior)
VoicemailBehavior (object) or RingBehavior (object)

Applied outside business hours. Defaults to voicemail.

object

Responses

Request samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "name": "Main line",
  • "business_hours": {
    },
  • "open_behavior": {
    },
  • "closed_behavior": {
    },
  • "recording": {
    }
}

Response samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "name": "Main line",
  • "business_hours": {
    },
  • "open_behavior": {
    },
  • "closed_behavior": {
    },
  • "recording": {
    },
  • "id": "rtc_01j8x3w",
  • "created_at": "2019-08-24T14:15:22Z"
}

List routing configs

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a routing config

Authorizations:
apiKey
path Parameters
routing_config_id
required
string

Responses

Response samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "name": "Main line",
  • "business_hours": {
    },
  • "open_behavior": {
    },
  • "closed_behavior": {
    },
  • "recording": {
    },
  • "id": "rtc_01j8x3w",
  • "created_at": "2019-08-24T14:15:22Z"
}

Update a routing config

Changes apply to new calls immediately.

Authorizations:
apiKey
path Parameters
routing_config_id
required
string
Request Body schema: application/json
required
tenant_id
required
string (TenantIdField)

The tenant this resource belongs to.

name
required
string
object or null

Weekly schedule in the tenant's timezone. Omit for 24/7 open_behavior.

required
object (RingBehavior)
VoicemailBehavior (object) or RingBehavior (object)

Applied outside business hours. Defaults to voicemail.

object

Responses

Request samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "name": "Main line",
  • "business_hours": {
    },
  • "open_behavior": {
    },
  • "closed_behavior": {
    },
  • "recording": {
    }
}

Response samples

Content type
application/json
{
  • "tenant_id": "tnt_01j8x2z",
  • "name": "Main line",
  • "business_hours": {
    },
  • "open_behavior": {
    },
  • "closed_behavior": {
    },
  • "recording": {
    },
  • "id": "rtc_01j8x3w",
  • "created_at": "2019-08-24T14:15:22Z"
}

Delete a routing config

Fails with routing_config_in_use if any number references it.

Authorizations:
apiKey
path Parameters
routing_config_id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Start a click-to-call

Dials connect_to (the agent) from the tenant's number; when they answer, dials to (the customer) showing the same tenant number, and bridges the two. Track progress via call.completed webhooks or by polling: dialing → ringing → in_progress → completed (failed if either side never answers). In test mode the simulated parties answer within seconds and the call auto-completes.

A small set of US rural exchanges known for access stimulation (traffic pumping) can't be dialed from any leg — such requests return destination_not_supported.

Authorizations:
apiKey
Request Body schema: application/json
required
from
required
string

A tenant number id (num_…) or its E.164 — the caller ID both parties see.

to
required
string

The customer's number, E.164.

connect_to
required
string

The agent's number, E.164 — rings first.

transcribe
boolean
Default: false

Stream live speech-to-text: each final utterance arrives as a call.transcript webhook and accumulates on GET /calls/{id}/transcript. Billed per transcribed minute. After the call completes, an AI summary lands on the call's summary field (a call.summary webhook fires when ready).

Responses

Request samples

Content type
application/json
{
  • "from": "string",
  • "to": "string",
  • "connect_to": "string",
  • "transcribe": false
}

Response samples

Content type
application/json
{
  • "id": "call_01j8x42",
  • "tenant_id": "tnt_01j8x2z",
  • "phone_number_id": "string",
  • "direction": "inbound",
  • "from": "string",
  • "to": "string",
  • "connect_to": "string",
  • "status": "dialing",
  • "answered_by": "string",
  • "duration_seconds": 0,
  • "recording_id": "string",
  • "voicemail_id": "string",
  • "summary": "string",
  • "started_at": "2019-08-24T14:15:22Z",
  • "ended_at": "2019-08-24T14:15:22Z",
  • "events": [
    ]
}

List calls

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

phone_number_id
string
limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a call

Authorizations:
apiKey
path Parameters
call_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "call_01j8x42",
  • "tenant_id": "tnt_01j8x2z",
  • "phone_number_id": "string",
  • "direction": "inbound",
  • "from": "string",
  • "to": "string",
  • "connect_to": "string",
  • "status": "dialing",
  • "answered_by": "string",
  • "duration_seconds": 0,
  • "recording_id": "string",
  • "voicemail_id": "string",
  • "summary": "string",
  • "started_at": "2019-08-24T14:15:22Z",
  • "ended_at": "2019-08-24T14:15:22Z",
  • "events": [
    ]
}

Retrieve a call's live transcript

The transcript so far — callable mid-call. Segments are final utterances in order; text is the full conversation joined.

Authorizations:
apiKey
path Parameters
call_id
required
string

Responses

Response samples

Content type
application/json
{
  • "call_id": "string",
  • "text": "string",
  • "segments": [
    ]
}

Send DTMF digits on a call

Plays digits to the call's remote party — dial an extension, enter a conference PIN, navigate a phone tree. The call must be in progress. On outbound calls tones reach the to party; on inbound calls the original caller.

Authorizations:
apiKey
path Parameters
call_id
required
string
Request Body schema: application/json
required
digits
required
string <= 32 characters

0-9, A-D, *, #, plus pause characters w (0.5 s) and W (1 s). Example: "wwww4512#".

Responses

Request samples

Content type
application/json
{
  • "digits": "string"
}

Response samples

Content type
application/json
{
  • "call_id": "string",
  • "digits": "string",
  • "status": "sent"
}

Ask the remote party a keypad question

Speaks prompt as text-to-speech and collects keypresses from the call's remote party. Each keypress fires a call.dtmf webhook; the collected result arrives as a call.gather webhook with digits and a reason of completed, timeout, or hangup. The call must be in progress. In test mode the simulated party presses 1 about 1.5 s after the prompt (calls to +15005550007 never press anything and time out).

Authorizations:
apiKey
path Parameters
call_id
required
string
Request Body schema: application/json
required
prompt
required
string <= 500 characters

Spoken to the remote party as TTS.

max_digits
integer [ 1 .. 32 ]
Default: 1

Collection ends once this many digits arrive.

terminator
string <= 1 characters

A digit (0-9, *,

timeout_ms
integer [ 1000 .. 60000 ]
Default: 10000

How long to wait for input.

Responses

Request samples

Content type
application/json
{
  • "prompt": "string",
  • "max_digits": 1,
  • "terminator": "s",
  • "timeout_ms": 10000
}

Response samples

Content type
application/json
{
  • "call_id": "string",
  • "status": "listening"
}

Start live transcription mid-call

Turns on live speech-to-text for an in-progress call, either direction — each final utterance arrives as a call.transcript webhook and accumulates on GET /calls/{call_id}/transcript, and an AI summary generates after hangup (call.summary). This is the agent-assist switch for inbound calls; transcribe: true at creation remains the click-to-call shortcut. Idempotent — starting an already-transcribing call is a no-op. Runs until hangup; billed per transcribed minute on the call's connected time.

Authorizations:
apiKey
path Parameters
call_id
required
string

Responses

Response samples

Content type
application/json
{
  • "call_id": "string",
  • "status": "transcribing"
}

Stream the call's audio in real time

Forks the call's audio to a WebSocket on the media gateway (media.handset.dev) as ~20 ms G.711 μ-law frames. Connect to the returned url with the returned token (?token=… or an Authorization: Bearer header) — the token is shown exactly once. Frames arrive as JSON: {"event":"media","track":"inbound","seq":1, "timestamp_ms":840,"payload":"<base64 pcmu>"}.

direction: bidirectional also plays audio you send on the same socket into the call ({"event":"media","payload":…}; send {"event":"clear"} to flush queued playback) — the substrate for AI voice agents. The call must be ringing or in progress; one active stream per call. Billed per connected minute (stream_minute). In test mode the simulated carrier streams a pulsing 440 Hz tone on the inbound track and echoes your playback on the outbound track; calls to +15005550008 fail to stream.

Authorizations:
apiKey
path Parameters
call_id
required
string
Request Body schema: application/json
direction
string
Default: "fork"
Enum: "fork" "bidirectional"

fork receives audio; bidirectional also plays your audio into the call.

track
string
Default: "both"
Enum: "inbound" "outbound" "both"

Which side(s) of the conversation to receive.

Responses

Request samples

Content type
application/json
{
  • "direction": "fork",
  • "track": "inbound"
}

Response samples

Content type
application/json
{
  • "id": "stm_01j8x4b",
  • "call_id": "string",
  • "tenant_id": "tnt_01j8x2z",
  • "direction": "fork",
  • "track": "inbound",
  • "status": "starting",
  • "url": "string",
  • "token": "string",
  • "stop_reason": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "started_at": "2019-08-24T14:15:22Z",
  • "stopped_at": "2019-08-24T14:15:22Z"
}

List a call's streams

Authorizations:
apiKey
path Parameters
call_id
required
string

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Stop a stream

Stops forking audio and settles billing. Idempotent — deleting an already-stopped stream returns its final state. Streams also end on their own when the call ends.

Authorizations:
apiKey
path Parameters
call_id
required
string
stream_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "stm_01j8x4b",
  • "call_id": "string",
  • "tenant_id": "tnt_01j8x2z",
  • "direction": "fork",
  • "track": "inbound",
  • "status": "starting",
  • "url": "string",
  • "token": "string",
  • "stop_reason": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "started_at": "2019-08-24T14:15:22Z",
  • "stopped_at": "2019-08-24T14:15:22Z"
}

List voicemails

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a voicemail

Authorizations:
apiKey
path Parameters
voicemail_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "vm_01j8x45",
  • "tenant_id": "tnt_01j8x2z",
  • "call_id": "string",
  • "from": "string",
  • "duration_seconds": 0,
  • "transcript": "string",
  • "audio_url": "http://example.com",
  • "created_at": "2019-08-24T14:15:22Z"
}

Retrieve a call recording

Authorizations:
apiKey
path Parameters
recording_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "rec_01j8x48",
  • "tenant_id": "tnt_01j8x2z",
  • "call_id": "string",
  • "duration_seconds": 0,
  • "consent_announced": true,
  • "audio_url": "http://example.com",
  • "created_at": "2019-08-24T14:15:22Z"
}

Webhooks

Manage endpoints that receive signed Handset events.

Create a webhook endpoint

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Retries with the same key within 24 h return the original response instead of repeating the action.

Request Body schema: application/json
required
url
required
string <uri>
enabled_events
Array of strings

Event types to deliver; omit for all.

description
string or null

Responses

Request samples

Content type
application/json
{
  • "enabled_events": [
    ],
  • "description": "string"
}

Response samples

Content type
application/json
{
  • "enabled_events": [
    ],
  • "description": "string",
  • "id": "whe_01j8x4b",
  • "status": "active",
  • "created_at": "2019-08-24T14:15:22Z",
  • "secret": "string"
}

List webhook endpoints

Authorizations:
apiKey
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Update a webhook endpoint

Authorizations:
apiKey
path Parameters
endpoint_id
required
string
Request Body schema: application/json
required
url
required
string <uri>
enabled_events
Array of strings

Event types to deliver; omit for all.

description
string or null

Responses

Request samples

Content type
application/json
{
  • "enabled_events": [
    ],
  • "description": "string"
}

Response samples

Content type
application/json
{
  • "enabled_events": [
    ],
  • "description": "string",
  • "id": "whe_01j8x4b",
  • "status": "active",
  • "created_at": "2019-08-24T14:15:22Z"
}

Delete a webhook endpoint

Authorizations:
apiKey
path Parameters
endpoint_id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Send a test event

Authorizations:
apiKey
path Parameters
endpoint_id
required
string
Request Body schema: application/json
event_type
string
Default: "message.received"

Responses

Request samples

Content type
application/json
{
  • "event_type": "message.received"
}

Response samples

Content type
application/json
{
  • "delivered": true,
  • "response_status": 0,
  • "error": "string"
}

An inbound SMS/MMS arrived at a tenant number. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

An outbound message reached the recipient's carrier as delivered. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

An outbound message failed (carrier rejection, filtering, etc.). Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

An inbound call began on a tenant number. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A call ended; includes outcome and duration. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

The AI summary of a transcribed call is ready. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A keypress on an active call (`digit`, plus `party`: agent | customer). Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A gather finished; carries the collected `digits` and a `reason` of completed | timeout | hangup. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A voicemail was recorded; transcript follows when ready. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A call recording is stored and available. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A media stream went active — the carrier is delivering audio. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A media stream ended; carries `reason` and billed `duration_seconds`. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A media stream could not start or died on a carrier error. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A 10DLC brand moved through vetting. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

A 10DLC campaign moved through carrier review. Webhook

Authorizations:
apiKey
Request Body schema: application/json
id
required
string
type
required
string
event_version
required
string
created_at
required
string <date-time>
tenant_id
string (TenantIdField)

The tenant this resource belongs to.

data
required
object

The full affected resource (Message, Call, Voicemail, …).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_01j8x4e",
  • "type": "message.received",
  • "event_version": "2026-08-11",
  • "created_at": "2019-08-24T14:15:22Z",
  • "tenant_id": "tnt_01j8x2z",
  • "data": { }
}

Web clients

Create a web client

Provisions a browser softphone endpoint (one SIP credential). Create one per agent seat — credentials must not be shared across concurrent devices. The browser never sees the credential: mint short-lived login tokens server-side via POST /web_clients/{id}/tokens and hand only the token to the page. Freshly created clients can take a few seconds to accept their first login.

Authorizations:
apiKey
Request Body schema: application/json
required
tenant_id
required
string
name
string

Label for your own bookkeeping, e.g. an agent seat.

Responses

Request samples

Content type
application/json
{
  • "tenant_id": "string",
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "tenant_id": "string",
  • "name": "string",
  • "sip_username": "string",
  • "status": "active",
  • "created_at": "2019-08-24T14:15:22Z"
}

List web clients

Authorizations:
apiKey
query Parameters
tenant_id
string

Scope results to one tenant.

limit
integer [ 1 .. 100 ]
Default: 25
after
string

Cursor from a previous page's next_cursor.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Retrieve a web client

Authorizations:
apiKey
path Parameters
web_client_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "tenant_id": "string",
  • "name": "string",
  • "sip_username": "string",
  • "status": "active",
  • "created_at": "2019-08-24T14:15:22Z"
}

Revoke a web client

Deletes the underlying credential — outstanding login tokens die with it and any registered browser session disconnects. Idempotent.

Authorizations:
apiKey
path Parameters
web_client_id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "tenant_id": "string",
  • "name": "string",
  • "sip_username": "string",
  • "status": "active",
  • "created_at": "2019-08-24T14:15:22Z"
}

Mint a login token

Returns a short-lived browser login token. Call this from your backend when a signed-in agent opens the softphone, and pass the token to the browser SDK. Mint a fresh token per session; tokens expire on their own and die early if the client is revoked.

Authorizations:
apiKey
path Parameters
web_client_id
required
string

Responses

Response samples

Content type
application/json
{
  • "token": "string",
  • "expires_at": "2019-08-24T14:15:22Z"
}

Usage

Usage summary

Totals the account's billable usage by kind over [start, end) for the key's mode. Live and test ledgers are separate; only live usage is invoiced.

Authorizations:
apiKey
query Parameters
start
string

RFC 3339 timestamp or YYYY-MM-DD. Defaults to the first instant of the current month.

end
string

RFC 3339 timestamp or YYYY-MM-DD. Defaults to now.

tenant_id
string

Scope results to one tenant.

Responses

Response samples

Content type
application/json
{
  • "object": "usage_summary",
  • "mode": "live",
  • "start": "2019-08-24T14:15:22Z",
  • "end": "2019-08-24T14:15:22Z",
  • "data": [
    ]
}

Events

Mint a realtime event-stream token

Returns a short-lived browser-safe token and the WebSocket url to connect it to (wss://media.handset.dev/v1/events?token=…). The socket pushes your account's events — the same envelopes your webhook endpoints receive — the moment they happen; tenant-scoped keys get only their tenant's events. Mint from your backend (your API key never reaches the browser) and re-mint on expiry; treat the stream as a low-latency refresh signal, with webhooks as the durable channel.

Authorizations:
apiKey

Responses

Response samples

Content type
application/json
{
  • "token": "hsrt_eyJhIjo…",
  • "expires_at": "2019-08-24T14:15:22Z"
}