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

# Errors

> Error envelope, status codes, and what each code means.

The API returns structured JSON for every error. Look at both the HTTP status and the `code` field — the status tells you the category, the code tells you exactly what happened.

## Envelope

```json theme={null}
{
  "error": {
    "code": "invalid_api_key",
    "message": "The API key provided is invalid or has been revoked.",
    "docs_url": "https://docs.sitevisit.app/errors/invalid_api_key",
    "request_id": "req_2026060512abc"
  }
}
```

| Field        | Notes                                                                                   |
| ------------ | --------------------------------------------------------------------------------------- |
| `code`       | Stable, snake\_case identifier. Don't parse `message` — use `code` for branching logic. |
| `message`    | Human-readable explanation. May change over time.                                       |
| `docs_url`   | Permalink to the docs page for this specific code.                                      |
| `request_id` | Echoes the `X-Request-Id` response header. Include this when contacting support.        |

Every response — success or error — sets `X-Request-Id`. Capture it in your client logs so we can trace failures end-to-end.

## Status codes

| Status | Meaning                                                                   |
| ------ | ------------------------------------------------------------------------- |
| `200`  | Success.                                                                  |
| `201`  | Resource created.                                                         |
| `204`  | Resource deleted (no body).                                               |
| `400`  | Your request is malformed — bad parameter shape, unknown enum value, etc. |
| `401`  | Authentication failed: missing, invalid, or revoked API key.              |
| `402`  | Plan limit hit. Response includes `upgrade_url` and `current_plan`.       |
| `403`  | Authenticated but not permitted to access this resource.                  |
| `404`  | The resource doesn't exist, or it belongs to a different account.         |
| `409`  | Idempotency-Key reuse while another request is still in flight.           |
| `422`  | Idempotency-Key reused with a *different* request body.                   |
| `429`  | Rate limit exceeded. Retry with backoff.                                  |
| `500`  | Something broke on our side. We get paged; you should retry with backoff. |

## Common error codes

| Code                      | HTTP  | Meaning                                                           | What to do                                                                |
| ------------------------- | ----- | ----------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `missing_authorization`   | `401` | No `Authorization` header.                                        | Send `Authorization: Bearer sv_live_…`.                                   |
| `invalid_api_key`         | `401` | Key doesn't match a live, non-revoked key.                        | Check for typos; verify in **Settings → Developers**.                     |
| `invalid_request_body`    | `400` | Body isn't a JSON object on a POST/PATCH.                         | Ensure `Content-Type: application/json` + valid JSON.                     |
| `invalid_parameter`       | `400` | A field was the wrong shape, out of range, or missing.            | Read `message` — it names the offending field.                            |
| `invalid_status`          | `400` | `status` value wasn't allowed for the resource.                   | See the API reference for valid enum values.                              |
| `invalid_priority`        | `400` | `priority` wasn't `LOW`, `MEDIUM`, or `HIGH`.                     | Use uppercase.                                                            |
| `plan_limit_exceeded`     | `402` | The user's plan tier is full (property / visit cap).              | Surface the `upgrade_url` from the response and let the user open it.     |
| `not_found`               | `404` | Resource doesn't exist or isn't owned by the caller.              | We never reveal whether a resource exists for someone else.               |
| `idempotency_in_progress` | `409` | Another request with the same `Idempotency-Key` is still running. | Retry in \~1 second — the original will have committed by then.           |
| `idempotency_key_in_use`  | `422` | The same key was reused with a different request body.            | Use a fresh key for a different request. See [Idempotency](/idempotency). |
| `rate_limited`            | `429` | Too many requests in a short window.                              | Back off — see `Retry-After` header.                                      |
| `internal_error`          | `500` | Unexpected server failure.                                        | Retry with exponential backoff. Include `request_id` when reporting.      |

## Plan limit response shape

When a write hits a plan cap, the error envelope is extended with `upgrade_url`, `current_plan`, `used`, and `limit`:

```json theme={null}
{
  "error": {
    "code": "plan_limit_exceeded",
    "message": "Your free plan allows 1 property; you currently have 1. Upgrade to Pro to add more.",
    "docs_url": "https://docs.sitevisit.app/errors/plan_limit_exceeded",
    "request_id": "req_abc...",
    "upgrade_url": "https://sitevisit.app/settings#upgrade",
    "current_plan": "free",
    "used": 1,
    "limit": 1
  }
}
```

Clients can branch on `code === "plan_limit_exceeded"` and surface a "go upgrade" CTA pointing at `upgrade_url`. MCP-driven agents do the same in natural language — the message + upgrade URL are designed to be quoted verbatim.

## Retrying

`429` and `5xx` responses are safe to retry. We recommend exponential backoff starting at 1 second, capped at 30 seconds, with jitter. `4xx` errors other than `409` and `429` are deterministic — retrying without changing the request won't help. For `409 idempotency_in_progress`, a short wait + retry is the correct response.
