Error envelope
Every error response uses the same JSON shape:code is a stable machine-readable identifier; message is human-readable
and may change. Always switch on code, not message.
Status codes
| Status | Code | Meaning | Retry? |
|---|---|---|---|
400 | bad_request | Malformed request body or invalid IP format | No — fix the request |
401 | unauthorized | API key / gateway auth missing or invalid | No — fix credentials |
403 | forbidden | Key tier does not permit this endpoint or field | No — upgrade tier |
404 | not_found | Resource doesn’t exist (rare) | No |
422 | validation_error | Pydantic validation failed | No — fix payload |
429 | rate_limited | Too many requests | Yes — see headers |
500 | internal_error | Unexpected server error | Yes — exponential backoff |
503 | unhealthy | Service degraded | Yes — exponential backoff |
400 bad_request carries a more specific code in the
envelope — one of invalid_ip_reserved (RFC-reserved / non-global address),
invalid_ip_integer_form (bare integer not accepted), or invalid_ip_format
(unparseable). Switch on these subcodes when you need to distinguish why an IP
was rejected.
Rate-limit headers
Every response carries the current rate-limit state:429 response adds two headers telling you when to retry:
X-RateLimit-Limit is your request ceiling, expressed as requests per
60-second window (not per second). X-RateLimit-Remaining is how many requests
remain in the current window. X-RateLimit-Window is the window length in
seconds. On a 429, Retry-After is the recommended wait in seconds and
X-RateLimit-Reset is the Unix timestamp when the next slot frees (≈
now + Retry-After). Because this is a sliding-window limiter, the exact reset
instant is only defined at denial time, so X-RateLimit-Reset appears on 429
responses only — prefer Retry-After for backoff.
Retry strategy
For transient errors (429, 500, 503):