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

# Errors

> Error shapes and status codes on both APIs.

The two APIs return **different error shapes**. If you're consuming both, handle
them separately.

## Marketplace API

A structured envelope with a code you can branch on:

```json theme={"system"}
{
  "error": {
    "code": "not_found",
    "message": "Collection not found."
  }
}
```

| Code             | Status |                             |
| ---------------- | ------ | --------------------------- |
| `bad_request`    | 400    | Bad parameters              |
| `not_found`      | 404    | No such collection or token |
| `rate_limited`   | 429    | Too many requests           |
| `upstream_error` | 502    | A data source failed        |

Branch on `error.code`, not the message — messages are for humans and may get
reworded.

On a `502` the message is deliberately generic. Upstream error text isn't passed
through, because it can contain query internals. Retry, and tell us if it
persists.

## Store API

A flat string:

```json theme={"system"}
{
  "error": "Insufficient permissions. Required scope: products:write"
}
```

| Status |                                           |
| ------ | ----------------------------------------- |
| `400`  | Bad payload or parameters                 |
| `401`  | Missing, invalid, disabled or expired key |
| `403`  | Valid key, wrong scope                    |
| `404`  | Not in your shop                          |
| `429`  | Too many requests                         |
| `500`  | Our problem                               |

## Handling both

```js theme={"system"}
async function request(url, options) {
  const res = await fetch(url, options);
  const body = await res.json().catch(() => null);

  if (res.ok) return body;

  // Marketplace: { error: { code, message } }
  // Store:       { error: "message" }
  const err = body?.error;
  const code = typeof err === 'object' ? err.code : null;
  const message = typeof err === 'object' ? err.message : err;

  throw Object.assign(new Error(message ?? res.statusText), {
    status: res.status,
    code,
  });
}
```

## What's worth retrying

| Status              | Retry                                                           |
| ------------------- | --------------------------------------------------------------- |
| `400`, `403`, `404` | No — the request is wrong, and repeating it repeats the mistake |
| `401`               | No, unless you're mid key-rotation                              |
| `429`               | Yes, honouring `Retry-After`                                    |
| `500`, `502`        | Yes, backing off                                                |

→ [Rate limits](/api/rate-limits) has a backoff implementation.

## Opaque 400s

Store API payload validation is strict but reports generically —
`"Invalid product payload"` rather than naming the field.

<Tip>
  Post the minimum viable payload first, then add fields back one at a time. Each
  endpoint page lists what's actually required.
</Tip>
