Account
Errors
The one error envelope, every error code, and whether to retry.
Every failure returns the same JSON envelope. Never an HTML page, never a bare string:
JSON
{ "error": { "code": "not_found", "message": "unknown metro 'boston'", "request_id": "req_7431b51e10ec2b77" } }| Field | Meaning |
|---|---|
code | One value from a closed list (below). Branch on this. |
message | A human-readable explanation. It can change; do not parse it. |
details | Optional machine-readable context, such as the parameter that failed or the scopes your key has. |
request_id | The id of this request. Quote it when you contact support. |
Error codes
Branch on code, not the status alone: 403 and 429 each cover codes with different remedies.
| Status | code | Retry? | What to do |
|---|---|---|---|
| 400 | bad_request | No | Fix the request; details usually names the parameter |
| 401 | unauthorized | No | Send a valid, unrevoked key in X-API-Key |
| 403 | forbidden | No | Your key is not scoped for this metro or type; details lists what it can read |
| 403 | include_not_permitted | No | Drop the include=, add include_mode=lenient, or use an entitled key |
| 403 | feature_not_in_plan | No | The endpoint needs a feature your plan does not include; details.feature names it |
| 404 | not_found | Only if the input changes | Check the metro, id or slug |
| 405 | method_not_allowed | No | Use GET; the API is read-only |
| 429 | rate_limited | Yes, after Retry-After seconds | Wait, then retry |
| 429 | quota_exceeded | No | The month's allowance is spent; Retry-After points at the start of next month |
| 500 | internal | Once, with backoff | If it recurs, contact support with the request_id |
| 501 | not_implemented | No | This feature is not available here; do not build on it |
| 503 | unavailable | Yes, after Retry-After | Back off and retry |
New codes can be added over time, so treat an unknown code by its status.
Handling errors in code
JavaScript
const res = await fetch(url, { headers: { "X-API-Key": process.env.TAILEND_API_KEY } });
if (!res.ok) {
const { error } = await res.json();
if (error.code === "rate_limited" || error.code === "unavailable") {
const wait = Number(res.headers.get("Retry-After") ?? 1);
// wait `wait` seconds, then retry once
} else {
console.error(error.code, error.message, error.request_id);
}
}request_id
Every response, success or failure, carries a request id: in the X-Request-Id header, and in the body of an error. Log it next to your own request context and quote it when you contact support; it is how we find your request.
Checklist
- Check
error.codebefore deciding whether to retry. - On a
429, readRetry-Afterinstead of guessing a backoff: the two codes mean very different waits. - Do not retry
400,401,403,404or501unchanged.