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

# Error Handling: Snappy API Error Codes & Recovery

> HTTP status codes, error payload shape, idempotency, and recommended retry patterns for the Snappy gifting API.

Snappy uses standard HTTP response codes to indicate the success or failure of an API request. If a request fails, we return a descriptive error object to help you identify and resolve the issue. **The error shape is the same in V2 and V3.**

***

## The Error Object

Every error response follows a consistent JSON structure. The structure differs slightly between validation errors (HTTP `400`) and all other errors.
**Validation errors (HTTP 400):**

```json theme={null}
{
  "path": "recipients[0].email",
  "errorCode": "INVALID_REQUEST",
  "message": "Invalid email format."
}
```

**All other errors (401, 403, 404, 409, 422, 5xx):**

```json theme={null}
{
  "status": 401,
  "errorCode": "UNAUTHORIZED",
  "message": "Authorization key is invalid."
}
```

| Field       | Type   | Description                                                                                    |
| :---------- | :----- | :--------------------------------------------------------------------------------------------- |
| `path`      | string | *(400 only)* Dot-separated path to the request parameter or body field that failed validation. |
| `status`    | number | *(non-400 only)* The HTTP status code.                                                         |
| `errorCode` | string | Granular code identifying the specific business-logic error.                                   |
| `message`   | string | Human-readable description of the error.                                                       |

<Note>
  **Error codes are stable; changing a code is considered a breaking change.** Error messages, however, may be updated over time to provide better clarity. Your integration should switch on `errorCode` for programmatic handling - never on the `message` text.
</Note>

### V3 error code anatomy

V3 endpoints use structured error codes in the format `{STATUS}_{DOMAIN}_{SEQUENCE}`:

| Segment                                                                                                                                                                  | Description                                   | Example                                                         |
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- | :-------------------------------------------------------------- |
| `{STATUS}`                                                                                                                                                               | The HTTP status code                          | `404`                                                           |
| `{DOMAIN}`                                                                                                                                                               | Short uppercase domain tag                    | `PROD` (products), `ORDS` (orders), `PBLC` (public-API generic) |
| `{SEQUENCE}`                                                                                                                                                             | 3-digit sequence number scoped to that domain | `001`                                                           |
| Example V3 codes: `404_PROD_001`, `400_PBLC_001`, `422_ORDS_003`.                                                                                                        |                                               |                                                                 |
| V2 endpoints use shorter symbolic codes (e.g., `INVALID_REQUEST`, `NOT_FOUND`, `UNAUTHORIZED`). Both formats are stable; switch on the full string regardless of format. |                                               |                                                                 |

***

## HTTP Status Codes

### 400 – Bad Request

The server could not process the request, usually due to a syntax error in the URL or the JSON body.
**How to fix**

* Verify your request body matches the expected schema.
* Ensure all required fields are present (e.g. `variantId`, `recipient.email`, `idempotencyKey`).
* Check that field values are the correct type (e.g. strings vs. integers).
* Inspect `path` in the response body - it points to the specific field that failed validation.

### 401 – Unauthorized

There is an issue with your API key credentials.
**How to fix**

* **Missing header.** Ensure you are passing the `X-Api-Key` header on every request.
* **Invalid key.** Double-check that the key hasn't been mistyped or deleted from the Dashboard.
* **Expired key.** Check whether the key has reached its expiration date and rotate it if needed. See [Authentication & Security](/pages/authentication-and-security).

### 403 – Forbidden

Your key is valid but does not have permission to access the requested resource.
**How to fix**

