# Telephony API

> Control-plane operations for carrier connectivity: create and manage SIP trunks, register phone numbers, and assign numbers to a trunk, agent, or flow.

Use the Telephony API to connect the phone network to your workspace. Define a
**trunk** for a carrier. Register the **numbers** that ride the trunk.
**Assign** each number to the agent, trunk, or routing flow that answers it.
These are control-plane operations. They configure routing, not live media. For
that reason, they live with the admin console, not in the media SDK.

> **NOTE:**
> Trunk and number management is a **control-plane** surface. Drive it from the
> admin console (`agent.firstbyte.fast`) and the control-plane API on
> `portal.firstbyte.fast`. There is **no media-SDK method** for it today. The
> [Voice SDK](/modalities/voice/sdk/javascript) `Calls` client originates and
> controls calls *over* a trunk that you already provisioned here (it takes a
> `trunkId`). The `Calls` client does not create trunks or numbers.
> For the task-oriented walkthroughs, see
> [SIP Trunking](/modalities/voice/transport-telephony/sip-trunking) and
> [Number Provisioning](/modalities/voice/transport-telephony/number-provisioning).
> This page is the operation-and-schema reference.

Every operation is **workspace-scoped**. The caller must hold a valid session
for the target workspace (`orgId`). Each operation is tenant-checked, so you
can touch only the trunks and numbers that your workspace owns. Writes take
effect **live**: the SIP gateway hot-reloads a changed trunk within a couple of
seconds, and the engine rebuilds the routing table, with no engine restart.

## Trunks

A trunk is a named SIP/RTP profile. The profile defines where your carrier's
SBC lives, how to authenticate, which codecs to offer, and what an inbound call
does. Four operations manage the set.

| Operation | Kind | Input | Effect |
|-----------|------|-------|--------|
| `admin.addTrunk` | mutation | `{ orgId, trunk }` | Create a trunk and push it live |
| `admin.updateTrunk` | mutation | `{ orgId, trunkId, trunk }` | Replace a trunk's config |
| `admin.removeTrunk` | mutation | `{ orgId, trunkId }` | Delete a trunk and drop its routing rule |
| `admin.listTrunks` | query | `{ orgId }` | List the workspace's trunks (canonical, live view) |

> **NOTE:**
> `admin.listTrunks` reads from the same live routing store that the engine
> consumes. The response is exactly what is in force on the data plane, not a
> cache that can drift. The API unseals secret subfields for the edit form on the
> way out. The API re-seals them on the next write.

### The trunk object

Only `trunk_id` is required. Every other field is optional and carries an
engine-side default. The most common fields:

- **`trunk_id`** (`string`, required) — The stable identifier for the trunk, typically the carrier-supplied circuit   name (for example `INT-KA-SBC09-9287`). Numbers resolve against this key.   Keep the key stable after calls start to flow.

- **`display_name`** (`string`) — The human-readable label that the console shows.

- **`realm`** (`enum`, default `internal`) — The trunk class. The class drives billing categorisation and which   destinations a tool call may transfer to. One of `internal` (your own   PBX/extensions), `external` (a PSTN/SIP carrier — per-minute billing   applies), or `ai_vendor` (an external media vendor such as LiveKit, Vapi, or   Twilio that acts as the agent leg).

- **`require_registration`** (`boolean`) — When `true`, the gateway REGISTERs outbound to the carrier with   `sip_username` / `sip_password`. When `false`, the gateway matches the trunk   by source IP (SBC) and sends no registration.

- **`sip_username`** (`string`) — The auth user for a registering trunk.

- **`sip_password`** (`string`) — The auth secret for a registering trunk. **Sealed at rest** — see the note   below.

- **`internal_sip_ip / external_sip_ip`** (`string`) — The signalling addresses. The split lets the gateway bind a private IP behind   cloud NAT while it advertises the public IP in `Contact` / `Via`.

- **`internal_rtp_ip / external_rtp_ip`** (`string`) — The media addresses, with the same private-bind / public-advertise split as   the SIP IPs.

- **`gateway_ip / sbc_ip`** (`string`) — The address of the upstream carrier gateway / session border controller that   the trunk peers with.

