Contactivity
Getting started.md

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 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.

Page Resources
calls.md Calls and call actions (speak, gather, bridge, record, redirect, ...)
recordings.md Recordings and transcriptions
conferences-queues.md Conferences, participants, queues, queue members
numbers.md Number search, ordering and routing; verified numbers
trunks-routes.md SIP trunks, credentials, IP allow lists, routes, limits, webhook public key
voice-apps.md Voice apps, PBX apps, client tokens, push credentials, pricing
ai-agents.md AI agents (versions, traffic, tests, calls, analytics) and AI providers
flows.md Flow validation and versions
knowledge-bases.md Knowledge bases and documents
billing.md Usage, call detail records, balance, ledger
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). 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.

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.