# Errors

Source: https://caravelpay.app/docs/errors.html · Caravel Pay docs · Last updated 2026-11-09

Failed requests return a non-`2xx` status and a JSON body with a stable `code` you can branch on, plus a readable `message`.

## Error format

*400 Bad Request*

```json
{
  "error": {
    "code": "invalid_amount",
    "message": "amount must be a string with two decimals, like \"136.41\"",
    "param": "amount"
  }
}
```

`param` is included when the error is about one field.

## HTTP status codes

| Status | Meaning | Retry? |
|---|---|---|
| `200` `201` | Success. | — |
| `400` | The request is malformed or a field is invalid. | No. Fix the request. |
| `401` | Missing or wrong credentials, signature or timestamp. | No. Fix the signing. |
| `403` | This merchant can't create checkouts right now: it is paused or waiting for approval. | No. See the code. |
| `404` | No such checkout for this merchant. | No. |
| `409` | The `order_id` already belongs to a checkout with different details, or a limit was reached. | No. See the code. |
| `429` | Too many requests. | Yes, after the `Retry-After` header. |
| `500` | Something went wrong on our side. | Yes, with backoff and the same `order_id`. |
| `503` | New checkouts are briefly unavailable. | Yes, after the `Retry-After` header, with the same `order_id`. |

## Error codes

| Code | Status | What happened |
|---|---|---|
| `invalid_request` | 400 | The body isn't valid JSON, a field is missing or unknown, a query parameter is wrong, or the body is over 16 KiB. See `param`. |
| `invalid_amount` | 400 | `amount` isn't a string with exactly two decimals between `"0.01"` and `"1000000.00"`. |
| `unsupported_currency` | 400 | `currency` isn't `"USD"`. |
| `invalid_url` | 400 | One of `return_url`, `cancel_url` or `callback_url` isn't an absolute HTTPS URL on your site (your `site_url`, with or without `www`). On `POST /v1/connect`: `site_url` isn't a plain HTTPS address. |
| `unknown_merchant` | 401 | `Caravel-Merchant` is missing or doesn't match a merchant. |
| `invalid_signature` | 401 | `Caravel-Signature` doesn't match. See [common mistakes](https://caravelpay.app/docs/authentication.html#mistakes). |
| `timestamp_expired` | 401 | `Caravel-Timestamp` is missing, malformed or more than 300 seconds from our clock. |
| `merchant_paused` | 403 | New checkouts are paused for this merchant. Open checkouts and payouts still finish. Email [hello@caravelpay.app](mailto:hello@caravelpay.app). |
| `merchant_pending_review` | 403 | The site is waiting for approval. Checkouts open once it is approved. |
| `not_found` | 404 | No checkout with that id belongs to you, or no such endpoint. |
| `order_id_conflict` | 409 | That `order_id` was already used for a checkout with different details. Use a new `order_id`. |
| `limit_exceeded` | 409 | A limit for this merchant was reached: order size, daily count or volume, or open checkouts. Try a smaller order or try again later. On `POST /v1/connect`: the payout wallet already serves the maximum number of merchants. |
| `rate_limited` | 429 | Slow down and retry after `Retry-After` seconds. |
| `server_error` | 500 | Unexpected error on our side. Safe to retry. |
| `service_unavailable` | 503 | New checkouts (or new merchants) are briefly unavailable. Retry after `Retry-After` seconds, with the same `order_id`. Open checkouts and payouts aren't affected. |

## Retrying safely

- **Network errors, `429` and `5xx`:** retry with exponential backoff, and wait at least `Retry-After` seconds when it is sent. Re-sign each attempt with a fresh timestamp, and keep the same `order_id` so you can't create a duplicate checkout.
- **Other `4xx`:** don't retry unchanged. Fix the request first.
