Errors
Errors use standard HTTP status codes and a consistent JSON body.
Error format
400 Bad Request
{
"error": {
"code": "invalid_parameter",
"message": "limit must be between 1 and 100",
"status": 400
}
}Branch on error.code, which is stable. error.message is for humans and may change.
Error codes
| Status | Code | What it means |
|---|---|---|
| 400 | invalid_parameter | A query parameter is missing, malformed or out of range. The message names the parameter. |
| 401 | missing_api_key | No key was sent. Use the Authorization: Bearer header, X-API-Key, or ?api_key=. |
| 401 | invalid_api_key | The key doesn't exist or was revoked. Create a new one in the dashboard. |
| 403 | plan_required | The feature isn't in your plan (for example, the WebSocket stream on Free). |
| 404 | not_found | The article or route doesn't exist. |
| 429 | daily_limit_reached | Free plan only: you used 100 requests today (UTC). Resets at 00:00 UTC, or upgrade for unlimited. |
| 429 | rate_limited | More than 20 requests in one second from the same account. Back off for the Retry-After seconds. |
| 500 | server_error | Something broke on our side. Retry with backoff; we're alerted automatically. |
| 503 | unavailable | A dependency is temporarily down. Retry with backoff and check the status page. |
What to retry
- Retry with backoff:
429 rate_limited(honorRetry-After),500,502,503,504and network errors. Use exponential backoff with jitter, starting around 1 second. - Don't retry: other
4xxerrors. Fix the request, key or plan first. - Free plan: after
429 daily_limit_reached, wait until 00:00 UTC or upgrade.
retry helper
import random
import time
import requests
def get_with_retry(url, headers, params, attempts=5):
for i in range(attempts):
r = requests.get(url, headers=headers, params=params, timeout=10)
if r.status_code == 429 or r.status_code >= 500:
wait = float(r.headers.get("Retry-After", 2 ** i)) + random.random()
time.sleep(wait)
continue
r.raise_for_status()
return r.json()
r.raise_for_status()