# Contactivity API reference

The Contactivity API places and controls calls, manages numbers, trunks and voice apps, runs AI agents and
reports usage. It is JSON over HTTPS, one project per path:

```
https://api.contactivity.io/v1/projects/{project_id}/...
```

The interactive reference is the [API Reference](/reference/) tab; the spec is
`https://api.contactivity.io/docs/openapi.yaml` (OpenAPI 3.1).
The SDKs are `@contactivity/sdk` (Node) and `contactivity_sdk` (Python). Moving from Twilio or Telnyx? See [compatibility.md](/guides/compatibility.md).

| Page | Resources |
|---|---|
| [calls.md](/guides/calls.md) | Calls and call actions (speak, gather, bridge, record, redirect, ...) |
| [recordings.md](/guides/recordings.md) | Recordings and transcriptions |
| [conferences-queues.md](/guides/conferences-queues.md) | Conferences, participants, queues, queue members |
| [numbers.md](/guides/numbers.md) | Number search, ordering and routing; verified numbers |
| [trunks-routes.md](/guides/trunks-routes.md) | SIP trunks, credentials, IP allow lists, routes, limits, webhook public key |
| [voice-apps.md](/guides/voice-apps.md) | Voice apps, PBX apps, client tokens, push credentials, pricing |
| [ai-agents.md](/guides/ai-agents.md) | AI agents (versions, traffic, tests, calls, analytics) and AI providers |
| [flows.md](/guides/flows.md) | Flow validation and versions |
| [knowledge-bases.md](/guides/knowledge-bases.md) | Knowledge bases and documents |
| [billing.md](/guides/billing.md) | Usage, call detail records, balance, ledger |
| [events.md](/guides/events.md) | Events, webhooks, webhook endpoint, delivery log, error debugger, alert rules |

## Region

Send requests to your project's region: `https://api.<region>.contactivity.io` (for example
`api.us1.contactivity.io`); `https://api.contactivity.io` is `us1`. A key from another region is refused with
`401` and a body naming the right host.

## Authentication

Create an API key in the portal (Settings > API keys) and send its secret as a bearer token:

```sh
curl https://api.contactivity.io/v1/projects/$PROJECT_ID/calls \
  -H "Authorization: Bearer $CONTACTIVITY_API_KEY"
```

A key belongs to one project. `{project_id}` must be that project, or the answer is `404 not_found`
("Project not found"). A bad or revoked key gets `401 authentication`.

### Scopes

