Skip to content

Management API (v1) ​

Server-to-server REST API for a consumer's backend: provisioning, outbound calls, call history and control. Base path /v1, JSON in and out.

Which endpoints you need. Most integrations use a handful; the rest are there when you need them. Your scenario page shows the exact calls for your kind of product.

Who needs itEndpointsUsed for
Every integrationPUT /v1/tenants/{t}, PUT …/agents/{ref}, PUT …/channels/{ref}The workspace, the people who take calls, the numbers
Every integration (recommended)PUT /v1/webhookReceiving events
Phone numbers (SIP)GET /v1/sip-trunks · GET /v1/numbersThe trunks your SIP lines can use, and your numbers
Shared lines (a team)PUT …/queues/{ref}, PUT …/queues/{ref}/membersWhere calls wait and who answers
Mobile appsPUT /v1/push-credentials/{provider}, PUT …/agents/{ref}/push-tokens/{deviceId}Ringing a phone in the background
Outbound callsPOST /v1/tenants/{t}/callsPlacing a call for an agent
WhatsApp through your own Meta appPOST /v1/webhooks/whatsapp/forwardPassing Meta's webhooks on
Optional featuresIVR flows, audio assets, reports, call history and recordings, PUT …/agents/{ref}/do-not-disturb, dashboard launchMenus, prompts, dashboards, records
Housekeepingkeys, events, deleting, erasing a customerRotation, missed webhooks, cleanup, privacy

Authentication ​

Authorization: Bearer ck_<slug>_<secret>

The first API key comes with the consumer (the operator sends it to you); more are issued and revoked through Keys. Keys are stored hashed; several can be active at once, so a key is rotated without downtime. Every request is scoped to the calling consumer — its tenants, their agents, queues, channels and calls. Anything else is 404.

Rate limit: 1200 requests per minute per consumer, in fixed one-minute windows (429 with Retry-After in seconds).

Conventions ​

  • Address entities by your own ids. {tenantRef}, {agentRef}, {queueRef}, {channelRef}, {flowRef} are your references (1–191 characters); you never need to store Calloop's ids.
  • PUT creates or replaces. A field you omit is reset to its default, not kept (e.g. omitting status sets it back to ACTIVE — a tenant PUT without status lifts a suspension). Kept when omitted: a tenant's settings (also when null; send {} to clear them), an agent's role and do-not-disturb, a channel's credentials, a SIP trunk's credentials and tech_prefixes, a rate sheet's currency.
  • Responses wrap the entity in a named key ({ "tenant": … }, { "queues": [ … ] }); fields are camelCase; linked entities are returned as Calloop ids (overflowQueueId, inboundQueueId, channelId). DELETE returns 204 with no body.
  • Calloop's numeric ids are returned in responses and events for convenience. Enum values in bodies are case-insensitive and returned uppercase (except a call detail's recording.status and an audio asset's storageProvider, which are lowercase).
  • Request bodies are limited to 10 MB (15 MB for audio uploads, 20 MB for rate sheet imports).
  • Retry POSTs safely with an Idempotency-Key — see Idempotency. PUT, PATCH, GET and DELETE are idempotent already.
  • Versions. /v1 grows by adding: new endpoints, new optional request fields, new response fields, new enum values and new event types. Ignore fields and event types you don't know, so additions never break you. Each event carries the api_version of its payload shape (Events).
  • The operator is the Calloop team that runs the platform: it creates your consumer, sends your first keys, and sets your account's plan and limits.

Idempotency ​

Send Idempotency-Key: <any unique string, 1–255 chars> on a POST (e.g. a UUID per logical request). If the request is retried with the same key:

