Error response format
string
Machine-readable. Branch on this, not on
message — the wording may change,
the code will not.string
Human-readable, and localised where a translation exists — by the locale saved
on your account, falling back to
Accept-Language. Safe to show a user, not
safe to parse.object | array
Extra context for the codes that carry it —
required on
INSUFFICIENT_FUNDS, retry_after on RATE_LIMIT_EXCEEDED. An empty object
when there is nothing to add, so the key is always present.Correlation id for this request, also returned as the
X-Request-ID header.
Quote it when contacting support. You may send your own X-Request-ID on the
way in and it will be echoed back.Error codes
A missing resource and one owned by another account both answer
404. That is
deliberate: a 403 here would confirm the id exists.403 comes from a feature being switched off rather than from
permissions, details.feature names the flag — the same name
GET /flags reports, so you can check availability before calling.
Validation errors
VALIDATION_ERROR is the one code where details is an array rather than an
object — one entry per field that failed:
field is the dotted path to the offending value, starting with the part of the
request it came from (body, query, path, header).
Rate limits
Two limits apply, and either can produce a429.
A global limit of 100 requests per 60 seconds, with a burst allowance of 10.
It is counted per caller, identified in this order: your X-API-Key, then your
authenticated user, then your IP address. An API key and a bearer token for the
same account are therefore two separate budgets.
Per-route limits on the expensive endpoints, which are much tighter. For
example POST /ip-check allows 5 requests per 60 seconds with a burst of 2 —
each call holds a connection open for the length of three live third-party
lookups.
Reading a 429
The delay is reported in both places a client might look:Retry-After is always a whole number of seconds — never an HTTP-date — and
details.retry_after is the same number as a JSON integer. Use either.
A few limits have no meaningful wait to quote. Those answer
429 with an empty
details and no Retry-After; back off on your own schedule rather than
reading a missing header as zero.X-RateLimit-Reset is a Unix timestamp in seconds. The X-RateLimit-* headers
describe the global limit and accompany successful responses too, so you can
slow down before being refused.
Handling errors
The Python SDK maps each status onto its own exception, so the common cases areexcept clauses rather than branches on a code:
409 covers both a genuine conflict and an empty wallet, so error.code is what
separates them. Every exception carries status_code, code, request_id and
the parsed response_body — read details from the last of those when you need
it, for example exc.response_body["error"]["details"]["required"].
Retry 429, 500 and 503. Do not retry 4xx codes other than 429 — the
request will fail the same way until you change it. When retrying anything that
spends money, send an Idempotency-Key so a retry cannot charge twice; see
Idempotency.