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

# Errors

> The error envelope, error codes, and how to handle failures.

# Errors

Every failure returns a standard HTTP status code and a consistent JSON
envelope.

## Error envelope

```json theme={null}
{
  "error": {
    "code": "validation_failed",
    "message": "items[0].quantity must be a positive integer"
  }
}
```

`code` is stable and machine-readable — branch on it. `message` is
human-readable and may change.

## Error codes

| `code`                | Status | Meaning                                                                   |
| --------------------- | ------ | ------------------------------------------------------------------------- |
| `invalid_body`        | `400`  | Body is missing, not JSON, or structurally unreadable                     |
| `unauthorized`        | `401`  | Key is missing, malformed, revoked, or expired                            |
| `card_required`       | `402`  | No card on file for the clinic — order cannot be charged                  |
| `payment_declined`    | `402`  | The clinic's card on file was declined                                    |
| `insufficient_scope`  | `403`  | Key lacks the scope this endpoint requires                                |
| `not_vita_clinic`     | `403`  | Write endpoint called by an org that is not an active Vita Clinic         |
| `not_found`           | `404`  | Resource does not exist or belongs to another organization                |
| `patient_not_found`   | `404`  | Referenced patient does not exist for this organization                   |
| `duplicate_request`   | `409`  | Idempotency conflict — the external id is in use with a different payload |
| `validation_failed`   | `422`  | Well-formed JSON that failed field validation                             |
| `product_not_found`   | `422`  | A referenced `product_id` does not exist                                  |
| `product_not_allowed` | `422`  | The product exists but is not orderable by this clinic                    |
| `rate_limited`        | `429`  | Per-key write rate limit exceeded                                         |
| `internal_error`      | `500`  | Unexpected server failure                                                 |

## Notable cases

* **`rate_limited`** — the response carries `Retry-After` and
  `X-RateLimit-*` headers. See [Rate Limits](/getting-started/rate-limits).
* **`card_required` / `payment_declined`** — the order was validated but not
  created as paid. Fix the card on file and retry with the same
  `external_order_id`; the request is idempotent.
* **`duplicate_request`** — you reused an external id with a different body.
  Replaying the *same* body is not an error: it returns the original record
  with `200` instead of `201`.
* **`not_found` vs `patient_not_found`** — resources outside your organization
  are reported as missing, never as forbidden.

## Handling errors in code

```typescript theme={null}
const res = await fetch("https://vitarelay.com/api/public/v1/orders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.VITARELAY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

if (!res.ok) {
  const { error } = await res.json();

  switch (error.code) {
    case "rate_limited":
      // back off using the Retry-After header, then retry
      break;
    case "card_required":
    case "payment_declined":
      // surface a billing problem to the clinic
      break;
    default:
      console.error(`VitaRelay error [${res.status}] ${error.code}: ${error.message}`);
  }
}
```

## 5xx errors

An `internal_error` means the request did not complete. It is safe to retry with
exponential backoff — include your external id so the retry is idempotent.
Contact [info@vitarelay.com](mailto:info@vitarelay.com) if failures persist.