* **Wrong scope.** Check that your API key was created with the correct scope for this action (e.g. `orders:create` to place an order, `products:read` to read the catalog). See [Authentication & Security](/pages/authentication-and-security).
* **Wrong environment.** Make sure you are not using a Testing API key against a Production resource or vice versa. See [Before You Begin](/pages/before-you-begin).
* **Insufficient masking scope.** Reading PII fields requires `:read:unmasked` rather than `:read:masked`. See [PII Masking](/pages/authentication-and-security#data-privacy--pii-masking).

### 404 – Not Found

The requested resource does not exist, or it exists but is not visible to the calling Account.
**How to fix**

* Double-check the ID in your request path - copy it directly from a previous API response to avoid typos.
* Verify the resource was successfully created before attempting to retrieve or update it.
* If you're scoping with `Snappy-Account-Id`, verify the resource belongs to that account.

<Note>
  Snappy intentionally returns `404` (rather than `403`) in some cases where a resource exists but is not visible to your key - for example, a Collection belonging to a different Account. This prevents exposing the existence of resources your key shouldn't know about. If you're confident the resource exists and you should have access, check your key's scope and account scoping headers.
</Note>

### 422 – Unprocessable Entity

The request was syntactically valid, but Snappy can't fulfill it due to a business rule or current resource state.
**How to fix**

* Read the `errorCode` and `message` to understand the specific rule that was violated.
* Common 422 scenarios on V3:
  * **Order already in transit** - `POST /v3/orders/{orderId}/cancel` returns `422` once the fulfillment partner has picked up the shipment.
  * **Variant not shippable to country** - `POST /v3/orders` returns `422` if the shipping address country is outside the variant's supported locations.
  * **Insufficient billing balance** - `POST /v3/orders` returns `422` when the Billing Method has insufficient funds.
  * **Idempotency conflict** - replaying an `idempotencyKey` with a *different* request body returns `422`. Use the same key only with identical bodies.
* Re-issue the request once the underlying state changes (e.g., refunded balance, address updated, order cancelled in time).

### 429 – Too Many Requests

Your integration has exceeded the API rate limit.
**How to fix**

* Pause your requests immediately.
* Respect the `Retry-After` response header - it tells you how long to wait before retrying.
* Implement an exponential backoff strategy - wait a short period before retrying, and increase the wait time with each subsequent retry.
* Review your request frequency against the published [Rate Limits](/pages/rate-limits).

### 500 – Internal Server Error

Something went wrong on Snappy's end.
**How to fix**

* Check the [Snappy Status Page](https://status.snappy.com/) to see if there is an ongoing incident.
* Retry once with a short delay - many `500`s are transient.
* If no incident is reported and the issue persists, contact support with:
  * Request URL and method
  * The `errorCode` returned in the response body
  * A timestamp of when the error occurred
  * The `idempotencyKey` you used (for writes), so support can correlate without risking a duplicate

### 502 – Bad Gateway

An upstream service returned an invalid response while processing your request. Almost always transient.
**How to fix**

* Retry with exponential backoff. For writes, only retry if you supplied an `idempotencyKey` - otherwise you risk duplicate side effects.

### 503 – Service Unavailable

The service is temporarily unavailable, typically during a deployment or under unusual load.
**How to fix**

* Retry with exponential backoff. Respect the `Retry-After` header if present.
* If the issue persists across several minutes, check the [Snappy Status Page](https://status.snappy.com/).

### 504 – Gateway Timeout

The request took too long to process and timed out.
**How to fix**

* Retry with a short delay; most `504`s are transient.
* For writes, only retry if you supplied an `idempotencyKey` (the original request may have actually succeeded before the timeout - the idempotency key lets you safely re-issue without risking a duplicate).
* If a specific endpoint times out consistently, contact support with the request details.

***

## Retry & Idempotency

Not all errors are safe to retry. The matrix below summarizes when retrying is the right move.

| Status                | Safe to retry?     | Strategy                                                               |
| :-------------------- | :----------------- | :--------------------------------------------------------------------- |
| `400`                 | No                 | Fix the request and re-issue.                                          |
| `401`                 | No                 | Fix credentials.                                                       |
| `403`                 | No                 | Fix scope or environment.                                              |
| `404`                 | No                 | Fix the resource ID or scoping.                                        |
| `422`                 | Conditionally      | Only after the underlying state changes (balance, availability, etc.). |
| `429`                 | Yes                | Exponential backoff; respect `Retry-After`.                            |
| `500`                 | Yes (with caution) | Backoff; for writes, only retry with an idempotency key.               |
| `502` / `503` / `504` | Yes                | Backoff; for writes, only retry with an idempotency key.               |

### Idempotency keys make retries safe

Many Snappy write endpoints accept an idempotency key. Sending the same key twice returns the *same* result - no duplicate side effect, no double-charge, no second gift sent.

| Endpoint               | Idempotency field                    | Notes                                                 |
| :--------------------- | :----------------------------------- | :---------------------------------------------------- |
| `POST /v3/orders`      | `idempotencyKey` in the request body | Required. 1–120 characters. Stable per logical order. |
| `POST /v2/orders`      | `recipient.key` in the request body  | Same semantics, different field location.             |
| **Rules of the road:** |                                      |                                                       |

* **Generate one stable key per logical operation** - e.g., `user-{userId}-order-{cartId}`. Reuse it on every retry of that operation.
* **Never generate a fresh random key on each retry** - that defeats the protection and will result in duplicates.
* **Replaying with a different body returns `422`** (idempotency conflict). Either reuse the original body or pick a new key.
* See [Duplicate Detection](/pages/duplicate-gifts-detection) for the full reference.

### Exponential backoff

For all retryable error codes, use exponential backoff with jitter:

1. First retry: wait \~1 second.
2. Each subsequent retry: double the wait, with random jitter.
3. Cap at \~60 seconds between retries.
4. Give up after 5–7 attempts unless the operation is critical and idempotent.
   Always respect a `Retry-After` header if the response includes one - it overrides your local backoff calculation.
