/v1/events/:eventKey/bookSecret keyBooking 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.
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
Every requested seat is now booked.
{
"ok": true,
"booked": ["STALLS-A-12", "STALLS-A-13"]
}On an idempotent replay, booked is empty because no new inventory transition was needed:
{
"ok": true,
"booked": []
}Nothing is booked when any requested seat is unavailable.
{
"error": "conflict",
"conflicts": [
{
"label": "STALLS-A-13",
"status": "booked"
}
]
}A test key cannot book live inventory, and a live key cannot book a sandbox event.
{
"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.
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}`);
}