API Reference
Calls

Terminate an active call

POST
/v1/calls/{call_id}/hangup

Requests termination of an in-progress call via the PBX. The browser SDK dials directly over WebRTC; this server-side endpoint is the control-plane hangup used to force-end a live call.

Workspace isolation: call_id must belong to the active workspace, otherwise this endpoint returns 404 (cross-tenant existence is not leaked).

Idempotent: if the PBX reports the call already ended, the request still succeeds. Addressable by either the Sautikit call row UUID (the call_id from POST /v1/calls / the id from GET /v1/calls) or the PBX session id (HD_…) the same call carries on the wire — the server resolves the stored PBX session itself either way.

Authorization

bearerAuth
AuthorizationBearer <token>

Long-lived ES256 JWT minted from the dashboard (https://app.sautikit.com/developers/api-keys). Signed by the platform keyring. Carries workspace_id and scopes claims; revoked via the platform deny-list.

In: header

Path Parameters

call_id*string

The call's Sautikit UUID, or the PBX session id the same call carries on the wire (HD_…, returned as session_id on POST /v1/calls and on every webhook). Either form addresses the same call.

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/calls/string/hangup"
{  "call_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0",  "status": "hung_up"}
{  "error": {    "code": "validation.bad_request",    "message": "string",    "request_id": "string",    "details": [      "string"    ],    "resolution": "string",    "reason": "invalid_characters",    "suggested_e164": "+254727524723"  }}
{  "error": {    "code": "validation.bad_request",    "message": "string",    "request_id": "string",    "details": [      "string"    ],    "resolution": "string",    "reason": "invalid_characters",    "suggested_e164": "+254727524723"  }}
{  "error": {    "code": "validation.bad_request",    "message": "string",    "request_id": "string",    "details": [      "string"    ],    "resolution": "string",    "reason": "invalid_characters",    "suggested_e164": "+254727524723"  }}
{  "error": {    "code": "validation.bad_request",    "message": "string",    "request_id": "string",    "details": [      "string"    ],    "resolution": "string",    "reason": "invalid_characters",    "suggested_e164": "+254727524723"  }}

Live Server-Sent Events stream of a call's lifecycle GET

Opens a `text/event-stream` (SSE) connection that pushes call lifecycle transitions (ringing, answered, completed, failed, …) as they happen. Intended for the in-app dialer's live call view. Past lifecycle events are also embedded inline on the Call resource (`Call.events`) — this endpoint is only for the live tail; there is no separate read endpoint for historical events. For a call handled by an AI agent the same stream also carries the live transcript, as two additional event kinds: - `transcript.turn` — a complete, flushed turn (`{kind, call_id, seq, role, text, final, occurred_at}`). The durable copy of the same turns is on `GET /v1/calls/{id}?transcript=1`. - `transcript.delta` — an incremental chunk of the turn being spoken right now (`delta` instead of `text`). Stream-only: partials are never persisted. The stream opens with a `: ready` SSE comment the moment it attaches — useful as a connection check, and ignorable by any conforming SSE client. A subscriber that joins mid-call is replayed the last 20 transcript events so it does not sit silent until the next turn. Neither transcript kind is available as a workspace webhook — they are per-call and far too chatty for HTTP fan-out. Read them here, with an API key (`calls.read`) like any other call read. Note that a browser `EventSource` cannot send an `Authorization` header, so an API-key subscriber must be server-side (curl, `fetch` with a reader, or any SSE client that sets headers). The connection stays open until the call reaches a terminal state or the client disconnects. The two id forms behave slightly differently by design. A session id (`HD_…`) attaches immediately — the browser dialer knows it before the call row exists, and subscribing early is the point, so no existence check is made; the channel is always scoped to the calling workspace, so an unknown session id simply yields a stream that never emits. A call UUID is looked up, and 404s when this workspace owns no such call. A call whose PBX session has not landed yet holds the request open for a few seconds rather than failing, since origination returns the UUID before the box calls back.

Get the call's active supervision GET

`supervision` is `null` when no supervisor is on the call. **Who can call this:** a signed-in workspace owner or admin, or a member holding the Supervisor licence — or an API key or OAuth token with the `calls.read` scope. Seeing who is listening in on a call is itself treated as supervisory access.