SituationResponse
The first request finished with 2xx or 4xxThat same status and body again, with Idempotent-Replayed: true. Nothing runs twice
The first request is still running409 idempotency_key_in_use, Retry-After: 1
Same key, different method, path or body422 idempotency_key_reused
The first request failed with 5xxThe retry runs normally (a 5xx isn't stored)

Keys are per consumer and kept for 24 hours. POST /v1/api-keys, POST /v1/signing-keys and POST /v1/webhook/secret are never replayed (the stored response would hold the new secret): a retry issues another key or rotates the webhook secret again; revoke the key you don't use, or use the newest secret. A request without the header behaves as before. A key whose first request never finished (the worker died) can be reused after 2 minutes. An empty key or one over 255 characters is a 400. The body is compared after parsing, as JSON text: the same fields in a different order count as a different body (422 idempotency_key_reused).

Errors ​

Every error from the /v1 endpoints in this document has one shape:

json
{ "error": { "code": "invalid_request", "message": "name is required" } }

Some errors add details (for in_use: what is using the entity). A 400 about one field names it in details.field, as you sent it — a nested field with its path — so a form can show the message next to that field:

json
{
  "error": {
    "code": "invalid_request",
    "message": "members[2].priority must be an integer between 1 and 1000",
    "details": { "field": "members[2].priority" }
  }
}

A 400 that isn't about one field (malformed JSON, a body over no field) has no details. 409 invalid_customer on POST …/calls carries details.field: "customer.address" too. Strings are trimmed; one of only spaces counts as missing (name is required). Two exceptions: a path that isn't an endpoint gets the web server's plain 404 body, and the Meta webhook routes (Webhooks in) answer with a bare status, except 404 not_enabled (below), which uses the error shape.

An unknown outbound call's agent_ref (body), or an unknown agent_ref / tenant_ref query filter, is a 404 not_found. Any other unknown *_ref in a body or query, including a queue's members[].agent_ref and a line's owner_agent_ref, is a 400 invalid_request.

StatuscodeWhen
400invalid_requestA field is missing, has the wrong type or is out of range (details.field names it); a *_ref in the body matches nothing; malformed JSON
401unauthorizedMissing or invalid API key
403consumer_suspendedThe consumer is suspended
404not_foundThe entity doesn't exist or isn't yours
404not_enabledPOST /webhooks/whatsapp (not under /v1) when direct Meta ingress isn't configured
402tenant_outbound_blocked, tenant_suspended, tenant_insufficient_balancePOST …/calls for a tenant whose own account with you (Pricing your tenants) can't pay for it
402outbound_blocked, account_suspended, insufficient_balancePOST …/calls your billing account can't pay for: outbound blocked (no balance, over the credit limit, an invoice overdue), suspended, or not enough balance or credit for the call's first billing block at the destination's price — message says which and how much
403line_not_ownedPOST …/calls from another agent's personal line — see Outbound calls
403destination_blocked, destination_not_allowed, emergency_not_supported, rate_too_highPOST …/calls the platform's call rules refuse: a blocked range (high-risk, premium), a country your plan doesn't allow, an emergency short code (Calloop has no emergency calling), a destination dearer a minute than your plan allows
409too_many_callsPOST …/calls while your plan's calls at once are all in use
409invalid_channel, channel_disabled, unsupported_channel, invalid_agent, agent_busy, invalid_customerPOST …/calls — see Outbound calls
409call_endedPOST /v1/calls/{id}/terminate on a call that already ended
409call_activeDeleting data before the call ended
409idempotency_key_in_useA request with this Idempotency-Key is still running
409in_useA DELETE of something still in use — see Deleting
409last_key, kid_takenRevoking your last active API or signing key; a kid that exists — see Keys
409tenant_activeDeleting a tenant that isn't SUSPENDED — see Deleting
403 409 429 503self_service_off; exists, too_many_trunks, suspended, in_use, invalid_state, no_addresses; too_soon; sip_unavailableSIP trunks
400 403 409invalid_request on currency; forbidden; in_use, not_prepaid, not_openPricing your tenants
413invalid_requestRequest body over the size limit
422idempotency_key_reusedThe Idempotency-Key was used with a different request
429rate_limitedOver the rate limit
500internal_errorUnexpected failure
503storage_unavailableGET /v1/calls/{id}/recording when the store holding it can't be reached; deleting data when a recording file can't be deleted; audio-asset URLs without object storage

Calloop — the call engine behind your product.