# Errors

Every error response has the same shape:

```json
{ "success": false, "error": "This order can no longer be changed or cancelled here. Refresh the page to see its latest status.", "code": "order_not_editable" }
```

- `code` is stable and machine-readable. Your program should branch on it.
- `error` is an English message for people. It may change.
- `details` (on `validation_failed` only) lists each problem with the field `path` and a `message`.
- The `X-Request-Id` header identifies the request. Quote it when you contact support.

## How requests are checked

A path that doesn't exist (`404 not_found`) or a wrong method (`405 method_not_allowed`) is answered first. Then the checks run in this order, and the first that fails decides the error:

1. The API key: `401 invalid_api_key`, `403 api_not_enabled`, `403 account_action_required`. Too many failed attempts from one network are answered `429 rate_limited` at this step.
2. The rate limit: `429 rate_limited`. This comes before the permission check, so refused calls still count.
3. The key's permission: `403 insufficient_scope`.
4. For writes, whether your account is on hold: `403 account_on_hold`.
5. The id in the path, if it isn't a valid id: `404 not_found`.
6. The query string and the body: `400 invalid_json`, `413 payload_too_large`, `415 unsupported_media_type`, `422 validation_failed`.
7. The `Idempotency-Key`: `400`, `409 request_in_progress`, `422 idempotency_key_reused`. Errors in the steps above never use up a key.
8. The business rules: a specific code with `404`, `409` or `422`. An id in the path that is valid but doesn't exist or isn't yours returns `404 not_found` on reads, and the endpoint's own code (such as `404 order_not_found`) on writes; each endpoint lists its codes.

## General error codes

| Code | HTTP status | Meaning |
|---|---|---|
| `invalid_json` | 400 | The request body is not valid JSON. |
| `idempotency_key_required` | 400 | This request needs an Idempotency-Key header. |
| `invalid_idempotency_key` | 400 | Idempotency-Key must be 1 to 255 visible ASCII characters. |
| `invalid_api_key` | 401 | The API key is missing, malformed, revoked or no longer valid. |
| `api_not_enabled` | 403 | API access is not enabled for this account. |
| `account_action_required` | 403 | Sign in to the portal and finish the required account steps first. |
| `insufficient_scope` | 403 | The API key does not have the permission this call needs. |
| `account_on_hold` | 403 | The account is on hold. Writes are refused; reads still work. |
| `not_found` | 404 | The resource does not exist or does not belong to this account. |
| `method_not_allowed` | 405 | The path exists but does not support this method. The Allow header lists the supported methods. |
| `request_in_progress` | 409 | A request with this Idempotency-Key is still running or its result is unknown. |
| `payload_too_large` | 413 | The request body is larger than 1 MB. |
| `unsupported_media_type` | 415 | The request body must be sent as application/json. |
| `validation_failed` | 422 | The input is not valid. `details` lists each field and the reason. |
| `idempotency_key_reused` | 422 | This Idempotency-Key was already used with a different request. |
| `request_rejected` | 422 | The request was rejected by a business rule. |
| `rate_limited` | 429 | Too many requests. Wait for the number of seconds in Retry-After. |
| `internal_error` | 500 | Unexpected error. Quote X-Request-Id when you contact support. |

## Business rule codes

Business rules return their own codes. Their status follows fixed rules:

- codes ending in `_not_found` are `404`: something in the path doesn't exist or isn't yours;
- codes ending in `_changed` are `409`: the data changed since you read it; read it again and decide again;
- other state conflicts, such as `order_not_editable` or `shipment_not_cancellable`, and uniqueness conflicts, such as `store_order_no_taken` and `library_sku_taken`, are `409`;
- quota and usage limits are `409` as well, because waiting doesn't reliably clear them; don't retry them automatically;
- everything else is `422`. An id inside the request body (a product, a shipping option) that doesn't exist or isn't yours is a `422` rule failure, not a `404`;
- a rule failure we can't describe more precisely is `422 request_rejected`.

The codes each endpoint can return are listed in its reference section. All business codes the API returns today:

