Error Handling & Rate Limits
Every error comes back as predictable JSON with a status, a machine-readable code and a human message. Here is the full map — and how to handle rate limits robustly.
Build integrations that fail gracefully.
Log in to the ConsoleThe error model
Every error response uses the same JSON shape:
{
"status": 400,
"code": "invalid_date_format",
"message": "start_date must be a valid date in YYYY-MM-DD format."
}
| Field | Description |
|---|---|
| status | The HTTP status code |
| code | A stable, machine-readable identifier you can switch on in code |
| message | A human-readable explanation |
Always switch on code, never on the message text — messages may change, codes won't.
Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | missing_parameter | A required parameter (e.g. service_number) wasn't provided |
| 400 | invalid_phone_format | Phone number isn't a valid Australian number |
| 400 | invalid_date_format | start_date/end_date isn't valid YYYY-MM-DD |
| 400 | invalid_date_range | start_date is after end_date |
| 400 | last_forwarding_number | Can't remove the last forwarding number on a service |
| 400 | unsupported_routing_type | The service uses IVR/advanced routing, not list forwarding |
| 401 | unauthorized | Missing, invalid or expired Bearer token |
| 403 | unauthorized_service | The service belongs to a different account |
| 404 | service_not_found | No service exists with that ID |
| 405 | method_not_allowed | Wrong HTTP method for the endpoint |
| 429 | too_many_requests | Rate limit exceeded |
| 500 | internal_error | Unexpected server error |
Handling rate limits
Rate limiting is communicated through headers on every response:
X-RateLimit-Limit— max requests in the window.X-RateLimit-Remaining— requests left in the window.X-RateLimit-Reset— Unix timestamp when the window resets.
When you exceed the limit you get 429 too_many_requests. A robust client respects these rather than hammering the endpoint.
Respecting rate limits in JavaScript
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
const remaining = res.headers.get("X-RateLimit-Remaining");
const resetAt = Number(res.headers.get("X-RateLimit-Reset"));
if (res.status === 429) {
const waitMs = Math.max(0, resetAt * 1000 - Date.now()) + 1000;
console.log(`Rate limited — retrying in ${Math.round(waitMs / 1000)}s`);
await new Promise(r => setTimeout(r, waitMs));
// ... retry the request
}
Backoff for transient errors
For 429 and 5xx errors, retry with exponential backoff — start short and grow:
const delay = (ms) => new Promise(r => setTimeout(r, ms));
async function apiGet(url, retries = 5) {
for (let attempt = 1; attempt <= retries; attempt++) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
if (res.status < 500 && res.status !== 429) return res;
const waitMs = Math.min(1000 * 2 ** (attempt - 1), 15000);
console.log(`Retry ${attempt} in ${waitMs}ms`);
await delay(waitMs);
}
throw new Error("Max retries exceeded");
}
Conventions that reduce errors
- Read before you write. Before a
DELETE, fetch the current routing so you never hitlast_forwarding_number. - Validate dates client-side. Send
YYYY-MM-DDand ensurestart_date <= end_date. - Use Australian formats.
0298765432/+61298765432or0412345678/+61412345678. - Send the right method. Use
PUTfor updates,POSTfor creates,DELETEfor removals — wrong methods return405.
Related
- Authentication — token errors and security.
- Call detail records — the most common
4xxcases (missing_parameter,invalid_date_format). - Manage forwarding —
invalid_phone_formatandlast_forwarding_number. - Code examples — ready-to-use snippets.
