---
path: /v1/errors
title: Errors
summary: Error response format, status codes, and retry semantics for the AgentFit API.
group: Guides
updated: 2026-06-22
---

# Errors

Most error responses are a JSON object with a stable machine-readable `code` and a
human-readable `error` message:

```json
{ "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 `400`s (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.

```python
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.