A key carries scopes. Each endpoint needs the scopes listed on its page (and in the spec's `x-scopes`); a key
without them gets `403 insufficient_scope` and a message naming the missing scope.

| Scope | Covers |
|---|---|
| `calls:read`, `calls:write` | Calls and call actions. Adding or removing conference participants and dequeuing also need `calls:write` |
| `recordings:read`, `recordings:write` | Recordings and transcriptions |
| `conferences:read`, `conferences:write` | Conferences and participants |
| `queues:read`, `queues:write` | Queues and members |
| `numbers:read`, `numbers:write` | Available numbers, numbers |
| `verified_numbers:read`, `verified_numbers:write` | Verified numbers (fetching a verification needs `verified_numbers:write`) |
| `trunks:read`, `trunks:write` | SIP trunks, credentials, IPs |
| `routes:read`, `routes:write` | Routes |
| `apps:read`, `apps:write` | Voice apps, flows, AI agents and providers, knowledge bases |
| `pbx:read`, `pbx:write` | PBX apps and voicemail |
| `clients:read`, `clients:write` | Client access tokens, in-app calling settings, push credentials |
| `pricing:read` | Prices and price checks |
| `billing:read` | Usage, CDRs, balance, ledger |
| `events:read`, `events:write` | Events, webhook endpoint, delivery log, errors, alert rules, webhook key rotation |
| (any key) | `GET /limits`, `GET /webhook-public-key` |

API keys cannot create keys, top up or change payment methods; those stay in the portal.

## Requests and responses

- Bodies are JSON (`Content-Type: application/json`) with snake_case fields. Bodies are strict: an unknown field
  is `400 unknown_parameter` (with `param`), a field of the wrong type is `400 invalid_parameter`.
- `POST` that creates returns `201` and the object; `DELETE` returns `204` with no body; reads and updates
  return `200` and the object.
- `DELETE` archives; nothing is deleted (see [Archiving](#archiving)). The one exception is a phone number:
  `DELETE /numbers/{phone_number}` releases it.
- Timestamps are RFC 3339 UTC with milliseconds: `2026-10-22T14:03:11.123Z`.
- Durations are whole seconds, in fields ending in `_secs`.
- Money is an object: `{"amount": "0.0130", "currency": "USD"}`. `amount` is a decimal string and never negative
  (4 decimals for usage, 2 for balances); a ledger entry says `credit` or `debit` in `direction`.
- IDs are opaque strings: `CA…` calls, `RE…` recordings, `TR…` transcriptions, `CF…` conferences, `QU…` queues,
  `app_…` voice apps, `trk_…` trunks, `rt_…` routes. Phone numbers are E.164 and are URL-encoded in paths
  (`/numbers/%2B14155551234`).
- Every response carries `X-Request-Id` (`req_…`). Quote it when you contact support.

## Errors

Every error has the same body:

```json
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_parameter",
    "message": "to must be an E.164 number, client:<name> or a SIP URI with a public host",
    "param": "to",
    "request_id": "req_us1_…",
    "doc_url": "https://docs.contactivity.io/errors/invalid_parameter"
  }
}
```

| `type` | HTTP status |
|---|---|
| `invalid_request` | 400, 402, 413, 422 |
| `authentication` | 401 |
| `permission` | 403 |
| `not_found` | 404 |
| `conflict` | 409 |
| `rate_limit` | 429 |
| `api_error` | 5xx |

Branch on `code`, not on `message`. `param` names the field, query parameter or header at fault, when there is
one. Codes every endpoint can return:

| Code | Status | Meaning |
|---|---|---|
| `authentication` | 401 | Missing, bad or revoked API key |
| `insufficient_scope` | 403 | The key lacks a scope this endpoint needs |
| `not_found` | 404 | The project (or the object) does not exist for this key |
| `unknown_parameter` | 400 | A body field the endpoint does not take |
| `invalid_parameter` | 400 | A field or query value is missing or wrong (`param` says which) |
| `invalid_json` | 400 | The body is not a JSON object |
| `rate_limit` | 429 | Too many requests; retry after `Retry-After` seconds |
| `idempotency_conflict` | 409 | The `Idempotency-Key` was used with a different request |
| `request_in_progress` | 409 | The first request with this `Idempotency-Key` is still running |
| `body_too_large` | 413 | A body over 64 KB sent with `Idempotency-Key` (uploads: the endpoint's own limit) |
| `api_error` | 500 | Our fault; safe to retry with the same `Idempotency-Key` |

Endpoint pages list the other codes they return (for example `call_state`, `insufficient_balance`,
`caller_id_not_verified`, `number_not_available`).

## Pagination

Lists return a page, newest first:

```json
{"data": [ ... ], "has_more": true, "next_cursor": "<opaque cursor>"}
```

Pass `limit` (1-200, default 50) and, for the next page, `starting_after=<next_cursor>`:

```sh
curl "https://api.contactivity.io/v1/projects/$PROJECT_ID/calls?limit=100&starting_after=$CURSOR" \
  -H "Authorization: Bearer $CONTACTIVITY_API_KEY"
```

A few small lists (conference participants, queue members, verified numbers, trunk credentials and IPs, available
numbers, catalogs such as voices) are a single page: `has_more` is always `false`. Their pages say so. The SDKs'
`iterate()` follows `next_cursor` for you.

## Archiving

Nothing you create is deleted. `DELETE` on a resource archives it and still returns `204`:

- It stops working at once: an archived API key cannot authenticate, a SIP credential or trunk IP cannot
  register or authenticate, a route no longer routes, a voice app, PBX app, flow or AI agent is handled like a
  missing one, an alert rule stops firing, a caller ID cannot be used, a queue refuses new callers, a knowledge
  base or document leaves search.
- Lists leave it out. Add `?archived=true` to a list to get only archived resources.
- `GET` by id still returns it, with `archived_at` set (an RFC 3339 timestamp; `null` while active).
- Changing it, or a second `DELETE`, is `409 archived`. Archiving is permanent: there is no restore.

Call data is never erased: calls, recordings and their audio, transcriptions, voicemail, AI agent transcripts,
summaries and data, and events. The one exception to archiving is a phone number: `DELETE /numbers/{phone_number}`
releases it. The Twilio- and Telnyx-compatible APIs archive in the same way (an archived resource answers `404`
there and leaves their lists); deleting an `IncomingPhoneNumbers` entry still releases the number.

Project retention settings (`retention` in `/v1/projects/{id}/compliance`) no longer remove anything: `PUT`
accepts them and changes nothing, and `GET` returns every retention value as `null` (data kept).

## Idempotency

Send `Idempotency-Key: <8-100 characters>` (a UUID is fine) on any `POST`. For 24 hours, the same key with the
same method, path and body returns the first response again, with `Idempotent-Replayed: true`, instead of acting
twice. So a `POST` that timed out can be retried safely with the same key.

- The same key with a different request: `409 idempotency_conflict`.
- The first request is still running: `409 request_in_progress`; retry shortly.
- With the header, the body is limited to 64 KB (`413 body_too_large`), except knowledge-base documents and
  voice-clone uploads, which keep their own limits.
- Ordering a number requires the header (`400 missing_parameter` without it).

The SDKs send a key on every `POST` (yours, or a generated one reused across retries).

## Rate limits

Requests are limited per project, 100 per second for API keys. Over the limit the API answers `429 rate_limit` with `Retry-After` (seconds).
Placing calls also has calls-per-second and simultaneous-call limits (`429 rate_limit` from `POST /calls`); see
`GET /limits`.

## Webhooks and events

Contactivity calls your URLs for CoXML documents, status callbacks, call-control events and AI agent tools. Every
request is signed with Standard Webhooks headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`,
Ed25519); the public key is at `GET /webhook-public-key`, and the SDKs verify it with
`verifyStandardWebhook` / `verify_standard_webhook`. Voice apps with `param_style: "contactivity"` receive events
in the native envelope `{id, type, created_at, project_id, data}`; see [events.md](/guides/events.md).

## Versioning

The version is in the URL (`/v1`). Additive changes (new endpoints, fields, event types, error codes) ship
without notice, so ignore fields you do not know. Breaking changes come as a new major version with at least
12 months of overlap; deprecated endpoints send `Deprecation` and `Sunset` headers.
