# How do I register a SIP trunk and DID numbers so VARTA can dial and answer?

Trunks and numbers are the dial plan — register your carrier's SIP trunk once, optionally register the DIDs it's authorised to use, and bind a number to an agent for inbound. Registering a number here does not purchase one from a carrier; it tells VARTA about a number you already hold.


Placing a real call with `"dial": true` on [`POST /v1/calls`](/docs/api/calls), or
answering an inbound one, needs a carrier account behind it. Trunks and numbers are
that configuration: a trunk is your SIP carrier, numbers are the DIDs that carrier has
given you, either as caller ID for outbound or as an inbound line bound to an agent.

Trunk credentials (`auth.username` / `auth.password`) are encrypted at rest and never
returned by any read endpoint — `has_credentials` tells you whether a trunk has them,
not what they are.

## Register a trunk

```varta-endpoint
POST /v1/trunks
```

```bash
curl -X POST "$VARTA_BASE_URL/trunks" \
  -H "Authorization: Bearer $VARTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Exotel primary",
    "address": "sip.exotel.com",
    "numbers": ["+918041234567"],
    "auth": {"username": "...", "password": "..."}
  }'
```

`address` (the carrier's SIP hostname) is the only required field. `numbers` is the
set of caller-ID numbers this trunk is authorised to present — required if you plan to
choose a specific `from` when dialling. `provider_trunk_id` is optional: supply it to
reference a trunk already registered directly with LiveKit; omit it to have `address`
and `auth` sent inline on every dial instead.

```json
{
  "id": "tk_a1",
  "name": "Exotel primary",
  "address": "sip.exotel.com",
  "numbers": ["+918041234567"],
  "provider_trunk_id": null,
  "has_credentials": true,
  "created_at": 1735600000.1
}
```

Like every state-changing call under `/v1`, this endpoint honours an
`Idempotency-Key` header — see [Idempotency](/docs/idempotency).

**Errors:** [`invalid_request`](/docs/errors/invalid_request) when `address` is
missing, a `numbers` entry isn't valid E.164 (international form, for example
`+918041234567`), or `auth` is present but isn't an object.

## List trunks

```varta-endpoint
GET /v1/trunks
```

```bash
curl "$VARTA_BASE_URL/trunks" \
  -H "Authorization: Bearer $VARTA_API_KEY"
```

Returns `{"data": [...], "has_more": false, "next_cursor": null}` — this endpoint
does not yet paginate — with each item shaped like the `POST /v1/trunks` response
above, oldest first.

**Errors:** none specific to this request.

## Get a trunk

```varta-endpoint
GET /v1/trunks/{trunk_id}
```

```bash
curl "$VARTA_BASE_URL/trunks/tk_a1" \
  -H "Authorization: Bearer $VARTA_API_KEY"
```

Returns the same shape as the `POST /v1/trunks` response above.

**Errors:** [`trunk_not_found`](/docs/errors/trunk_not_found) when no trunk with that
id exists for your tenant.

## Delete a trunk

```varta-endpoint
DELETE /v1/trunks/{trunk_id}
```

```bash
curl -X DELETE "$VARTA_BASE_URL/trunks/tk_a1" \
  -H "Authorization: Bearer $VARTA_API_KEY"
```

```json
{ "id": "tk_a1", "deleted": true }
```

**Errors:** [`trunk_not_found`](/docs/errors/trunk_not_found) when no trunk with that
id exists for your tenant.

## Register a number

```varta-endpoint
POST /v1/numbers
```

This registers a DID **you already hold with a carrier** — it does not purchase or
provision a new number. VARTA has no number-purchasing relationship with any carrier;
buy the number from your carrier first, then tell VARTA about it here so it can be
used as caller ID or bound to an agent for inbound.

```bash
curl -X POST "$VARTA_BASE_URL/numbers" \
  -H "Authorization: Bearer $VARTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"number": "+918041234567", "trunk_id": "tk_a1", "agent_id": "ag_412", "label": "Support line"}'
```

`number` (E.164) is the only required field. `trunk_id`, `agent_id` and `label` are
optional — set `agent_id` here or with the dedicated bind call below.

```json
{
  "id": "num_c3",
  "tenant_id": "acme",
  "number": "+918041234567",
  "trunk_id": "tk_a1",
  "agent_id": "ag_412",
  "label": "Support line",
  "created_at": 1735600000.1
}
```

Like every state-changing call under `/v1`, this endpoint honours an
`Idempotency-Key` header — see [Idempotency](/docs/idempotency).

**Errors:** [`invalid_request`](/docs/errors/invalid_request) when `number` isn't
valid E.164.

## List numbers

```varta-endpoint
GET /v1/numbers
```

```bash
curl "$VARTA_BASE_URL/numbers" \
  -H "Authorization: Bearer $VARTA_API_KEY"
```

Returns `{"data": [...], "has_more": false, "next_cursor": null}`, each item shaped
like the `POST /v1/numbers` response above, sorted by number.

**Errors:** none specific to this request.

## Bind a number to an agent

```varta-endpoint
POST /v1/numbers/{number}/bind
```

Inbound calls on this number are answered by the bound agent, unless an inbound
routing webhook overrides the choice.

```bash
curl -X POST "$VARTA_BASE_URL/numbers/%2B918041234567/bind" \
  -H "Authorization: Bearer $VARTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "ag_412"}'
```

The number in the path is E.164 (`+918041234567`) — URL-encode the leading `+` as
`%2B`. Returns the updated number record, shaped like the `POST /v1/numbers` response.

Like every state-changing call under `/v1`, this endpoint honours an
`Idempotency-Key` header — see [Idempotency](/docs/idempotency).

**Errors:** [`invalid_request`](/docs/errors/invalid_request) when `agent_id` is
missing from the body or the number in the path isn't valid E.164,
[`not_found`](/docs/errors/not_found) when that number isn't registered.

## Release a number

```varta-endpoint
DELETE /v1/numbers/{number}
```

```bash
curl -X DELETE "$VARTA_BASE_URL/numbers/%2B918041234567" \
  -H "Authorization: Bearer $VARTA_API_KEY"
```

```json
{ "number": "+918041234567", "deleted": true }
```

This removes VARTA's record of the number and its binding — it does not release the
number back to your carrier; that's a separate step with whoever you hold it from.

**Errors:** [`invalid_request`](/docs/errors/invalid_request) when the number in the
path isn't valid E.164, [`not_found`](/docs/errors/not_found) when it isn't
registered.
