Developer docs: Pagination, errors and limits
Pagination, errors and limits
The rules every endpoint follows, so one client handles all of them.
Formats
- JSON in and out, UTF-8, over HTTPS only.
- Times are ISO 8601 in UTC (
2026-10-16T12:00:00.000Z). Date filters accept a date (2026-10-16) or a full timestamp; a date-only end covers that whole day. - IDs are UUIDs. A malformed id answers 400; an unknown one, or one outside your key's scope, 404.
- Phone numbers are E.164 (
+15551230001). - Fields are camelCase. Responses may gain new fields at any time; ignore the ones you do not know.
Pagination
List endpoints take page (from 1) and limit (default 20, at most 100; a larger value is clamped). The response carries the totals:
{
"success": true,
"data": [
"…"
],
"pagination": {
"page": 2,
"limit": 50,
"total": 137,
"pages": 3
}
}Every list has a unique tiebreaker (the record id), so pages never repeat or skip a record that did not change.
Filtering and sorting
Each list documents its filters in the reference (status, agent, direction, date ranges and more). sort picks the field and order is asc or desc (default desc, newest first).
Incremental sync and caching
Calls, leads, appointments and scheduled calls accept updatedSince. Request ?updatedSince=<cursor>&sort=updatedAt&order=asc, page through, and save the last updatedAt as the next cursor (full example). Cancelled appointments are hidden unless you pass includeCancelled=true.
GET responses carry an ETag. Send it back as If-None-Match and an unchanged response answers 304 Not Modified with no body.
Errors
A failed request answers with an HTTP status and a stable code from our error registry. Switch on error.code; message is for people and may be reworded.
{
"success": false,
"message": "limit must not be less than 1",
"error": {
"code": "VALID_001",
"message": "limit must not be less than 1",
"details": {
"validation": [
"limit must not be less than 1"
]
}
},
"meta": {
"timestamp": "2026-10-16T12:00:00.000Z",
"path": "/api/v1/external/calls-history?limit=0"
}
}Server errors answer 500 with code INTERNAL. They are safe to retry with backoff; writes should carry an Idempotency-Key.
| Status | Code | Message | When |
|---|---|---|---|
| 400 | CALL_004 | No phone number assigned | The agent has no phone number to call from |
| 400 | PHONE_004 | Number already assigned | The agent already has a number |
| 400 | SCHED_002 | Invalid schedule time | scheduledAt must be in the future |
| 400 | SCHED_010 | Choose a lead or enter a phone number, not both | Send leadId or phoneNumber, not both |
| 400 | VALID_001 | Validation failed | A query or body field is invalid or not supported (see error.details.validation) |
| 400 | WEBHOOK_003 | Webhook URL must use https | URL must use https |
| 400 | WEBHOOK_004 | Webhook URL must resolve to a public address | URL must resolve to a public address |
| 400 | WEBHOOK_005 | Unknown webhook event | Unknown webhook event name |
| 401 | APIKEY_002 | Invalid or revoked API key | X-API-Key missing, unknown, expired or revoked |
| 403 | APIKEY_003 | API key is missing a required scope | The key lacks a scope this endpoint requires (details.required / details.granted) |
| 403 | AUTH_006 | You do not have permission to perform this action | Agent-bound key used on an organization-wide resource or another agent |
| 403 | EXT_010 | This feed is not available to this API key | Demo-line feed, enabled partner accounts only |
| 403 | PLAN_001 | This feature is not included in your current plan | Your plan does not include API access (Enterprise) |
| 403 | PLAN_002 | Plan limit reached | A plan limit was reached (agents, transfer contacts, ...) |
| 404 | AGENT_001 | Agent not found | Agent not found, inactive or outside the key scope |
| 404 | APPT_001 | Appointment not found | Appointment not found or outside the key scope |
| 404 | CALL_002 | Call not found | Call not found or outside the key scope |
| 404 | LEAD_001 | Lead not found | Lead not found or outside the key scope |
| 404 | PHONE_001 | Number not found | Number not found |
| 404 | REC_001 | Recording not found | The call has no recording |
| 404 | SCHED_001 | Scheduled call not found | Scheduled call not found or outside the key scope |
| 404 | WEBHOOK_001 | Webhook endpoint not found | Webhook subscription not found |
| 409 | IDEM_001 | Duplicate request | Same Idempotency-Key still in flight, or used on another endpoint |
| 409 | REC_010 | This recording is still held by the phone provider and is not available to play here yet | The recording is still at the phone provider and cannot be served yet |
| 409 | SCHED_004 | Scheduled call can no longer be changed | The scheduled call is no longer pending |
| 422 | IDEM_002 | Idempotency key was already used with a different request body | Idempotency-Key reused with a different request body |
| 429 | RATE_001 | Rate limit exceeded | Rate limit exceeded; wait for Retry-After seconds |
Rate limits
Each API key may make 60 requests per minute by default, across all endpoints and IP addresses. Every response reports your budget for the current window:
X-RateLimit-Limit-external: 60
X-RateLimit-Remaining-external: 57
X-RateLimit-Reset-external: 42Reset is in seconds. Over the limit you get 429 RATE_001 with a Retry-After header (seconds): wait that long, then retry. Need more? Tell us your expected volume through sales.
Idempotency
Network failures happen after the server has acted. To retry a write safely, send an Idempotency-Key header (1 to 200 characters; a UUID is ideal). For 24 hours, a retry with the same key and the same body replays the first response instead of running again.
- A retry while the first request is still running answers
409 IDEM_001; wait and retry. - The same key with a different body answers
422 IDEM_002, never someone else's result. - A request that failed does not keep its key, so you can retry it with the same key.
- Keys are private to your API key; they never collide with another customer's.
Endpoints that accept it:
POST /calls/outboundPOST /scheduled-callsPOST /agentsPOST /numbers/{id}/assignPOST /webhooks