HTTP status codes
| Code | Meaning | When you see it |
|---|
200 | OK | Successful GET / action (stop, schedule, etc.) |
201 | Created | POST /v2/agent, POST /batches — resource created |
400 | Bad Request | Invalid or missing parameter — check message in the response body |
401 | Unauthorized | Missing or invalid API key — add Authorization: Bearer <key> |
403 | Forbidden | Valid key but insufficient permissions |
404 | Not Found | Resource ID doesn’t exist or belongs to another account |
429 | Too Many Requests | Rate limit hit — back off and retry with exponential backoff |
500 | Internal Server Error | Unexpected server error — also returned when scheduled_at uses the Z suffix (use +00:00 instead) |
Error response shape
All 4xx/5xx responses return JSON:
{
"error": 1001,
"message": "agent_id is required"
}
The error integer is an internal code; message is human-readable. The message field is the most useful for debugging.
Execution status enum
Returned in GET /executions/{id} and webhook payloads as the status field.
| Status | Type | Description |
|---|
scheduled | Intermediate | Call is scheduled for a future time |
queued | Intermediate | Call accepted and waiting to dial |
rescheduled | Intermediate | Call was rescheduled (e.g. due to guardrails) |
initiated | Intermediate | Dialing has started |
ringing | Intermediate | Recipient’s phone is ringing |
in-progress | Intermediate | Call answered, conversation active |
call-disconnected | Soft terminal | Line dropped — data still finalizing; completed follows |
completed | Terminal ✓ | Call finished; all fields populated |
no-answer | Terminal | Recipient didn’t pick up |
busy | Terminal | Line was busy |
failed | Terminal | Telephony provider error |
canceled | Terminal | Call canceled before answer |
stopped | Terminal | Manually stopped via API |
error | Terminal | Internal error |
balance-low | Terminal | Insufficient wallet balance to place call |
call-disconnected is not the final state. conversation_duration, total_cost, recording_url, and extracted_data are null or 0 at this point. Wait for completed (or any other terminal status above) before reading those fields.
Polling pattern
TERMINAL = {"completed","no-answer","busy","failed","canceled","stopped","error","balance-low"}
while True:
data = get_execution(execution_id)
if data["status"] in TERMINAL:
break # safe to read all fields now
time.sleep(5)
Batch status enum
Returned by GET /batches/{batch_id} as the status field.
| Status | Description |
|---|
created | Batch created, not yet scheduled |
scheduled | Scheduled and waiting for start time |
running | Calls are actively being placed |
completed | All calls finished |
stopped | Manually stopped |
failed | Error prevented execution |
Per-call status in a batch
Each execution inside GET /batches/{batch_id}/executions has an individual status from the execution enum above. The batch’s execution_status field gives a summary count: { "completed": 5, "no-answer": 2, "running": 3 }.
Scheduling errors
| Scenario | HTTP | Message |
|---|
scheduled_at uses Z suffix | 500 | Internal server error |
scheduled_at < 2 minutes ahead | 400 | ”Scheduled time should be atleast 2 minutes in the future” |
| Invalid ISO 8601 format | 400 | Parsing error |
Fix: always use a numeric offset: 2026-06-23T18:30:00+00:00.