- **`codec_preferences`** (`string[]`) — The ordered codec offer, for example `["PCMU", "PCMA"]`. G.711 µ-law/A-law   are the wire codecs. See the   [Codec Guide](/modalities/voice/transport-telephony/codecs).

- **`channel_limit`** (`integer`) — The maximum number of concurrent calls on the trunk.

- **`media_mode`** (`enum`, default `proxy`) — `proxy` routes RTP through the engine's media plane (the production default).   `direct` is signalling-only carrier→runtime media.   > **WARNING:** > `direct` is **partial** today. The live runtime degrades a `direct` trunk >   back to `proxy`, because direct-media open is not yet implemented end to end. >   Leave the mode on `proxy` unless you explicitly test the direct path.

- **`compliance_headers_mode`** (`enum`, default `full`) — The identity-header policy on outbound INVITEs. `full` sends   regulator-friendly caller-identity headers (P-Asserted-Identity,   Remote-Party-ID, P-Access-Network-Info). `minimal` suppresses them, for   carriers whose CLI screening silently drops calls that assert identity. For   finer control, use `enable_pai`, `enable_pani`, and `custom_pani_header`.

#### Inbound routing target

An inbound INVITE matched to a trunk needs a destination. Set **one** of these
fields. The engine regenerates the routing table on write, so the target takes
effect immediately:

- **`inbound_skill_id`** (`string`) — Offer the call to the oldest-idle agent who holds this skill (ACD fallback).

- **`vdn_id`** (`string | null`) — Route to a VDN, so its IVR/vector program runs on inbound. This field is   mutually exclusive with an agent binding. The router emits a `vector:` rule   when the field is set. See   [PBX / ACD](/modalities/voice/transport-telephony/pbx-acd).

- **`inbound_ai_quic_url`** (`string`) — Stream the answered call to an AI agent endpoint over QUIC.   `inbound_ai_websocket_url` is the WebSocket-fallback equivalent for bespoke   WS audio receivers.

> **NOTE:**
> **Secrets are sealed at rest.** The API applies field-level encryption to
> `sip_password` and `api_bearer_token` before storage. The API opens them only
> under the trunk's own workspace. The rest of the trunk (IPs, usernames,
> routing) stays readable to the engine without any decrypt. Deployments that
> require this — HIPAA / SOC 2 — get it by default. You send the plaintext
> secret. You never receive the secret back in the clear, except in the edit
> form.

### Create a trunk

**Trunk payload (bring-your-own carrier):**
```jsonc
{
  "trunk_id": "carrier-primary",
  "display_name": "Primary PSTN carrier",
  "realm": "external",
  "require_registration": true,
  "sip_username": "acme-sip",
  "sip_password": "•••••••••",          // sealed on write
  "sbc_ip": "203.0.113.10",
  "codec_preferences": ["PCMU", "PCMA"],
  "channel_limit": 200,
  "media_mode": "proxy",
  "inbound_skill_id": "0f3c…"           // where inbound calls land
}
```

**Operation:**
```jsonc
// admin.addTrunk — mutation
{
  "orgId": "<workspace-id>",
  "trunk": { /* the payload above */ }
}
// → { ok: true }
```

`updateTrunk` takes the same `trunk` object plus a `trunkId`. It replaces the
stored config wholesale. `removeTrunk` takes just `{ orgId, trunkId }`.

## Numbers

A phone number (DID) is a row in the workspace's number registry. The row maps
an E.164 address to a trunk and/or an agent. Register a number to route an
inbound call to it. The gateway rejects a call to an unregistered number.

| Operation | Kind | Input | Effect |
|-----------|------|-------|--------|
| `admin.upsertPhoneNumber` | mutation | `{ orgId, number }` | Create or update a number and its inbound routing |
| `admin.deletePhoneNumber` | mutation | `{ orgId, id }` | Remove a number and its routing entry |

- **`e164`** (`string`, required) — The number in E.164, for example `+14155550123`.

- **`number_type`** (`enum`, default `local`) — `local`, `toll-free`, or `mobile`.

- **`country`** (`string`, default `US`) — The ISO-3166 alpha-2 country code.

