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

# Checkout Errors

> Error codes returned by the ACP checkout endpoints

# Checkout Errors

The checkout endpoints use the ACP error envelope, which differs from the [standard Gomry error format](/errors) used everywhere else in this API. The catalog endpoints use the standard format.

```json theme={null}
{
  "type": "invalid_request",
  "code": "sold_out",
  "message": "General Admission is sold out.",
  "param": "items[0].id"
}
```

<ResponseField name="type" type="string">
  `invalid_request`, `processing_error`, or `service_unavailable`. A coarse class for clients that do not switch on `code`.
</ResponseField>

<ResponseField name="code" type="string">
  The specific reason. See the table below.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable. Safe to log; not intended to be shown to a buyer verbatim.
</ResponseField>

<ResponseField name="param" type="string">
  Present when one field caused the failure — for example `items[0].id`.
</ResponseField>

Every error response also carries `API-Version`, and echoes `Request-Id` and `Idempotency-Key` when you sent them. Correlating a failure is exactly when those headers matter most.

## Codes

| Code                      | Status | Type                  | Meaning                                                                                                                                |
| ------------------------- | ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_body`            | `400`  | `invalid_request`     | The body failed validation. Check `param`. Also returned for an **unexpected field** — see the warning below.                          |
| `invalid_header`          | `400`  | `invalid_request`     | A required header is missing or malformed.                                                                                             |
| `unsupported_api_version` | `400`  | `invalid_request`     | The `API-Version` header names a version other than `2025-09-12`. An **absent** header is accepted and treated as the current version. |
| `item_not_found`          | `400`  | `invalid_request`     | No ticket type matches `items[].id`. Usually an **event id passed where a ticket type id belongs**.                                    |
| `multiple_events`         | `400`  | `invalid_request`     | The cart mixes ticket types from different events. One session sells one event.                                                        |
| `currency_mismatch`       | `400`  | `invalid_request`     | The cart mixes currencies.                                                                                                             |
| `invalid_signature`       | `401`  | `invalid_request`     | The signature failed, the timestamp is outside the ±5 minute window, or the key is not on the ACP partner allowlist.                   |
| `payment_declined`        | `402`  | `processing_error`    | The delegated payment token was declined.                                                                                              |
| `payment_requires_action` | `402`  | `processing_error`    | The payment needs additional authentication (3DS).                                                                                     |
| `session_not_found`       | `404`  | `invalid_request`     | No such session for this key. Sessions are scoped to the key that created them.                                                        |
| `invalid_session_state`   | `409`  | `invalid_request`     | The operation is not legal in the session's current state — completing a canceled session, for instance.                               |
| `sold_out`                | `409`  | `invalid_request`     | Not enough inventory remains.                                                                                                          |
| `quantity_limit_exceeded` | `409`  | `invalid_request`     | The requested quantity exceeds the organizer's per-order cap.                                                                          |
| `not_on_sale`             | `409`  | `invalid_request`     | Sales have not opened, or have closed.                                                                                                 |
| `amount_mismatch`         | `409`  | `processing_error`    | The total changed between quote and completion. Re-read the session and confirm the new total with your buyer.                         |
| `request_not_idempotent`  | `409`  | `invalid_request`     | A request with the same `Idempotency-Key` is still running. Retry shortly.                                                             |
| `internal_error`          | `500`  | `processing_error`    | Something failed on our side.                                                                                                          |
| `temporarily_unavailable` | `503`  | `service_unavailable` | A dependency is unavailable. Retry with backoff.                                                                                       |

<Warning>
  **Unexpected fields are rejected, not ignored.** Request bodies are validated strictly, so a body carrying a field the schema does not define returns `400 invalid_body` rather than silently dropping it.

  This is deliberate and exists for one reason above all: a body containing something like `card_number` must fail loudly rather than be quietly accepted into our logs and traces. See [Agentic Commerce](/commerce/introduction#payment-is-token-only).
</Warning>

## Blocking messages vs errors

An error is a failed **request**. A blocked **session** is not an error — it returns `200` with `status: "not_ready_for_payment"` and one or more entries in `messages[]`:

```json theme={null}
{
  "status": "not_ready_for_payment",
  "messages": [
    {
      "type": "error",
      "code": "missing",
      "param": "buyer.name",
      "content_type": "plain",
      "content": "A buyer name is required to issue a ticket."
    }
  ]
}
```

`messages[].code` is a **separate, smaller enum** from the error codes above — the ACP spec closes it to six values:

| Code               | Meaning                                      |
| ------------------ | -------------------------------------------- |
| `missing`          | A required value has not been supplied.      |
| `invalid`          | A supplied value is not acceptable.          |
| `out_of_stock`     | The requested line cannot be filled.         |
| `payment_declined` | The payment attempt failed.                  |
| `requires_sign_in` | The buyer must authenticate.                 |
| `requires_3ds`     | The payment needs additional authentication. |

A `type: "error"` message blocks payment; `type: "info"` is advisory. Read `status` for the decision and `messages` for the reason.

## Retrying

<AccordionGroup>
  <Accordion title="Safe to retry as-is">
    `500 internal_error`, `503 temporarily_unavailable`, and `409 request_not_idempotent`. Use exponential backoff, and reuse the same `Idempotency-Key` so a retry cannot double-charge.
  </Accordion>

  <Accordion title="Fix, then retry">
    Every `400`. The request is wrong and will fail identically until changed.
  </Accordion>

  <Accordion title="Re-read the session first">
    `409 sold_out`, `409 not_on_sale`, `409 amount_mismatch`. The world changed underneath the session. `GET` it, show the buyer what is now true, and only then retry.
  </Accordion>

  <Accordion title="Do not retry">
    `401 invalid_signature` and `402 payment_declined`. Retrying a declined token will not make it succeed; ask for another payment method.
  </Accordion>
</AccordionGroup>
