> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kataven.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Telephony

> Connect Twilio, Plivo, or Vobiz to Kataven — carrier credentials (encrypted), phone-number management, agent pinning, inbound + outbound routing, per-tenant cost caps.

Two-step model:

1. **Provider credentials** — the API keys for your carrier (Twilio,
   Plivo, Vobiz). Stored encrypted via the Secret Encryptor service;
   never returned in plaintext.
2. **Phone numbers** — individual E.164 numbers that reference one
   provider credential and (optionally) pin to one agent.

## Providers

Today supported: `twilio`, `plivo`, `vobiz`.

### Required credentials JSON

| Provider | Required keys               |
| -------- | --------------------------- |
| `twilio` | `account_sid`, `auth_token` |
| `plivo`  | `auth_id`, `auth_token`     |
| `vobiz`  | `auth_id`, `auth_token`     |

```python theme={null}
client.telephony.create_provider(
    provider="twilio",
    label="Production Twilio",
    credentials={
        "account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "auth_token": "your_auth_token",
    },
)
```

The handler validates the JSON shape before accepting it — pasting
half a config gets a `400` immediately, not a runtime failure on the
first call.

A provider with phone numbers attached can't be deleted (`409`); you
have to remove the numbers first.

## Phone numbers

```python theme={null}
client.telephony.create_number(
    e164="+12025550123",
    provider="twilio",
    credentials_id="<provider uuid>",
    agent_id="<agent uuid>",   # optional — pins the number to one agent
    inbound_enabled=True,
    outbound_enabled=True,
)
```

`e164` is sanity-checked locally (`+` + 7–15 digits). The carrier
rejects malformed numbers downstream regardless.

Reassign a number to a different agent:

```python theme={null}
client.telephony.update_number(number_id, agent_id="<new agent>")
```

Or detach it (set `agent_id=None`).

## Outbound origination

`POST /api/v1/calls/originate` — dispatches an outbound call.
Pipeline:

1. JWT auth (route middleware).
2. **Caller-ID ownership** — `from_number` must be a tenant-owned,
   `outbound_enabled` number.
3. **Cost caps** — concurrent / per-minute / daily / daily-minute
   counters in Redis. Exceeded cap → `429`.
4. **Decrypt credentials** via Secret Encryptor.
5. POST to Call Processor `/telephony/originate`.
6. On success, INCR Redis counters.

```python theme={null}
result = client.calls.originate(
    from_number="+12025550123",
    to_number="+14155550100",
    agent_id="<agent uuid>",
)
print(result["provider_call_id"], result["session_id"])
```

The `session_id` lets you deep-link to the conversation viewer while
the call is still in progress.

## Cost caps

Each tenant has a per-account row controlling the four cap dimensions:

| Field               | Meaning                                |
| ------------------- | -------------------------------------- |
| `concurrent_max`    | Hard ceiling on simultaneous calls.    |
| `calls_per_minute`  | Burst limit.                           |
| `daily_calls_max`   | Per-day budget (rolling UTC midnight). |
| `daily_minutes_max` | Per-day talk-time budget.              |

```python theme={null}
client.call_limits.update(
    concurrent_max=20,
    calls_per_minute=10,
    daily_calls_max=1000,
    daily_minutes_max=2400,
)
```

Set any field to `0` to **block all** of that dimension — useful as
a kill-switch during an incident.
