Errors
Every error response is JSON with a stable shape:
{ "error": { "status": 401, "code": "invalid_key", "message": "API key is invalid or revoked." }, "request_id": "req_abc"}Forward the request_id to support when contacting us — it lets us trace exactly
which call failed.
Error catalogue
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_address | Missing or too-short address parameter. |
| 400 | invalid_url | URL parameter missing or fails our URL validator. |
| 400 | invalid_id | Listing id is malformed. |
| 400 | invalid_filter | A filter key is unknown or value is out of range. |
| 400 | missing_field | Required field absent from request body. |
| 401 | unauthenticated | No Authorization header. |
| 401 | invalid_key | API key not found or revoked. |
| 403 | forbidden | Authenticated, but lacks scope to access this resource. |
| 404 | not_found | Listing or job not found. |
| 409 | conflict | Resource already exists (e.g. duplicate webhook URL). |
| 422 | validation_failed | Body fails JSON schema validation. |
| 429 | rate_limited | RPM threshold exceeded for your plan. |
| 429 | quota_exceeded | Account is out of credits. Top up or wait for renewal. |
| 500 | internal_error | Unhandled server-side error. We get paged. |
| 502 | upstream_failure | Upstream lookup failed. Retry-safe with exponential backoff. |
| 504 | upstream_timeout | Upstream took too long. Retry-safe. |
Retrying
429 rate_limited— back off until your next minute and retry.429 quota_exceeded— do not retry. Top up or wait.5xx— retry with exponential backoff, capped at 60s.
import httpx, time
def call_with_retry(url, headers, retries=3): for attempt in range(retries): r = httpx.get(url, headers=headers, timeout=30) if r.status_code < 500 and r.status_code != 429: return r if r.status_code == 429 and r.headers.get("retry-after"): time.sleep(int(r.headers["retry-after"])) else: time.sleep(2 ** attempt) return r