| Code | HTTP status | Example message | Returned by |
|---|---|---|---|
| `library_not_in_library` | 422 | This product is not in My products. Please refresh the page. | [`POST /v1/sku-mappings`](https://docs.rephut.com/developers/reference/sku-mappings#map-your-sku-to-one-of-your-products) |
| `library_sku_taken` | 409 | This SKU is already used for another product. | [`POST /v1/sku-mappings`](https://docs.rephut.com/developers/reference/sku-mappings#map-your-sku-to-one-of-your-products) |
| `order_not_editable` | 409 | This order can no longer be changed or cancelled here. Refresh the page to see its latest status. | [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention), [`POST /v1/orders/{id}/cancel`](https://docs.rephut.com/developers/reference/orders#cancel-an-order-that-needs-attention) |
| `order_not_found` | 404 | This order could not be found. | [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention), [`POST /v1/orders/{id}/cancel`](https://docs.rephut.com/developers/reference/orders#cancel-an-order-that-needs-attention) |
| `order_product_not_allowed` | 422 | Choose products from My products. Other products can't be added to your orders here. | [`POST /v1/orders/preview`](https://docs.rephut.com/developers/reference/orders#check-a-new-order-without-saving-it), [`POST /v1/orders`](https://docs.rephut.com/developers/reference/orders#create-an-order), [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) |
| `orders_unavailable` | 422 | We can't take orders right now. Please contact your account manager. | [`POST /v1/orders/preview`](https://docs.rephut.com/developers/reference/orders#check-a-new-order-without-saving-it), [`POST /v1/orders`](https://docs.rephut.com/developers/reference/orders#create-an-order), [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) |
| `preference_channel_unavailable` | 422 | This shipping option is no longer available. Please choose another one. | [`POST /v1/orders/preview`](https://docs.rephut.com/developers/reference/orders#check-a-new-order-without-saving-it), [`POST /v1/orders`](https://docs.rephut.com/developers/reference/orders#create-an-order), [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention), [`POST /v1/shipments`](https://docs.rephut.com/developers/reference/shipments#request-a-shipment-from-your-stock) |
| `product_not_reorderable` | 422 | One of the products is no longer available to reorder. Please refresh the page and try again. | [`POST /v1/requests/reorder`](https://docs.rephut.com/developers/reference/requests#ask-us-to-restock-products-at-your-agreed-prices) |
| `shipment_changed` | 409 | This shipment was updated. Please review it again. | [`POST /v1/shipments/{id}/confirm`](https://docs.rephut.com/developers/reference/shipments#confirm-a-shipment-s-freight-quote) |
| `shipment_not_cancellable` | 409 | This shipment can no longer be cancelled here because we've already sent you the freight quote. Please contact your account manager. | [`POST /v1/shipments/{id}/cancel`](https://docs.rephut.com/developers/reference/shipments#cancel-a-shipment-you-requested) |
| `shipment_not_decidable` | 409 | This shipment can no longer be confirmed or commented on. It may have been updated or already confirmed. Please refresh the page. | [`POST /v1/shipments/{id}/confirm`](https://docs.rephut.com/developers/reference/shipments#confirm-a-shipment-s-freight-quote) |
| `shipment_not_found` | 404 | This shipment could not be found. | [`POST /v1/shipments/{id}/cancel`](https://docs.rephut.com/developers/reference/shipments#cancel-a-shipment-you-requested), [`POST /v1/shipments/{id}/confirm`](https://docs.rephut.com/developers/reference/shipments#confirm-a-shipment-s-freight-quote) |
| `shipments_unavailable` | 422 | We can't take shipments right now. Please contact your account manager. | [`POST /v1/shipments`](https://docs.rephut.com/developers/reference/shipments#request-a-shipment-from-your-stock) |
| `stock_short` | 422 | Some products don't have enough available stock. Refresh the page to see the latest quantities and try again. | [`POST /v1/shipments`](https://docs.rephut.com/developers/reference/shipments#request-a-shipment-from-your-stock) |
| `store_order_no_locked` | 409 | Orders from your store keep the store's order number, so it can't be changed here. | [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) |
| `store_order_no_taken` | 409 | You already have an order with this store order number. | [`POST /v1/orders`](https://docs.rephut.com/developers/reference/orders#create-an-order), [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) |
| `unknown_country` | 422 | Choose your country from the list. | [`POST /v1/requests/sourcing`](https://docs.rephut.com/developers/reference/requests#ask-us-to-source-new-products) |

The message column shows the current English text; it may change.

## Handling errors

| Status | What to do |
|---|---|
| 400, 413, 415, 422 | Fix the request. Sending the same request again gives the same error. |
| 401, 403 | Check the key, its permissions and your account in the portal. Don't retry automatically. |
| 404 | Check the id; it may belong to another account. |
| 405 | Use a method from the `Allow` header. |
| 409 | Read the resource again with `GET`, then decide. For `request_in_progress` see [Idempotency](https://docs.rephut.com/developers/idempotency). |
| 429 | Wait the number of seconds in `Retry-After`, then retry. See [Rate limits](https://docs.rephut.com/developers/rate-limits). |
| Network error or timeout on a write | Retry with the same `Idempotency-Key`. The first request may still be running, and a new key could create a duplicate. See [Idempotency](https://docs.rephut.com/developers/idempotency). |
| 500, or `request_in_progress` that doesn't go away | Reads: retry with backoff. Writes: check the current state with `GET`, and use a new `Idempotency-Key` only if the write didn't happen. See [Idempotency](https://docs.rephut.com/developers/idempotency). |