- **`trunk_id`** (`string | null`) — The trunk that carries this number's calls. This is the number → trunk   assignment.

- **`agent_id`** (`string | null`) — The agent that answers calls to this number. This is the number → agent   assignment.

- **`status`** (`enum`, default `active`) — `active`, `pending`, or `inactive`. Only an `active` number is published to   the inbound routing index. A flip to `pending`/`inactive` withdraws the   number, but does not delete the row.

- **`provider`** (`string | null`) — Where the number came from (for example `manual`, or a carrier name). For   numbers that you import by hand, use your own value.

> **NOTE:**
> When you register a number, the API publishes a lookup entry. The SIP gateway
> resolves this entry on every inbound INVITE (by dialed number, then by trunk
> source IP). The API also rebuilds the routing table, so the call reaches its
> assigned agent. Only `active` numbers are published. `deletePhoneNumber` (or a
> status flip) withdraws the entry immediately.

### Assignment is the whole point

To provision an address and to *assign* it are two steps. A number with neither
a `trunk_id` nor an `agent_id` still parks a call. The assignment model:

- **Number → trunk** (`trunk_id`) — which carrier profile carries the call.
- **Number → agent** (`agent_id`) — which agent answers it.
- **Trunk → target** (`inbound_skill_id` / `vdn_id` / `inbound_ai_quic_url`) —
  the fallback destination for any inbound call on the trunk that a specific
  number does not override.

A number-level assignment wins over the trunk's default target. You can point a
whole trunk at one flow, and carve out individual DIDs to specific agents.

**Register + assign a DID:**
```jsonc
// admin.upsertPhoneNumber — mutation
{
  "orgId": "<workspace-id>",
  "number": {
    "e164": "+14155550123",
    "number_type": "local",
    "country": "US",
    "trunk_id": "carrier-primary",   // → trunk
    "agent_id": "support-bot",       // → agent
    "status": "active"
  }
}
```

## Managed number catalog (search-and-buy)

`telephony.searchAvailableNumbers` searches an upstream carrier catalog for a
new DID that you can allocate. The search pages and filters by country and
type. The search also annotates each offer to show whether your workspace
already owns the number.

- **`query`** (`string`) — A substring / area-code filter.
- **`country`** (`string`, default `all`) — An ISO country filter.
- **`type`** (`enum`, default `all`) — `local`, `toll-free`, `mobile`, or `all`.
- **`page / perPage`** (`integer`) — Pagination; `perPage` clamps to 50.

> **WARNING:**
> The managed catalog is a **stub today**. Search returns a small demo set
> (India-only, served by a placeholder catalog provider), pending live upstream
> carrier integration. The response *shape* is final, so you can wire against it
> now safely. However, you cannot purchase a live number through it yet. **The
> allocation registry and the inbound routing that reads it are real.** A number
> that you register by hand with `upsertPhoneNumber` behaves exactly like a
> purchased number will. To go live now, bring your own DID over a trunk.

## Per-tenant SIP address (no purchase)

Separately from carrier trunks, every workspace gets a ready-to-use SIP address
at creation — `<workspace-id>.sip.firstbyte.fast` (with matching
`<workspace-id>.webrtc.firstbyte.fast` and apex records). No Telephony API call
is necessary. The address exists from onboarding. The gateway derives the
tenant from the leftmost DNS label of an inbound INVITE. Use the address to
register softphones, or to point a test carrier at your workspace immediately.
Details are in
[Number Provisioning](/modalities/voice/transport-telephony/number-provisioning).

## Related

  - **[SIP Trunking](/modalities/voice/transport-telephony/sip-trunking)** — Task walkthrough: connect a carrier, bring your own trunk, or use a managed one.
  - **[Number Provisioning](/modalities/voice/transport-telephony/number-provisioning)** — The three routes to a reachable address, and how to assign it.
  - **[PBX / ACD](/modalities/voice/transport-telephony/pbx-acd)** — VDNs, vectors, and the skill-queue routing that trunks and numbers target.
  - **[Calls API](/modalities/voice/api/calls)** — Originate and control live calls over a provisioned trunk from the SDK.
