---
title: "Book held seats"
description: "Resolve a buyer hold and permanently book reserved seats from your trusted server with safe retries and conflict handling."
---

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

# Book held seats

**POST /v1/events/:eventKey/book** — Authentication: Secret key

Booking turns a temporary buyer hold into a permanent sale. This endpoint is server-only and accepts `sk_test_…` or `sk_live_…` credentials whose mode matches the event.

> **Server-side only**
>
> Never call this endpoint from browser code. The buyer SDK holds seats and hands your backend an opaque `holdId`; only your backend has booking authority.

## Request

```http title="HTTP request"
POST /v1/events/ev_9f3a/book HTTP/1.1
Host: api.seatlayer.io
Authorization: Bearer sk_test_••••••••
Content-Type: application/json

{
  "holdId": "hold_01J8F2A7MRQ4",
  "bookingRef": "order_1842"
}
```

| Field | Type | Required | Description |
|---|---|---:|---|
| `holdId` | `string` | Yes | Opaque hold identifier returned by the buyer SDK. |
| `bookingRef` | `string` | Yes | Your order or payment reference. Makes safe retries idempotent. |

## Responses

### 200 Booked

Every requested seat is now booked.

```json title="200 response"
{
  "ok": true,
  "booked": ["STALLS-A-12", "STALLS-A-13"]
}
```

On an idempotent replay, `booked` is empty because no new inventory transition was needed:

```json title="200 replay response"
{
  "ok": true,
  "booked": []
}
```
### 409 Conflict

Nothing is booked when any requested seat is unavailable.

```json title="409 response" {3-7}
{
  "error": "conflict",
  "conflicts": [
    {
      "label": "STALLS-A-13",
      "status": "booked"
    }
  ]
}
```
### 403 Mode mismatch

A test key cannot book live inventory, and a live key cannot book a sandbox event.

```json title="403 response"
{
  "error": "mode_mismatch"
}
```

## Safe retry behavior

`bookingRef` is your idempotency key. Repeating the same request after a timeout succeeds without creating a second booking or spending credits twice. A replay may return an empty `booked` array because the seats were already booked by the same reference.

```js title="server/book.js"
const response = await fetch(
  "https://api.seatlayer.io/v1/events/ev_9f3a/book",
  {
    method: "POST",
    headers: {
      authorization: `Bearer ${process.env.SEATLAYER_SECRET_KEY}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({ holdId, bookingRef: order.id }),
  },
);

if (response.status === 409) {
  return redirectBuyerToPicker();
}

if (!response.ok) {
  throw new Error(`SeatLayer booking failed: ${response.status}`);
}
```

> **Charge from authoritative data**
>
> Resolve the hold from your server before taking payment. Build your order total from the hold's server response—not labels, tiers, or prices supplied by the browser.

Source: https://docs.seatlayer.io/server-api/booking/index.mdx
