Errors
The API uses conventional HTTP status codes and a consistent JSON error envelope so
you can branch on the machine-readable error code.
Error envelope
Errors return a JSON body with an error code and usually a human-readable
message. Validation errors add an errors map keyed by field.
{
"error": "unauthorized",
"message": "Invalid, expired, or revoked API key."
}
{
"error": "validation_failed",
"errors": { "recording_url": ["must be a valid URL"] }
}
Always branch on the error code, not the message text โ messages may be
reworded.
Status codes
| Status | Meaning | When it happens |
|---|---|---|
| 200 | OK | Successful read; also a duplicate POST /v1/calls (existing call returned). |
| 201 | Created | A new call or upload was accepted. |
| 400 | Bad request | Malformed request โ e.g. an unparseable JSON body, or a webhook with a bad signature. |
| 401 | Unauthorized | Missing API key, or the key is invalid, expired, or revoked. {"error":"unauthorized"} |
| 403 | Forbidden | The key is valid but lacks the required scope for this action. {"error":"forbidden"} |
| 404 | Not found | The resource does not exist, or it belongs to another company. {"error":"not_found"} |
| 409 | Conflict | The report is not ready yet. {"error":"report_not_ready","status":"analyzing"} |
| 422 | Unprocessable | Validation failed, a required field was missing (e.g. no_file), or the payload was empty. |
| 429 | Too many requests | Rate limit exceeded. See Rate limits. {"error":"rate_limited"} |
| 500 | Server error | An unexpected error on our side. Safe to retry with backoff. |
Writes are idempotent on external_call_id โ retrying a POST /v1/calls
after a timeout will not create a duplicate call. Retry 429 and 5xx with
exponential backoff.