Workspace stats (calls today / failed / minutes / spend)
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.
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
Query Parameters
Value in
- "today"
- "7d"
- "month"
Response Body
application/json
application/json
curl -X GET "https://example.com/v1/calls/stats"{ "total": 0, "failed": 0, "minutes_total": 0, "spend_minor": 0, "currency": "string"}{ "error": { "code": "validation.bad_request", "message": "string", "request_id": "string", "details": [ "string" ] }}Place an outbound call via the PBX POST
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` when no `client_request_id` label is sent, and the PBX echoes that field back on callbacks. To correlate on identifiers Sautikit issues instead, use the `call_id` / `session_id` returned by this endpoint (they appear on `call.completed` / `call.failed` webhook payloads as `call_id` / `pbx_call_id`). Loopback label: send `client_request_id` (or its camelCase alias `clientRequestId`) to attach your OWN correlation handle to the call — an order id, a ticket number, your own uuid. It becomes the call's `clientRequestId` on the PBX, which echoes that field back verbatim, so an asynchronous callback can be tied to whatever the call was placed for without keeping a map of our identifiers: - on the 201 response, under whichever of the two spellings you sent; - as the `clientRequestId` field of every PBX callback and event body — the voice callback, per-number event forwarding, and `call.*` webhook deliveries; - on your voice callback URL as `?clientRequestId=…`. Callback and event bodies are forwarded exactly as the PBX sent them; nothing is rewritten to carry your label. The label appears in them because it IS the call's identifier on the box. Because the PBX treats `clientRequestId` as an idempotency hint, a label is a per-call identity: use a distinct one per call rather than reusing a single label across a campaign. Deduplication of your own retries is still `Idempotency-Key`, which the label replaces on the wire when both are sent. Labels beginning `mcp:` or `broadcast:` are refused (400) — those prefixes are reserved for internal call routing. Calls placed with `control: "mcp"` keep the internal token on the wire, so a label sent with one is echoed on the 201 response only. Which labels come back on callbacks: the PBX refuses a `clientRequestId` containing **whitespace, commas, braces or quotes**, so a label carrying any of those cannot ride the call. The call is still placed — it carries an internal token instead — and your label is echoed on the 201 response, but it will NOT appear on callback or event bodies. A JSON document is therefore not usable as a label; send an opaque id (order number, ticket id, uuid) and keep the structure on your own side. Large labels: a label whose percent-encoded form exceeds 2048 bytes is omitted from the `?clientRequestId=` query parameter. A request line that long is answered 414 by common webserver defaults (nginx, Apache), and that rejection would land on YOUR endpoint and break the call's routing rather than just its correlation. The callback body still carries it whatever its size, since that is the PBX's own field. Keep a label under ~2000 characters to have it on the query string too.
Get one call by id GET
Next Page