> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noisemaker.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Error envelope, error codes table, and retry guidance.

Every error response from the v1 API (except research routes and internal metering routes) follows a consistent envelope:

```json theme={null}
{
  "error": {
    "code": "string",
    "message": "string",
    "requestId": "8 hex chars",
    "details": [{ "path": [], "code": "", "message": "" }]
  }
}
```

`details` appears only on zod validation failures (HTTP 400 `invalid_request`), where each issue in the validation errors maps to one `details` entry.

`requestId` is an 8-character hex string that correlates server-side logs to the response. Include it when reporting issues.

## Error codes

| HTTP | Code                               | Meaning                                                                              | Retryable                             |
| ---- | ---------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------- |
| 400  | `invalid_request`                  | Request body failed schema validation; check `details`                               | No — fix the payload                  |
| 400  | `invalid_topup_amount`             | Top-up is outside $5–$500 range or not whole cents                                   | No — fix the amount                   |
| 400  | `invalid_scope`                    | API key scope is not in the allowed list                                             | No                                    |
| 400  | `invalid_cidr`                     | CIDR notation is malformed                                                           | No                                    |
| 400  | `key_not_active`                   | Only active keys can be rotated                                                      | No                                    |
| 400  | `not_a_replacement`                | The key is not a replacement in a rotation                                           | No                                    |
| 400  | `invalid_workspace_settings`       | Workspace settings update failed validation                                          | No                                    |
| 400  | `invalid_workspace_icon`           | Icon upload or refresh failed                                                        | No                                    |
| 400  | `invalid_signature`                | Stripe webhook signature verification failed                                         | No                                    |
| 401  | `invalid_key`                      | API key is absent, revoked, pending, or expired grace                                | No                                    |
| 401  | `invalid_credentials`              | Email or password is wrong                                                           | Yes — with corrected credentials      |
| 403  | `signup_not_allowed`               | Email domain is not in the allowed list                                              | No                                    |
| 403  | `csrf_forbidden`                   | Session cookie CSRF token is missing or invalid                                      | No                                    |
| 403  | `ip_forbidden`                     | Client IP is outside the key's `allowedCidrs`                                        | No — check the key's CIDR             |
| 403  | `metering_forbidden`               | Operation token is invalid for this operation                                        | No                                    |
| 404  | `workspace_not_found`              | Workspace does not exist, is not accessible, or key's bound workspace does not match | No                                    |
| 404  | `key_not_found`                    | API key does not exist in the workspace                                              | No                                    |
| 404  | `delivery_not_found`               | API key delivery record not found                                                    | No                                    |
| 404  | `job_not_found`                    | Job does not exist or is not accessible                                              | No                                    |
| 404  | `chat_thread_not_found`            | Chat thread does not exist                                                           | No                                    |
| 404  | `recommendation_not_found`         | Recommendation not found                                                             | No                                    |
| 409  | `workspace_exists`                 | Workspace slug is already taken                                                      | No                                    |
| 409  | `email_exists`                     | Account with this email already exists                                               | No                                    |
| 409  | `username_exists`                  | Username is already in use                                                           | No                                    |
| 409  | `username_unavailable`             | Username is reserved or taken                                                        | No                                    |
| 409  | `job_already_running`              | One running measurement per brand slot is occupied                                   | Yes — poll and retry after completion |
| 409  | `pipeline_capacity_reached`        | Worker cap (`GEO_MAX_PIPELINE_JOBS`) reached; no more jobs can start                 | Yes — retry after backoff             |
| 409  | `insufficient_credits`             | Credits are too low to start or continue an operation                                | No — top up credits                   |
| 409  | `stopped_credits`                  | Operation stopped mid-stream because a credit lease was refused                      | Yes — retry the operation             |
| 409  | `metering_conflict`                | Idempotency key was reused with different input, or a CAS conflict                   | No                                    |
| 409  | `idempotency_conflict`             | Idempotency key was already used with a different request                            | No                                    |
| 409  | `invalid_state`                    | Resource state does not allow the requested transition                               | No                                    |
| 409  | `rotation_in_progress`             | Key already has a replacement pending                                                | No                                    |
| 409  | `delivery_expired`                 | 10-minute delivery window has passed                                                 | No — create a new key                 |
| 409  | `operator_provider_not_configured` | Provider is not configured for this operator                                         | No                                    |
| 409  | `mock_topup_disabled`              | Mock top-ups are disabled when Stripe is configured                                  | No                                    |
| 409  | `route_not_registered`             | Route is not in the API route registry                                               | No                                    |
| 409  | `stale_state`                      | Recommendation transition CAS failed (stale `expectedVersion`)                       | Yes — re-fetch and retry              |
| 409  | `invalid_transition`               | Recommendation action is not valid from current status                               | No                                    |
| 429  | `rate_limited`                     | Generic rate limit hit                                                               | Yes — exponential backoff             |
| 429  | `research_rate_limited`            | Research on-demand start limit (1/hour/workspace)                                    | Yes — retry after the window          |
| 429  | `verification_cooldown`            | Verification email resend cooldown not elapsed                                       | Yes — wait and retry                  |
| 503  | `auth_unavailable`                 | Authentication is unavailable (missing server key)                                   | Yes — retry                           |
| 503  | `delivery_unavailable`             | Key delivery encryption is unavailable                                               | Yes — retry                           |
| 503  | `entropy_unavailable`              | Unable to generate a unique API key                                                  | Yes — retry                           |
| 503  | `billing_unavailable`              | Stripe billing is not configured                                                     | No                                    |
| 503  | `email_delivery_unavailable`       | Email delivery service is unavailable                                                | Yes — retry                           |
| 500  | `internal_error`                   | Unexpected server error                                                              | Yes — retry                           |

## Retry strategy

| Category                   | Codes                                                                                                                                                                                                                                             | Strategy                                                                  |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **Terminal**               | `invalid_request`, `invalid_topup_amount`, `signup_not_allowed`, `csrf_forbidden`, `ip_forbidden`, `invalid_key`, `invalid_credentials`, `workspace_not_found`, `delivery_expired`, `invalid_state`, `invalid_transition`, `idempotency_conflict` | Do not retry. Fix the request.                                            |
| **Safe to retry**          | `rate_limited`, `research_rate_limited`, `verification_cooldown`, `pipeline_capacity_reached`, `internal_error`, `auth_unavailable`, `delivery_unavailable`                                                                                       | Retry with exponential backoff (start at 1s, double each attempt, max 5). |
| **Retry after resolution** | `job_already_running`                                                                                                                                                                                                                             | Poll the run list and retry when the slot is free.                        |
| **Retry with correction**  | `insufficient_credits`, `stopped_credits`                                                                                                                                                                                                         | Top up credits, then retry.                                               |
| **Retry with re-fetch**    | `stale_state`                                                                                                                                                                                                                                     | Re-fetch the resource to get the current version, then retry.             |

## 402 insufficient\_credits body

```json theme={null}
{
  "error": {
    "code": "insufficient_credits",
    "message": "Insufficient credits. Add credits to continue.",
    "requestId": "a1b2c3d4",
    "details": [
      { "path": ["requiredMicrousd"], "code": "insufficient_credits", "message": "500000" },
      { "path": ["availableMicrousd"], "code": "insufficient_credits", "message": "100000" }
    ]
  }
}
```

## 409 stalestate body

```json theme={null}
{
  "error": {
    "code": "stale_state",
    "message": "Recommendation state version has changed",
    "requestId": "e5f6g7h8"
  }
}
```
