Errors
Most error responses are a JSON object with a stable machine-readable code and a
human-readable error message:
{ "error": "Audit limit reached; try again in about 8 min.", "code": "site_quota", "retry_after": 480 }
The HTTP status conveys the class of error; the code field disambiguates within a class.
Rate-limit and quota errors include a retry_after (seconds). Internal failures never leak
stack traces or database strings — a 500 returns a generic internal code. A few
parameter-validation 400s (for example a malformed UUID) return only error with no
code.
Status codes
| Status | Meaning | Retryable |
|---|---|---|
| 200 | Success status — the synchronous report or poll result is in the body. | — |
| 202 | Accepted status — the audit was enqueued (async). | — |
| 303 | Redirect status — a form post; follow it to the share page. | — |
| 400 | Client error status — the request is invalid; read the error message and fix it. | No |
| 404 | Not-found error — no run or resource exists for that id. | No |
| 405 | Method-not-allowed error — the HTTP method is unsupported on this route. | No |
| 422 | Unprocessable — the run is not finished yet, or the audit could not produce a report. | No |
| 429 | Rate-limit error — quota or rate exceeded; the error reason is in the body, honour Retry-After. |
Yes |
| 500 | Internal server error — an unexpected error; the message is generic, safe to retry. | Yes (with backoff) |
| 503 | Unavailable — returned by the edge (CDN/proxy) when the service is briefly down; retry with backoff. | Yes (with backoff) |
Error codes
These are the stable code values the API emits:
code |
Status | Description |
|---|---|---|
invalid_base_url |
400 | base_url is missing or not a valid URL. |
not_public_address |
400 | base_url resolves to a private or non-public address. |
http_not_supported |
400 | base_url uses http://; only https:// is accepted. |
not_found |
404 | No audit run exists for the given id. |
not_done |
422 | The run exists but has not finished yet (e.g. a diff was requested too early). |
audit_failed |
422 | The audit ran but could not produce a report for the target site. |
rate_limited |
429 | The per-IP or per-target request rate was exceeded; see retry_after. |
site_quota |
429 | The 24-hour per-site audit quota is exhausted; see retry_after. |
method_not_allowed |
405 | The HTTP method is not supported on this route. |
internal |
500 | An unexpected server error occurred; safe to retry with backoff. |
Retry strategy
For 429, read the Retry-After header (seconds) and wait at least that long. For 500
and 503, retry with exponential backoff (for example 1s, 2s, 4s, capped) and a jitter.
Never retry a 400 without changing the request — it will fail the same way.
import time, requests
def get_with_retry(url, attempts=5):
delay = 1.0
for _ in range(attempts):
r = requests.get(url, timeout=10)
if r.status_code < 500 and r.status_code != 429:
return r
wait = int(r.headers.get("Retry-After", delay))
time.sleep(wait)
delay = min(delay * 2, 30)
return r
The legacy
/audit/{id}in-memory poll path is deprecated; use the durable/api/public/audit/*endpoints.