Provisional
Errors
One error shape, a request id on every response, and a clear retry rule.
Error responsesPermalink to this section
Failures return a JSON body with an error object and the same request_id that successful responses carry. Branch on the HTTP status and the machine-readable code; the message is for humans and may be reworded.
{
"error": {
"code": "invalid_parameter",
"message": "sort must be market_id",
"param": "sort"
},
"request_id": "req_7f21c9ab"
}Status codesPermalink to this section
| Status | Meaning | Retry |
|---|---|---|
| 400 | A parameter is missing, malformed or out of range, for example a limit above 200. | No. Fix the request. |
| 401 | No key, malformed header, or a key that is not valid. | No. |
| 404 | No market matches that platform and market id. | No. |
| 429 | Too many requests in the window. | Yes, after waiting. |
| 500 | Something failed on our side. | Yes, with backoff. |
| 503 | Temporarily unable to serve the request. | Yes, with backoff. |
Handling failuresPermalink to this section
RETRYABLE = {429, 500, 503}
def call(path, **kwargs):
for attempt in range(5):
resp = session.get(f"{BASE}{path}", timeout=20, **kwargs)
if resp.status_code not in RETRYABLE:
resp.raise_for_status()
return resp.json()
wait = float(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
raise RuntimeError("Riddle API still failing after retries")- Never retry a 401. The answer will not change.
- Back off exponentially with jitter so a fleet of workers does not retry in lockstep.
- Fail visibly. A sync that silently swallows errors produces a dataset with holes you find months later.