General admission
The full GA quantity lifecycle — hold, extend, book, release — with contract-verified request and response JSON, remaining-capacity semantics, and the SDK selection flow.
General admission
A general-admission (GA) area is not a single seat — it is a capacity
pool. Instead of holding one labelled seat, you reserve a quantity of
tickets against the area's total capacity, and many buyers can each hold
part of the same area concurrently. This guide walks the full server-side
GA lifecycle: hold a quantity, extend the hold, book it, and
release it. Every JSON block below is copied verbatim from the API
contract (openapi.json) so what you read here is exactly what the API
sends and accepts.
For individual reserved seats, see Hold and book lifecycle — the calls are the same endpoints, but the GA fields and the capacity semantics below are what make an area a pool rather than a seat. For the concepts behind seat statuses, see Core concepts.
All write calls authenticate with a secret key
(X-Api-Key: sk_live_xxx); the read calls accept a public or secret key.
objectLabel is the GA area KEY, not a seat label
The single most important distinction: for a GA area, the value you pass
in objectLabels[] is the area's key from the published chart
(e.g. "GA-LAWN"), not an individual seat label like "A-12". A
seat's label names one physical seat; a GA area's key names a whole
capacity pool that you draw a quantity from. Passing a GA area key where
the server expects a seat — or a seat label where it expects a GA area —
is the classic integration mistake this guide exists to prevent.
GA holds require BOTH objectType and an explicit quantity
To hold against a GA area you MUST pass, on a single-element
objectLabels array:
objectType: "generalAdmission"— the server does not infer GA from the label; and- an explicit non-zero
quantity— the number of tickets to reserve.
There is no silent default-to-1 for GA. A single-element
objectLabels array that names a GA area but omits
objectType:"generalAdmission" (a "bare" GA label) is rejected 400 —
it is never quietly held as an individual seat. This guard exists so a
bare hold can never oversell a pool by being mistaken for a one-seat
reservation.
1. Hold a quantity from the GA area
Reserve quantity tickets from the area's pool with
POST /events/{eventKey}/seats/hold. Pass the area key in a
single-element objectLabels[], plus objectType:"generalAdmission" and
the quantity. The holdToken ties the reservation to a buyer session.
A successful hold returns 201 with the array-shape response
(objects[] carries one entry for the area) and its holdExpiresAt.
POST /api/v1/events/evt_q3-2026-jazz-night/seats/hold HTTP/1.1
Host: seatbuilder.org
X-Api-Key: sk_live_xxx
Content-Type: application/json
{"objectLabels":["GA-LAWN"],"objectType":"generalAdmission","quantity":40,"holdToken":"7f6c3a91-b1ef-4d0a-9c84-3f12a8e0b5d2"}{"chartKey":"chart_8a2b1c","eventKey":"evt_q3-2026-jazz-night","holdToken":"7f6c3a91-b1ef-4d0a-9c84-3f12a8e0b5d2","holdExpiresAt":"2026-10-02T18:55:00.000Z","objects":[{"objectLabel":"GA-LAWN","status":"held","quantity":40}]}The hold succeeds only if
capacity − sum(held + booked quantity across all tokens) ≥ quantity,
checked atomically. If the pool cannot cover the request the hold is
rejected 409 (pool full). A single request whose quantity alone
exceeds the total area capacity is rejected 400. A bare
{"objectLabels":["GA-LAWN"]} with no objectType/quantity is
rejected 400.
2. Extend the GA hold
Extend the payment window on an active GA hold with
POST /events/{eventKey}/seats/extend, passing the holdToken (and,
optionally, the area key in objectLabels[]; omit it to extend every
object held by the token). ttlSeconds defaults to 600 (10 min) and is
capped at 3600 (1 hour). Success returns 200 with the new
holdExpiresAt and the extended[] labels.
POST /api/v1/events/evt_q3-2026-jazz-night/seats/extend HTTP/1.1
Host: seatbuilder.org
X-Api-Key: sk_live_xxx
Content-Type: application/json
{"holdToken":"7f6c3a91-b1ef-4d0a-9c84-3f12a8e0b5d2","objectLabels":["GA-LAWN"],"ttlSeconds":600}{"holdExpiresAt":"2026-10-02T19:05:00.000Z","extended":[{"objectLabel":"GA-LAWN"}],"failed":[]}3. Book the held quantity
At checkout, confirm the reserved quantity with
POST /events/{eventKey}/seats/book. Book the GA area under its GA
hold — pass the same holdToken from step 1 and the area key in
objectLabels[]. A bare GA-area label with no matching GA hold is
rejected 400; a GA area is never booked as an individual seat. Success
returns 200 with a booked[] array.
POST /api/v1/events/evt_q3-2026-jazz-night/seats/book HTTP/1.1
Host: seatbuilder.org
X-Api-Key: sk_live_xxx
Content-Type: application/json
{"objectLabels":["GA-LAWN"],"holdToken":"7f6c3a91-b1ef-4d0a-9c84-3f12a8e0b5d2","extraData":{"orderId":"ord_98a2"}}{"booked":[{"chartKey":"chart_8a2b1c","eventKey":"evt_q3-2026-jazz-night","objectLabel":"GA-LAWN","status":"booked","bookedAt":"2026-10-02T18:46:00.000Z","extraData":{"orderId":"ord_98a2"}}]}Booking a GA hold is capacity-neutral: the held quantity becomes
booked, so sum(held + booked) is unchanged and the area's remaining
does not move at book time.
4. Release the GA hold — holdToken is REQUIRED
Give a GA reservation back to the pool with
POST /events/{eventKey}/seats/release. For a GA area the holdToken is
required: a GA area holds many concurrent rows (one per token), so a
release must be token-scoped and frees only that token's reserved
quantity. Releasing a GA area without a holdToken is rejected 400
with "holdToken is required to release a general-admission area" —
nothing is freed and no per-row state is disclosed. (Individual seats, by
contrast, release token-agnostically.)
POST /api/v1/events/evt_q3-2026-jazz-night/seats/release HTTP/1.1
Host: seatbuilder.org
X-Api-Key: sk_live_xxx
Content-Type: application/json
{"objectLabels":["GA-LAWN"],"holdToken":"7f6c3a91-b1ef-4d0a-9c84-3f12a8e0b5d2"}{"chartKey":"chart_8a2b1c","eventKey":"evt_q3-2026-jazz-night","status":"free","releasedAt":"2026-10-02T19:15:30.000Z","released":[{"objectLabel":"GA-LAWN"}]}Omitting the holdToken for a GA area returns 400:
{"statusCode":400,"message":"holdToken is required to release a general-admission area","error":"Bad Request"}Remaining-capacity semantics
A GA area's availability is derived from ONE definition:
remaining = capacity − sum(held + booked quantity across all tokens)Every hold reserves quantity and reduces remaining; a release frees
that token's quantity back; a booking is capacity-neutral (held → booked
keeps the sum). Read the live remaining per area with
GET /events/{eventKey}/objects — the remaining field reflects the
current pool state, so you can decide up front whether a requested
quantity will fit before you attempt the hold.
GET /api/v1/events/evt_q3-2026-jazz-night/objects?labels=GA-LAWN HTTP/1.1
Host: seatbuilder.org
X-Api-Key: pk_live_xxx{"eventKey":"evt_q3-2026-jazz-night","objects":[{"objectLabel":"GA-LAWN","objectType":"generalAdmission","categoryKey":"lawn","capacity":100,"remaining":60}]}SDK GA selection flow
In the browser SDK, a GA area stays interactive as long as remaining > 0
and renders an "N left" capacity indicator (Phase 50). When a buyer
picks a quantity with the GA quantity picker, the SDK's onObjectSelected
callback fires with the area key and, for a multi-ticket GA selection, the
selected quantity:
SeatBuilder.render({
// ...config
onObjectSelected: (event) => {
// event.objectLabel — the GA area KEY (e.g. "GA-LAWN")
// event.quantity — present for a GA selection of more than one ticket
console.log(event.objectLabel, event.quantity);
},
});quantity is undefined for a single individual-seat selection and is
populated only when a GA selection reserves more than one ticket, so your
handler can treat "has quantity" as "this was a GA pool selection." The
SDK never treats another session's partial GA hold as "held-by-other" —
the area stays selectable while capacity remains.
Putting it together
The GA lifecycle is: hold a quantity against the area key (step 1,
always with objectType:"generalAdmission" + explicit quantity),
extend the payment window if needed (step 2), book the reserved
quantity under the same holdToken at checkout (step 3), and release
anything abandoned — always with the holdToken for a GA area
(step 4). Track remaining = capacity − sum(held + booked) to know
whether a request will fit, and surface the SDK's "N left" indicator so
buyers see live pool availability.
For the individual-seat equivalent of this flow see Hold and book lifecycle; for the end-to-end checkout choreography see Hold seats and confirm at checkout; and for the webhook that tells your backend a hold expired or a booking landed, see Webhooks.