# Errors

Hooksbase uses ordinary HTTP status codes, but the important part is knowing which failures are safe to retry, which ones mean your request can never succeed as written, and which ones are quota or lifecycle preconditions.

Most API, ingest, and form POST error responses have a JSON body shaped like `{ "error": "..." }`. Quota and some lifecycle failures add structured fields (see [Quota errors](#quota-errors)). The public signed file download route can return a plain-text `404` because the signed token itself carries access.

## Status codes

- **400**: Validation failure, malformed cursor, invalid analytics window, invalid usage range, malformed form data, or another bad request shape.
- **401**: Missing or invalid project API key or webhook ingest secret.
- **403**: Forbidden by role, disabled project on project-auth routes, or using a saved tier-gated feature such as payload transforms after downgrade.
- **404**: Missing resource, foreign project resource, or archived webhook ingest.
- **409**: Lifecycle precondition failure, paused webhook ingest, duplicate allowlist pattern, missing retained payload for replay, or storage/backlog quota exhaustion.
- **413**: HTTP ingest payload exceeds the current 10 MB maximum body size, or a form upload exceeds the per-file or per-ingest file limit.
- **415**: Unsupported form-ingest content type.
- **422**: Semantically invalid input such as bad cron expressions or transform output that is not valid JSON.
- **429**: Ingest-rate or replay-volume quota exhaustion.
- **503**: Temporary coordinator or analytics unavailability.

## Quota errors

Quota failures return a structured JSON shape so you can decide whether to back off, surface a user-visible limit, or prompt for an upgrade.

**Quota error response**

```json
{
  "error": "Quota exceeded for replayVolume24h.",
  "code": "quota_replayVolume24h_exceeded",
  "scope": "webhook",
  "scopeId": "wh_...",
  "quota": "replayVolume24h",
  "limit": 250,
  "current": 250,
  "attemptedDelta": 1,
  "retryAfterSec": 3600
}
```

Treat quota errors differently from ordinary validation failures:

- `429` usually means retry later
- `409` quota preconditions usually mean storage or backlog must be reduced before more work can be accepted

## Temporary failures

Hooksbase has two main retry-safe temporary failures:

- `503` when coordinator handoff or analytics reads are temporarily unavailable
- idempotent ingest or replay retries when you provided an `Idempotency-Key`

If a request mutates state and you want to retry it safely, send an idempotency key whenever the surface supports one. This matters most for public ingest and bulk replay / bulk DLQ recovery job creation.

## Common mistakes

- Retrying non-idempotent writes without an idempotency key.
- Treating `404` and `409` the same on webhook ingest.
- Assuming analytics `503` means quota enforcement is offline. Quotas do not depend on Analytics Engine availability.
- Ignoring the structured quota payload and retrying immediately in a tight loop.
