Place an outbound call via the PBX
Places an outbound call from a workspace number (from) to one or
more destination numbers (to). The call is dialed immediately and
the response returns before the remote party answers — poll
GET /v1/calls/{id} or subscribe to call.answered /
call.completed webhooks for final status.
A wallet pre-flight check fires before the PBX is contacted:
if the workspace balance is zero the request is rejected 402
(wallet.insufficient_balance) without a PBX leg being created.
Idempotency & correlation: the Idempotency-Key header scopes
deduplication — repeated requests with the same key within the
active leg's lifetime return the original response. The header
value is also forwarded to the PBX as its clientRequestId, but
the PBX does not reliably echo it back on callbacks — correlate
on the call_id / session_id returned by this endpoint
instead (they appear on call.completed / call.failed
webhook payloads as call_id / pbx_call_id).
Authorization
bearerAuth 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
Header Parameters
Client-supplied idempotency key. Reusing the same key for the same workspace within a live leg returns the original call row instead of a duplicate dial.
length <= 64Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/calls" \ -H "Content-Type: application/json" \ -d '{ "from": "+254712345678", "to": [ "+254700000001" ] }'{ "call_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0", "session_id": "HD_1a2b3c", "status": "ringing"}{ "error": { "code": "validation.bad_request", "message": "`from` is required", "request_id": "req-a1b2c3d4" }}{ "error": { "code": "validation.bad_request", "message": "string", "request_id": "string" }}{ "error": { "code": "wallet.insufficient_funds", "message": "Unable to place call. Insufficient balance on your account. Top up to continue.", "request_id": "req-a1b2c3d4" }}{ "error": { "code": "validation.bad_request", "message": "string", "request_id": "string" }}{ "error": { "code": "validation.bad_request", "message": "string", "request_id": "string" }}{ "error": { "code": "validation.bad_request", "message": "string", "request_id": "string" }}List call detail records for the active workspace GET
Returns the workspace's call detail records, most-recent first. Cursor pagination via `cursor` (opaque, returned as `next_cursor` when more results exist). Filters: `status` (CSV), `direction`, `q` (substring on `remote_e164` or `session_id`), `from` / `to` (RFC3339 or unix seconds), `session_id` (exact match), `limit` (1–100, default 25).
Workspace stats (calls today / failed / minutes / spend) GET
Aggregates over the window selected by `since`. Supported values: `today` (default), `7d`, `month`. Currency follows the workspace wallet currency. **Eventual consistency.** `spend_minor` and (to a lesser extent) `minutes_total` are finalized asynchronously, after the call's `call.completed` event has been processed. The lag between hang-up and stats reflecting final cost is typically under 5 seconds, and can be longer at times of high load. Customers building real-time dashboards SHOULD poll this endpoint no more frequently than every 30 seconds. For per-call cost in near-real-time, subscribe to the `call.completed` webhook event instead — its payload carries the final `cost_minor` after rating.