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

# Idempotency and replay safety

> How Maple makes retries safe without an idempotency key — replay-safe decisions, diff-applied menus, idempotent replay, and dedupe on your side.

Networks fail mid-request. The question that matters is: if you retry, will something happen twice? With Maple, the writes you’ll retry most are built to be safe to repeat — so you can retry on a timeout without reconciling first.

> [!NOTE]
>
> There is **no `Idempotency-Key` header** on this API. For orders, menus, and events, safety is a property of each operation, described below — not something you opt into per request. The one place you do send a key is **booking and waitlist writes**, which take a body-level `idempotency_key` (see below).

## Order decisions are replay-safe

Repeating an order decision — `accept`, `deny`, `ready`, `complete`, `cancel`, or `status` — returns the same acknowledgement without applying the side effect twice:

```json
{ "object": "order_decision", "order_id": "ord_...", "decision": "accept", "status": "received" }
```

If you send `accept` and the response times out, send it again. The order is accepted exactly once, and you get `{ "status": "received" }` either way. This makes a simple retry-on-transient-error loop correct without any bookkeeping on your side.

## Publishing a menu is a diff

A menu publish describes the desired state of the whole menu and is applied as a diff against what’s currently published, keyed by your `externalId`s:

- Re-publishing an **identical** document is a no-op.
- Object identities stay stable across publishes by `externalId`, so republishing doesn’t recreate or churn objects.
- Validation is all-or-nothing — a `400` changes nothing, so a failed publish leaves the menu exactly as it was.

So if a publish times out, re-sending the same document is safe. See [Publish a menu](https://docs.maple.inc/developer-api/guides/publish-menu.md).

## Booking writes carry an `idempotency_key`

Creating, changing, or cancelling a booking (`POST /v1/bookings`, `POST /v1/bookings/{id}/change`, `POST /v1/bookings/{id}/cancel`), cancelling one as the venue (`POST /v1/bookings/{id}/venue_cancel`), and seating a walk-in (`POST /v1/locations/{locationId}/walk_ins`) require an `idempotency_key` **in the body**, scoped to your app. Walk-in keys, like waitlist joins, are your app’s across all its locations. Keys must contain 1–255 characters. For **Maple-native bookings**, the ledger records the write and its replay outcome in one transaction:

- Retrying with the **same key and the same request** replays the original outcome — you get the same `booking` back, and nothing is booked, moved, or cancelled twice.
- The **same key with a different request** (a different guest, slot, or note) answers `409` with `code: "idempotency_key_reused"`. Generate a fresh key per intended write; a UUID is fine.

A retry returns the same booking identity without reapplying the mutation; the response is read back from the current booking. Keys have a 24-hour replay window from the first claim. Replays are checked before `expected_version`, which will often be stale after the original write succeeds.

Native and provider holds are persisted and owned by the app that acquired them. Repeating the same `slot_token` replays the app’s live hold without taking another lock. Another app cannot consume or release it, even at a jointly granted location. Unknown provider holds release their quota reservation after a conservative 24-hour inactivity quarantine, independently of their retained audit record; expired operations cannot be revived by retrying.

### Provider-backed writes

For OpenTable, Maple durably claims the key and request digest before provider I/O and uses one stable provider request ID. A pending change or cancellation also reserves the mirrored booking against other partner writes. On success, Maple records the mirror and replay outcome together. Transient mirror failures are retried synchronously; a background reconciler repairs outstanding work.

A provider timeout returns `409 operation_in_progress` while the outcome is unresolved. Retry the same request and key after `details.retry_after` seconds. Maple performs a reconciliation read before repeating a booking write with the original provider request ID. Completed writes replay without calling the provider again.

> [!WARNING]
>
> An unresolved pending key is **not** recycled when its 24-hour automatic replay window ends. If `details.reason` is `replay_window_expired`, stop automatic retries and contact Maple with `details.operation_id`. Do not generate a new key to work around an uncertain result.

Booking configuration uses content-addressed publication instead: identical configuration is a no-op without an explicit key. The status venue verbs (confirm, decline, seat, complete, no-show) take `expected_version`, not an idempotency key; after an ambiguous response, read the booking before taking another action. Venue-side cancellation and walk-ins do take an `idempotency_key`, scoped to your app like every other key.

```json
{ "hold_id": "bkh_…", "guest": { "first_name": "Alex", "last_name": "Quinn", "phone": "+12125551234" }, "idempotency_key": "6f1c…" }
```

## Replaying an event is idempotent

`POST /v1/webhook_events/{eventId}/replay` re-delivers an event only to subscriptions that haven’t already received it. Subscriptions that already got it are skipped, so replaying after an outage won’t double-deliver to healthy endpoints.

## Your side: dedupe deliveries

The one place you must add idempotency is your **webhook handler**. Delivery is at-least-once, so the same event can arrive more than once. Dedupe on the envelope `id`:

```ts
async function handleEvent(event: { id: string; type: string; data: unknown }) {
  if (await alreadyProcessed(event.id)) return; // no-op on a repeat
  await process(event);
  await markProcessed(event.id);
}
```

Make the work itself idempotent where you can — for example, key your own order records by Maple’s `order_id` so a duplicate `order.notification` updates rather than duplicates. Don’t rely on event ordering; reconcile against `GET /v1/orders/{orderId}` when sequence matters. See [Webhooks](https://docs.maple.inc/developer-api/concepts/webhooks.md#delivery-guarantees).

## Summary

| Operation                               | Safe to retry?                   | Why                                                                                                                                                                          |
| --------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Order decisions                         | Yes                              | Replay-safe; repeats return `received` with no double effect                                                                                                                 |
| Menu publish                            | Yes                              | Diff-applied; identical re-publish is a no-op                                                                                                                                |
| Event replay                            | Yes                              | Skips subscriptions already delivered to                                                                                                                                     |
| Native booking create / change / cancel | Yes, with your `idempotency_key` | Same key + same body replays; same key + different body is `409 idempotency_key_reused`                                                                                      |
| Walk-ins and venue-side cancellation    | Yes, with your `idempotency_key` | Same key + same body replays the same booking, even after its table is taken; same key + different body (or, for walk-ins, another location) is `409 idempotency_key_reused` |
| Native booking holds                    | While the original hold is live  | Re-presenting its `slot_token` returns the live hold                                                                                                                         |
| Provider booking writes / holds         | With the same key / quote        | Durable claims and stable provider request IDs; unresolved outcomes can require reconciliation                                                                               |
| Booking configuration                   | Yes                              | Identical configuration is a content-addressed no-op                                                                                                                         |
| Receiving webhooks                      | You dedupe                       | At-least-once delivery — dedupe on event `id`                                                                                                                                |
