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

# Errors

> The error envelope, the types we return, and what to do about each.

Reclack answers with conventional HTTP status codes. A `2xx` means the call
worked, a `4xx` means something about the request needs fixing, and a `5xx`
means the problem is ours.

<Frame caption="Every error has a type, an optional code and a message for a person">
  <img className="block dark:hidden" src="https://mintcdn.com/reclack/hSqUHJp0KVXvwTsQ/images/error-shape.svg?fit=max&auto=format&n=hSqUHJp0KVXvwTsQ&q=85&s=ae1896e546511197024c16bc19de3e4f" alt="An error response body beside the four error types and the action each one calls for." width="880" height="300" data-path="images/error-shape.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/reclack/hSqUHJp0KVXvwTsQ/images/error-shape-dark.svg?fit=max&auto=format&n=hSqUHJp0KVXvwTsQ&q=85&s=285a9ba19332847ed1635aa77f110ca2" alt="An error response body beside the four error types and the action each one calls for." width="880" height="300" data-path="images/error-shape-dark.svg" />
</Frame>

## The envelope

Every error has the same shape:

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "price_not_found",
    "message": "No price was found for that id"
  }
}
```

| Field | Meaning |
| - | - |
| `type` | The broad category. Switch on this. |
| `code` | A stable, machine-readable reason. Present when a specific one applies. |
| `message` | A sentence written for a person. Safe to log, not meant for pattern matching. |

Successful responses are just as plain. Objects and lists come back under
`data`:

```json theme={null}
{
  "data": {
    "id": "cus_7Fh2PqX9",
    "email": "dana@acme.dev"
  }
}
```

## Types

| Type | Status | What it means |
| - | - | - |
| `invalid_request_error` | 400, 404, 409 | The request is missing something, contradicts itself, or names an object that does not exist. |
| `authentication_error` | 401 | No key, a revoked key, or a key that does not exist. |
| `permission_error` | 403 | The key is valid but is not allowed to do this. |
| `api_error` | 500 | Something failed on our side. Retry is reasonable. |

## Common cases

<AccordionGroup>
  <Accordion title="401 authentication_error">
    The key was missing, malformed, or revoked. Check that you are sending
    `Api-Key: rk_live_...` and that the key has not been deleted
    in the dashboard.

    We answer the same way for a key that never existed and one that was
    revoked, so the endpoint cannot be used to find out which.
  </Accordion>

  <Accordion title="403 permission_error">
    The key is good but lacks the permission for this endpoint. Mint a key with
    the permission, or add it to the existing one. See
    [Authentication](/authentication#what-a-key-can-do).
  </Accordion>

  <Accordion title="400 invalid_request_error">
    A field is missing or will not parse. The message names the field. Amounts
    are decimal strings such as `"29.00"`, dates are RFC 3339, and networks are
    lowercase names such as `base`.
  </Accordion>

  <Accordion title="409 on a usage record">
    A usage record with that `idempotency_key` already exists. That is the
    safeguard working: the original record stands and nothing was billed twice.
    See [Idempotency](/idempotency).
  </Accordion>

  <Accordion title="429 too many requests">
    You hit a rate limit. Back off and retry. Payer-facing endpoints on the
    hosted checkout are throttled harder than merchant endpoints, because a
    link is public and anyone holding the URL can call them.
  </Accordion>

  <Accordion title="500 api_error">
    Ours. Retry with the same request. If it persists, write to support with
    the time and the object reference.
  </Accordion>
</AccordionGroup>

## Handling errors well

```js theme={null}
async function createCheckout(body) {
  const response = await fetch("https://api.reclack.com/v1/checkouts", {
    method: "POST",
    headers: {
      "Api-Key": process.env.RECLACK_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });

  if (response.ok) {
    const { data } = await response.json();
    return data;
  }

  const { error } = await response.json();

  // Retry only what is worth retrying. A 400 will fail again with the same
  // body, and a 401 will fail again with the same key.
  if (response.status >= 500) {
    throw new RetryableError(error.message);
  }

  throw new Error(`${error.type}: ${error.message}`);
}
```

<Tip>
  Log the `code` rather than the `message`. Messages are written for people and
  may be reworded. Codes are part of the contract.
</Tip>

## Infrastructure failures

When something fails deep in the stack, we answer with a plain `api_error`
instead of echoing the underlying failure. Internal detail such as SQL
fragments, table names or host addresses never reaches a response body. The
full error is logged on our side with the handler that produced it, so support
can find it from the time and the reference.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.