SeatBuilderSeatBuilder Docs

Book held seats

Secret key only (a public key gets `403 public_key_not_allowed`). Confirms one or more previously-held seats as booked in a single atomic, all-or-nothing batch. Booked rows keep the `holdToken` that booked them (so the same token can later release them); tokens are never included in responses or webhooks. Pass every seat label in `objectLabels[]`; all must currently be held under the same `holdToken` returned from `/hold`. If any one seat is no longer held by that token, the whole request fails with `409` and nothing is booked. Booked seats remain in `booked` status until explicitly released via `/release`. Emits one `seat.booked` webhook per booked seat if a subscription exists. Safe to retry: seats already booked under this `holdToken` are returned unchanged (same `bookedAt`) without another webhook or usage count; a mix books the held ones and returns all.

POST/api/v1/events/{eventKey}/seats/book

Path Parameters

eventKeystring
objectLabelsarray<string>

Seat labels to book in one atomic request. Single seat = array of one. All must be held by the supplied holdToken or the whole request fails. A general-admission area is booked under its GA hold (pass the holdToken returned from the GA /hold), NOT as an individual seat — a GA-area label with no GA hold under the holdToken fails 409 not_held_by_token (410 hold_expired if its GA hold expired).

Items1 <= items <= 200
holdTokenstring

The holdToken returned from the preceding /hold call(s). All listed labels must be held under this token.

Length1 <= length
extraData?object

Arbitrary JSON metadata merged onto EVERY booked seat (e.g. one { orderId } for the whole order).

Empty Object

Response Body

curl -X POST "https://seatbuilder.org/api/v1/events/string/seats/book" \  -H "Content-Type: application/json" \  -d '{    "objectLabels": [      "A-12",      "A-13"    ],    "holdToken": "7f6c3a91-b1ef-4d0a-9c84-3f12a8e0b5d2",    "extraData": {      "orderId": "ord_98a2"    }  }'

{
  "booked": [
    {
      "chartKey": "chart_8a2b1c",
      "eventKey": "evt_q3-2026-jazz-night",
      "objectLabel": "A-12",
      "status": "booked",
      "bookedAt": "2026-10-02T18:46:00.000Z",
      "extraData": {
        "orderId": "ord_98a2"
      }
    },
    {
      "chartKey": "chart_8a2b1c",
      "eventKey": "evt_q3-2026-jazz-night",
      "objectLabel": "A-13",
      "status": "booked",
      "bookedAt": "2026-10-02T18:46:00.000Z",
      "extraData": {
        "orderId": "ord_98a2"
      }
    }
  ]
}
{
  "statusCode": 0,
  "error": "string",
  "message": "string",
  "code": "validation_failed",
  "failed": [
    {
      "objectLabel": "string",
      "reason": "string",
      "eventKey": "string",
      "environment": "string",
      "booked": 0,
      "held": 0
    }
  ],
  "errors": {
    "property1": [
      "string"
    ],
    "property2": [
      "string"
    ]
  }
}
{
  "statusCode": 0,
  "error": "string",
  "message": "string",
  "code": "validation_failed",
  "failed": [
    {
      "objectLabel": "string",
      "reason": "string",
      "eventKey": "string",
      "environment": "string",
      "booked": 0,
      "held": 0
    }
  ],
  "errors": {
    "property1": [
      "string"
    ],
    "property2": [
      "string"
    ]
  }
}
{
  "statusCode": 0,
  "error": "string",
  "message": "string",
  "code": "validation_failed",
  "failed": [
    {
      "objectLabel": "string",
      "reason": "string",
      "eventKey": "string",
      "environment": "string",
      "booked": 0,
      "held": 0
    }
  ],
  "errors": {
    "property1": [
      "string"
    ],
    "property2": [
      "string"
    ]
  }
}
{
  "statusCode": 0,
  "error": "string",
  "message": "string",
  "code": "validation_failed",
  "failed": [
    {
      "objectLabel": "string",
      "reason": "string",
      "eventKey": "string",
      "environment": "string",
      "booked": 0,
      "held": 0
    }
  ],
  "errors": {
    "property1": [
      "string"
    ],
    "property2": [
      "string"
    ]
  }
}

{
  "statusCode": 409,
  "error": "Conflict",
  "message": "One or more seats are no longer held by this token",
  "code": "not_held_by_token",
  "failed": [
    {
      "objectLabel": "A-13",
      "reason": "not_held_by_token"
    }
  ]
}
{
  "statusCode": 0,
  "error": "string",
  "message": "string",
  "code": "validation_failed",
  "failed": [
    {
      "objectLabel": "string",
      "reason": "string",
      "eventKey": "string",
      "environment": "string",
      "booked": 0,
      "held": 0
    }
  ],
  "errors": {
    "property1": [
      "string"
    ],
    "property2": [
      "string"
    ]
  }
}

Hold seats POST

Places a hold on the requested seat(s) for the calling workspace. Pass `objectLabels[]` — the single canonical request shape — to hold N individual seats atomically under one `holdToken` (all-or-nothing: if any seat cannot be held the whole request fails `409` and nothing is held). A single seat is a one-element array. GA areas ride on a single-element array: pass `objectLabels: ["GA-LAWN"]` together with `objectType: "generalAdmission"`, `quantity`, and (optionally) `categoryKey`; those GA fields are only valid on a one-element array. The hold lasts `ttlSeconds` (default 900). Pass the returned `holdToken` to `/book` to confirm purchase or `/release` to cancel. Holds expire automatically; subscribers to `seat.hold_expired` receive a webhook on expiry. Atomic via Redis SET NX EX — concurrent hold attempts on the same seat never double-book. The response is always the array shape ({ chartKey, eventKey, holdToken, holdExpiresAt, objects: [...] }), whether one seat or many. Safe to retry: seats this `holdToken` already holds count as held (no `409`, expiry unchanged — use `/extend`); `holdExpiresAt` is the earliest expiry across the requested seats (already-held seats keep theirs). For a GA area `quantity` is the token's total: repeating it is a no-op, a different value resizes the hold (`409 ga_capacity_exceeded` if it does not fit; the previous quantity is kept).

Release seats POST

Returns seats to `free`. With `holdToken`, only rows under that token are released (secret key: held and booked; public key: held only — booked seats are never released by a public key). `objectLabels` may be omitted when `holdToken` is given = everything that token holds on the event. Without `holdToken` (secret key only) any held or booked individual seat is released (admin release); a general-admission area always requires `holdToken` (`400 hold_token_required`), and so does every public-key call. Idempotent: labels that are already free — including an expired hold, swept or not — come back in `alreadyFree[]` with `200`. If any label is held/booked by a different token (or booked and the caller is a public key) the whole request fails `409 not_held_by_token` with `failed[]` and nothing is released (all-or-nothing). GA entries in `released[]` carry the freed `quantity`. Emits one `seat.released` webhook per released row.

Book held seats — SeatBuilder Docs