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

# Errors

> How the merchant MCP reports failures: HTTP-level rejections, isError tool results with stable error codes, and retry rules for timed-out writes.

Failures surface at two levels. Authentication, origin and rate-limit problems are rejected **before** MCP, as plain HTTP responses. Everything that happens inside a tool call comes back as an MCP tool result with `isError: true`.

## HTTP-Level

| Status | Cause                                                  | Body                                                                                          |
| ------ | ------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| `401`  | Missing or invalid credential                          | `WWW-Authenticate: Bearer realm="OAuth", resource_metadata="…"` header; start OAuth discovery |
| `403`  | `Origin` header not on the allow-list                  | `{ "error": "Origin not allowed" }`                                                           |
| `429`  | More than 60 requests in a minute for this user or key | `{ "error": "Rate limit exceeded" }` with `Retry-After` in seconds                            |

These are outside the MCP envelope; an MCP client library typically raises them as transport errors.

## Tool Errors

A failed tool call is a normal JSON-RPC success whose result has `isError: true`, a single text block and **no** `structuredContent`:

```json theme={null}
{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "{\"error\":{\"code\":\"INVALID_INPUT\",\"message\":\"Date range exceeds 90 days\",\"nextSteps\":[\"Split the range into shorter intervals.\"],\"retryable\":false}}"
    }
  ]
}
```

Parse the text as JSON to get:

| Field       | Meaning                                                                                                 |
| ----------- | ------------------------------------------------------------------------------------------------------- |
| `code`      | One of the codes below. Stable; branch on this                                                          |
| `message`   | Short, safe description. For `INVALID_INPUT` it names the problem; for other codes it is a fixed phrase |
| `nextSteps` | Instructions an agent can follow                                                                        |
| `retryable` | Currently always `false` — see [Retrying](#retrying)                                                    |

Nothing else from the underlying failure is exposed: no stack traces, SQL, upstream responses or internal IDs. Every request is logged server-side with the `x-request-id` echoed on the HTTP response; quote it when contacting support.

## Codes

| Code                   | When                                                                                                                                                                     | Typical fix                                                                                            |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `INVALID_INPUT`        | Schema violation, a date range over 90 days, an offset-less write timestamp, `period` combined with `from`/`to`, an account-wide stats query on a multi-currency account | Correct the arguments; `message` says what failed                                                      |
| `PERMISSION_DENIED`    | The credential cannot act on this account                                                                                                                                | Check the key or token                                                                                 |
| `NOT_FOUND`            | The venue, order, menu or link does not exist **on this account** — resources on other accounts look identical to non-existent ones                                      | Call `list-venues` (or the relevant list tool) and check the ID                                        |
| `CONFLICT`             | State prevents the write, e.g. a discount code already used at that venue                                                                                                | Read the current state, then decide                                                                    |
| `TIMEOUT`              | The tool exceeded 30 seconds, or an upstream call timed out                                                                                                              | Narrow the query. For a write, **check state before retrying**                                         |
| `UPSTREAM_UNAVAILABLE` | A dependency (database, payments provider) could not be reached                                                                                                          | Wait, then retry reads; check state before retrying writes                                             |
| `INTERNAL_ERROR`       | Anything unexpected                                                                                                                                                      | Retry reads once; email [mcp@storekit.com](mailto:mcp@storekit.com) with the request ID if it persists |

## Retrying

Reads (`readOnlyHint: true`) are safe to retry after `TIMEOUT`, `UPSTREAM_UNAVAILABLE` or `INTERNAL_ERROR`, with backoff.

Writes are different. `create-discount-code` and `create-payment-link` are marked `idempotentHint: false` and have no idempotency key. A `TIMEOUT` is returned when the 30-second budget runs out, which can happen **after** the row has been committed — the code or link may already exist. Before retrying a write:

1. `list-discount-codes` and look for the code, or `list-payment-links` filtered by venue and look for the title/reference.
2. Only call the write again if it is absent.

Never retry writes automatically. This is why `retryable` is `false` on every code today: the server does not know whether your write landed, so it does not tell you to retry.

## Validation Errors From Your Client

Because errors carry no `structuredContent`, spec-compliant clients (including the official TypeScript SDK) skip output-schema validation on them and surface the envelope. If your client instead reports a `-32602` or "structured content does not match schema" error, it is validating the *text* against the tool's `outputSchema`; read the text block directly.


## Related topics

- [Response Format](/docs/developers/mcp/response-format.md)
- [storekit Payments Setup](/docs/guides/payments/storekit-payments-setup.md)
- [Orders Failing: Till Offline](/docs/guides/integrations/pos/lightspeed/till-offline.md)
- [Zonal (Aztec)](/docs/guides/integrations/pos/zonal.md)
- [Promotional Banners](/docs/guides/marketing/promotional-banners.md)
