Send a waiting caller to an AI agent
Sends an answered caller who has no extension yet to a published AI agent. It works once per caller and only while the caller is still held (hold, hold line or callback offer): a caller already connected to a person, already with an AI, or who has hung up returns 409 dialer.invalid_state, and no second AI leg is ever started. 202 means the request is recorded; the call moves within one hold step (a few seconds), or at once when the phone system supports redirect (via: redirect). The AI minutes are billed to the workspace wallet as dialer_overflow. If the agent cannot take the call, the caller carries on down the overflow ladder (callback offer, then apology), never into silence. Needs dialer.supervise.
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
Path Parameters
The attempt_id of a waiting caller on the live board.
uuidRequest 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
curl -X POST "https://example.com/v1/dialer/calls/497f6eca-6276-4993-bfeb-53cbbbba6f08/handoff-to-ai" \ -H "Content-Type: application/json" \ -d '{}'{ "attempt_id": "7a838dca-3ea8-4a7c-8133-ba2c86aeb22d", "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", "status": "requested", "via": "ladder"}{ "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" }}{ "error": { "code": "validation.bad_request", "message": "string", "request_id": "string", "details": [ "string" ], "resolution": "string", "reason": "invalid_characters", "suggested_e164": "+254727524723" }}Stream campaign events GET
Opens a `text/event-stream` (SSE) connection that pushes the campaign's events as they happen. Every frame has an `event:` line and carries its kind inside the JSON as well: `data:` is `{"kind": "...", "event": "...", "data": {...}}` where `data` is the payload. Kinds: `dialer.call.started`, `dialer.call.answered`, `dialer.call.waiting` (also `contact_name`, `phone`, `step`), `dialer.call.connected`, `dialer.call.handed_to_ai`, `dialer.call.abandoned`, `dialer.call.completed`, `dialer.callback.scheduled`, `dialer.campaign.paused`, `dialer.campaign.completed`, `dialer.campaign.ratio_changed`, `dialer.budget.warning` (the webhook payloads), `dialer.session.state` (`id`, `extension_id`, `user_name`, `state`, `since`), `dialer.session.removed` (`id`), `dialer.stats` (`in_flight`, `answer_rate`, `ratio`, `abandon_rate_pct`; at most every 5 seconds) and `dialer.access_removed` (the last frame: the supervisor lost the role). Frames that come from an event carry `id:` (the event id; drop repeats). The stream ends after 30 minutes and is re-authorised while open, so reconnect and reload `.../live`. Delivery is best effort: events during a disconnect are not replayed. Needs `dialer.supervise`.
List do-not-call numbers GET
Workspace-wide. A suppressed number is never dialed by any campaign, including one already running.