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

# Idempotency

> Order ids make every purchase safe to retry: how to choose one, what a retry returns, and what happens to an order that was interrupted.

[Buy packs](https://catchbackcards.mintlify.site/partner-api/endpoints/buy-packs.md) and [Buy a card](https://catchbackcards.mintlify.site/partner-api/endpoints/buy-card.md) spend your balance, so each one requires an `Idempotency-Key` header: your own id for the order. We record it before anything is charged, and from then on that order is charged at most once, however many times you send it.

[Withdraw](https://catchbackcards.mintlify.site/partner-api/endpoints/create-withdrawal.md) takes an `Idempotency-Key` too, with its own rules on that page.

> [!WARNING]
>
> Generate one order id per order and reuse it on every attempt of that order. A new id is a new order: a second set of packs, or a second card.

## The order id

* **Header:** `Idempotency-Key`. Through the [MCP server](https://catchbackcards.mintlify.site/partner-api/mcp.md), the `buy_packs` and `buy_card` tools take it as `order_id`.
* **Format:** 8–128 characters of `A-Z`, `a-z`, `0-9`, `_` and `-`. A UUID works. A missing header is a `400` (`"Idempotency-Key header is required"`), and so is one in the wrong format.
* **Scope:** unique per CatchBack account, across pack and card orders together. Every API key linked to the same account shares the same ids, and an id used for a pack order can't be reused for a card order (`422 IDEMPOTENCY_KEY_REUSED`).
* **Lifetime:** an id doesn't expire. A finished order keeps returning its original response to that id.

## What a retry returns

| You send an order id that… | You get | Charged? |
| - | - | - |
| Is new | The order runs | Once |
| Belongs to an order that finished | The stored response, with its original status, plus `"idempotent_replay": true` | No, not again |
| Belongs to an order that is still running | `409 IDEMPOTENCY_IN_PROGRESS`, usually with the order's `checkout_id` and `started_at`. Resend later, or follow it with [Get an order](https://catchbackcards.mintlify.site/partner-api/endpoints/get-order.md) | No, not again |
| Belongs to an order held for review, because we couldn't confirm its charge | `409 IDEMPOTENCY_IN_PROGRESS` with `needs_review: true`, plus the order's `checkout_id` and `started_at`. Resending won't help until we settle it: follow it with [Get an order](https://catchbackcards.mintlify.site/partner-api/endpoints/get-order.md) | No, not again |
| Belongs to an attempt that failed just as you sent this one | `409 IDEMPOTENCY_RETRY`. Resend it | No |
| Belongs to an order that failed earlier | The order runs again: a failed order refunds itself and frees its id | Once, if it succeeds this time |
| Was used for a different body | `422 IDEMPOTENCY_KEY_REUSED`. Use a new id for a different order | No |

Only a new order is checked against your [rate limits](https://catchbackcards.mintlify.site/partner-api/rate-limits.md): a replay or an in-progress answer never counts against them, so you can always find out what your first attempt did.

```json 409 IDEMPOTENCY_IN_PROGRESS theme={null}
{
  "error": "A purchase with this idempotency key is still in progress.",
  "code": "IDEMPOTENCY_IN_PROGRESS",
  "checkout_id": "c8baa66a-4f1e-4a70-9a3c-2b6d1e9f0a11",
  "started_at": "2026-10-28T20:19:55.120934+00:00"
}
```

## What counts as the same order

A retry must send the same body as the first attempt. We compare the fields that define the order, after filling in their defaults, so leaving out a default and sending it explicitly are the same order:

| Endpoint | The order is |
| - | - |
| Buy packs | `pack_type`, `category` (default `pokemon`), `quantity` (default `1`) and `risk_level` (default `standard`) |
| Buy a card | `card_id` and `max_price` |

Anything else is a different order, and reusing the id for it is a `422`.

## Timeouts and interrupted orders

A purchase can take up to 60 seconds, so set your client's timeout above that. If a request times out or the connection drops, you can't tell whether it went through: resend the same order id. You get the finished order, `409 IDEMPOTENCY_IN_PROGRESS` while it is still running, or `500 PAYMENT_OUTCOME_UNKNOWN` if the order runs on this attempt and we can't confirm its charge.

If a request is cut off on our side after the charge (a timeout, a crash, a deploy), the order stays in progress until we close it from our own records. We check every 5 minutes for orders that have been running for more than 5, so it usually takes about 10 minutes. From then on, your resend returns a shorter body than the original, marked `"reconciled": true`:

```json Pack order theme={null}
{
  "checkout_id": "c8baa66a-4f1e-4a70-9a3c-2b6d1e9f0a11",
  "pack_type": "bronze",
  "quantity": 2,
  "total_charged": 50,
  "pull_ids": ["fe04349b-…", "1d7c0a52-…"],
  "reconciled": true,
  "message": "This order completed but its response was lost. The items are in your vault.",
  "idempotent_replay": true
}
```

```json Card order theme={null}
{
  "checkout_id": "5d1c…",
  "transaction_id": "a17e…",
  "card_id": "0e94dc61-…",
  "source": "shop",
  "listing_id": null,
  "price": 1450,
  "total_charged": 1451,
  "reconciled": true,
  "message": "This order completed but its response was lost. The card is in your vault.",
  "idempotent_replay": true
}
```

A reconciled pack order has no `pulls`, no `summary` and no `remaining_balance`. Its `pull_ids` are the `pull_id`s of the pulls, and the cards are already in your vault: read them, with their CatchBack offers, from [Get vault](https://catchbackcards.mintlify.site/partner-api/endpoints/get-vault.md). A reconciled card order has no `fee`, `remaining_balance` or `item`.

The 10-minute close above doesn't apply to an order held for review. When we can't confirm a charge (`500 PAYMENT_OUTCOME_UNKNOWN`, and the `409` after it carries `needs_review: true`), nothing was delivered, so our records can't prove the charge and there is nothing to close the order from. It stays in progress until a person settles it, and the id is then free to send again.

You don't have to resend an order to learn where it stands. [Get an order](https://catchbackcards.mintlify.site/partner-api/endpoints/get-order.md) takes its `checkout_id` (the `409` body carries it): it shows the order as `pending` until it completes or we close it, a pack order's pulls as soon as they have landed, and `reconciled: true` on an order we closed. [List orders](https://catchbackcards.mintlify.site/partner-api/endpoints/list-orders.md) shows the same for every order in a window. An order held for review stays `pending` until we settle it. Settling removes it, so Get an order then answers `404` and List orders no longer shows it.

If an order still answers `409` after 30 minutes, we couldn't prove from our records that it went through. Contact us with the order id and the `checkout_id`.

## Purchases that need review

A purchase can return `"needs_review": true`. On a card buy's `200` the card is yours and only our bookkeeping needs a check. On an error, you were, or may have been, charged and the order didn't finish cleanly, so it stays in progress and we're alerted to settle it by hand. Either way, don't retry under a new order id. Details: [Errors](https://catchbackcards.mintlify.site/partner-api/errors-and-limits.md#when-a-purchase-needs-review).

## Shipments

[Create a shipment](https://catchbackcards.mintlify.site/partner-api/endpoints/create-shipment.md) takes the same header in the same format, with some differences. Its ids are a separate set from your pack and card order ids, a retry of a finished shipment returns the shipment as it stands now rather than a stored response, and one we couldn't confirm stays with our team until we've checked it. See [its retry table](https://catchbackcards.mintlify.site/partner-api/endpoints/create-shipment.md#retries).


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