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

# Errors

> The ReconLayer error envelope, validation issue format, and status code catalog.

ReconLayer returns errors as JSON objects with a machine-readable `error` code and, in most cases, a human-readable `message`. Some errors add extra fields — `issues` for validation failures, or extra context fields for domain-specific conflicts.

## Error envelope

```json theme={null}
{
  "error": "string",
  "message": "string",
  "issues": [ { "path": ["field", "name"], "message": "string" } ],
  "details": { },
  "upstreamStatus": 0
}
```

<ResponseField name="error" type="string" required>
  Machine-readable error code. Stable — safe to branch on in client code. See the catalog below.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable description of what went wrong. Present on most errors; not guaranteed to be stable wording.
</ResponseField>

<ResponseField name="issues" type="array">
  Present on `invalid_request`. Each item has a `path` (array of field-name segments locating the offending field) and a `message`.
</ResponseField>

<ResponseField name="details" type="object">
  Present on some errors with extra structured context (e.g. conflict details).
</ResponseField>

<ResponseField name="upstreamStatus" type="integer">
  Present on errors that proxy a failure from an upstream provider, indicating the upstream's HTTP status.
</ResponseField>

## Validation errors — `400 invalid_request`

Request bodies and query strings are validated against the API's schemas. A validation failure returns `400` with `error: "invalid_request"` and an `issues` array:

```json 400 Invalid request body theme={null}
{
  "error": "invalid_request",
  "issues": [
    {
      "path": ["sourceAmount"],
      "message": "must match pattern \"^-?\\d+(\\.\\d+)?$\""
    }
  ]
}
```

Each issue's `path` is the segment path to the offending field, with array indices and nested object keys decoded — for example `["amount", "currency"]` for a nested field, or `["metadata", "0", "value"]` for an array item.

### Two special header-validation cases

Two specific missing-header conditions are surfaced with their own error codes instead of a generic `invalid_request`, because they're common integration mistakes:

<AccordionGroup>
  <Accordion title="400 invalid_idempotency_key — missing Idempotency-Key">
    ```json theme={null}
    {
      "error": "invalid_idempotency_key",
      "message": "Idempotency-Key header is required."
    }
    ```

    Returned by any write endpoint that requires `Idempotency-Key` when the header is absent. See [Idempotency](/api-reference/idempotency).
  </Accordion>

  <Accordion title="400 invalid_organization_context — missing or unresolvable organization">
    ```json theme={null}
    {
      "error": "invalid_organization_context",
      "message": "x-organization-id header is required."
    }
    ```

    Returned when the request's organization cannot be resolved from the credential or headers. See [Authentication → Organization resolution](/api-reference/authentication#organization-resolution).
  </Accordion>
</AccordionGroup>

## Authentication and authorization errors

| Status | `error`        | When                                                                                                                                              |
| ------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `unauthorized` | Missing `Authorization` header, or the bearer token is not a valid API key or Clerk session.                                                      |
| `403`  | `forbidden`    | The credential is valid but the action requires a signed-in dashboard user (not an API key), or the user lacks the required workspace permission. |

```json 401 Missing credential theme={null}
{
  "error": "unauthorized",
  "message": "Missing bearer credential. Provide an API key or session token."
}
```

```json 403 Signed-in session required theme={null}
{
  "error": "forbidden",
  "message": "This action requires a signed-in dashboard session."
}
```

See [Authentication](/api-reference/authentication) for the full set of `401`/`403` cases.

## Not found — `404 not_found`

Requesting a resource (by ID) that doesn't exist for your organization returns `404`:

```json 404 Payment intent not found theme={null}
{
  "error": "not_found",
  "message": "Payment intent pi_xyz was not found for organization org_acme."
}
```

ReconLayer scopes lookups to your resolved `organizationId` — a resource that exists in a *different* organization is indistinguishable from one that doesn't exist, and both return `404`.

## Conflicts — `409`

`409` responses indicate the request was understood but cannot be applied as-is, because of a conflict with existing state. ReconLayer uses several specific codes:

<AccordionGroup>
  <Accordion title="409 payment_intent_conflict — externalReference exists with different fields">
    Returned by `POST /v1/payment-intents` when the `externalReference` already exists for your organization but the new request's canonical fields (amounts, currencies, beneficiary, etc.) don't match the stored payment intent. The response includes the conflicting fields:

    ```json theme={null}
    {
      "error": "payment_intent_conflict",
      "message": "A payment intent with this external reference already exists for the organization, but the canonical request fields do not match.",
      "caseId": "case_01J...",
      "paymentIntentId": "pi_01J...",
      "externalReference": "invoice-9182",
      "mismatches": [ /* fields that disagree */ ],
      "existing": { "sourceAmount": "100.00", "sourceCurrency": "USD", "...": "..." }
    }
    ```

    See [API Design Principles → Business-level uniqueness](/api-reference/design-principles#business-level-uniqueness).
  </Accordion>

  <Accordion title="409 idempotency_key_conflict — Idempotency-Key reused with a different body">
    ```json theme={null}
    {
      "error": "idempotency_key_conflict",
      "message": "This Idempotency-Key was already used for a different request on this operation."
    }
    ```

    See [Idempotency](/api-reference/idempotency#how-it-works-under-the-hood).
  </Accordion>

  <Accordion title="409 idempotency_request_in_progress — original request still processing">
    ```json theme={null}
    {
      "error": "idempotency_request_in_progress",
      "message": "A request with this Idempotency-Key is already being processed."
    }
    ```

    Retry after a short delay. See [Idempotency](/api-reference/idempotency#how-it-works-under-the-hood).
  </Accordion>
</AccordionGroup>

## Rate limiting — `429`

Exceeding your organization's request rate returns `429` with the standard `@fastify/rate-limit` error body and `RateLimit-*` / `Retry-After` headers. See [Rate limits](/api-reference/rate-limits).

## Server and upstream errors — `5xx`

| Status                | Meaning                                                                                                                                                                                                 |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `500`                 | An unexpected error inside ReconLayer. If this persists, contact support with the request's timestamp and, if available, the `Idempotency-Key` used.                                                    |
| `502` / `503` / `504` | Returned when an upstream provider (a bank rail, stablecoin provider, or chain indexer) the request depends on is unavailable or errored. May include `upstreamStatus` with the upstream's status code. |

<Warning>
  A `5xx` response means the request may or may not have been applied. For write endpoints, **retry with the same `Idempotency-Key` and the same body** — ReconLayer will either replay the completed result or safely re-execute, never duplicate. See [Idempotency](/api-reference/idempotency).
</Warning>

## Quick reference

| Status | `error`                           | Typical cause                                                                      |
| ------ | --------------------------------- | ---------------------------------------------------------------------------------- |
| `400`  | `invalid_request`                 | Body or query failed schema validation; see `issues`.                              |
| `400`  | `invalid_idempotency_key`         | Missing `Idempotency-Key` header on a write endpoint.                              |
| `400`  | `invalid_organization_context`    | Organization could not be resolved from credential/headers.                        |
| `401`  | `unauthorized`                    | Missing, invalid, revoked, or expired credential.                                  |
| `403`  | `forbidden`                       | Action requires a signed-in dashboard user, or user lacks the required permission. |
| `404`  | `not_found`                       | Resource does not exist for your organization.                                     |
| `409`  | `payment_intent_conflict`         | `externalReference` exists with different canonical fields.                        |
| `409`  | `idempotency_key_conflict`        | `Idempotency-Key` reused with a different request body.                            |
| `409`  | `idempotency_request_in_progress` | Original request with this key is still processing.                                |
| `429`  | (rate-limit body)                 | Organization exceeded its request rate.                                            |
| `5xx`  | varies                            | Internal or upstream provider error — safe to retry idempotent writes.             |

## Next steps

* [Idempotency](/api-reference/idempotency) — safe retries for `409`/`5xx` on write endpoints.
* [Rate limits](/api-reference/rate-limits) — avoiding and handling `429`.
* [Authentication](/api-reference/authentication) — avoiding `401`/`403`.
