Changelog
Notable changes to the SeatBuilder REST API and JavaScript SDK, newest first.
Changelog
2026-10-07 · v1.25.0
Added
POST /events/{eventKey}/actions/move-to-new-chart-copy: detaches one event from a chart shared with other events. The chart's published version is copied into a new published chart and the event is moved to it; holds and bookings are kept. Republishing either chart no longer affects the other. Owners and admins can do the same from the event page in the dashboard ("Give this event its own chart").event.chart_changedwebhook: sent when an event is moved to its own chart, witheventKey,chartKeyandpreviousChartKey.
Fixed
- Duplicating a chart in the dashboard now respects the plan's chart limit.
- Dashboard: users in more than one workspace now always work in the workspace selected in the switcher. Charts, webhooks and uploads could previously act on another workspace the user owned, and events, API keys and the live seat viewer could fail with "Requested workspace does not match active session".
Changed
- Publishing is refused with
400 invalid_section_geometrywhen a section has nopoints[]polygon with at least 3 points. The SDK can't draw such a section, so its seats would be unreachable for buyers. (v1.24.1)
2026-10-02 · SDK 5.10.0
Changed
- Phones: tap to toggle, with a seat card that doesn't cover the map. In the compact layout a tap on a free seat selects it and a tap on a selected seat deselects it, like a click on desktop. Instead of a bottom sheet, a small card next to the seat shows its section, row, seat, category and status, so you can keep tapping other seats. General-admission areas open the same card with a quantity stepper.
- Desktop seat tooltip now uses the same light card style.
Fixed
- The seat card updates immediately when you click the seat you're hovering.
- Phones: removing your last seat no longer shows a "Seat just taken" message.
Removed
- The
viewInMapmessage key (only used by the removed mobile seat sheet).
2026-10-01 · SDK 5.9.0
Added
- Compact (mobile) layout. In a container under 640px wide (or a short
landscape phone) the seat map switches to a touch-first layout: tap a
section to enter it, tap a seat to select it and see its details, a tray
with the selection, a key, and search by section, row or seat. A one-finger
swipe scrolls the page until the map is zoomed in. New options:
mobile,mobileBreakpoint,mobileTray,onLayoutChange; newchart.getLayout(). See Render a seat map.
Fixed
- Seat numbers and section badges now update after animated camera moves.
2026-10-01 · v1.20.0
Changed
- Publishing a chart no longer silently changes sold seats.
POST /charts/{chartKey}/publishis refused with409 publish_affects_reservationswhen the draft would remove, re-type (seat ↔ GA) or re-categorise a booked or held label on any event of the chart. It is also refused when a GA area would shrink below its booked + held quantity.failed[]lists every affected label per event. Pass?force=trueto publish anyway. The dashboard shows the same list and asks before publishing. 429responses now carrycode: "rate_limited"andRetry-After: 1.
Added
GET /charts/{chartKey}/publish-check: a dry run that lists the same affected labels without publishing.
2026-09-30 · v1.19.0
Added
- Caller-chosen
eventKeyonPOST /events(1–64 characters,A-Z a-z 0-9 _ -). With your own key the call is safe to retry: the sameeventKey+chartKeyreturns the existing event with200. A key already used elsewhere returns409 event_key_conflict. See Multiple performances. chartKeyin thePOST /eventsresponse.- Rate limits are now documented.
Changed
DELETE /events/{eventKey}is refused with409 event_has_reservationswhile any seat is booked or live-held. Previously it deleted the holds and bookings along with the event. The response now also includeseventKey:{ "eventKey": "…", "deleted": true }.
2026-09-28 · v1.18.1 (SDK 5.8.1)
Added
onSelectionChange— fires with the full selection snapshot on every change (tap,selectObjects, hold confirmed or failed, expiry, restore, clear): each object'sholdStatus(pending/held),count,byCategorytotals and apendingflag. Enable checkout when!selection.pending && selection.count > 0. Recommended over the per-object callbacks.chart.getSelectedObjects()andchart.getSeatInfo(label)(section, row, seat number, category, live status, bounds).- Camera methods:
fitVenue(),focusSection(key),focusSeat(label),focusSeats(labels),fitSelection(). - Overview sections that hold selected seats show a "✓ n" badge.
Fixed
- Own holds restored after a page refresh or a realtime reconnect are part
of the selection again — they previously showed as selected but did not
count toward
maxSelectedObjectsand were not released byclearSelection(). - State changes repaint only the affected seats, instead of the whole map.
- Previously released SDK bundles stay available at their pinned
/sdk/v{X.Y.Z}/path after an SDK release (/sdk/v5.7.1/keeps working).
Changed
- Max zoom is relative to seat size (a seat at most ~96px on screen) instead of a fixed 10×.
- Reset in a section returns to the overview; section drill-in is framed so seats render at most ~40px; the section picker, Escape and back animate.
No API or server changes; SDK 5.8.1 works with server v1.16.0+.
SDK 5.8.0 (briefly served on 2026-09-28) did not render in browsers; do not pin /sdk/v5.8.0/.
2026-09-26 · v1.17.0
Fixed
bookanswers410 hold_expiredandextendanswersreason: "hold_expired"after the expiry sweep too. Previously this only worked in the few seconds between expiry and the background sweep; after the sweep,bookreturned409 not_held_by_tokenandextendreturned409 no_active_hold. An expired hold is now remembered for its token until the seat is held again (by any token) or an operator force-releases or changes it; for a GA area the record stays for that token. Seats and general-admission areas behave the same.- Releasing an expired hold returns
alreadyFreewhether or not the expiry sweep has run (before the sweep it was reportedreleased), and a laterbookwith that token still answers410 hold_expired. If another token has since held the seat, you still get409 not_held_by_token. - Booking a general-admission area without a GA hold under the token
returns
409 not_held_by_token(withfailed[], like a seat), not400 bad_request.
Added
chartKeyonGET /events/{eventKey}, next tochartId— the same value/holdand/bookreturn.- Display labels on
GET /events/{eventKey}/objects:labelon general-admission objects (the area's name from the chart) andsectionLabelon objects that belong to a section.objectLabelstays the key you pass to/holdand/book.
2026-09-25 · SDK 5.7.1
- Fixed: the
5.7.0package on npm was built from stale sources and contained the 5.6.0 code. Install@seatbuilder/[email protected](same source as 5.7.0, built correctly); 5.7.0 is deprecated. The/sdk/script-tag bundle was not affected.
2026-09-23 · v1.16.0 / SDK 5.7.0
A seat-integrity release: tightens who can book, extend, and release a
seat; makes hold expiry and every write endpoint behave consistently
under retries; and stops /status from leaking internal state.
Breaking for integrators
bookandextendare now secret-key only. A publishable (pk_*) key calling either now gets403 public_key_not_allowed. These were never intended for browser code; if anything in your stack called them with a public key, move that call to your server.- A public key releasing a seat must now pass
holdToken, and only freesheldrows under that exact token — never a booked seat. Releasing without a token from a public key now fails400 hold_token_requiredinstead of releasing token-agnostically. GET /statushas a new response shape. Internal database ids and legacy rows are gone; general-admission areas are now one aggregated entry per area (capacity/held/booked/remaining) instead of one row per hold; and hold tokens are no longer returned at all — pass your own token as?holdToken=to get it flagged back viamine/mineQuantityinstead. See Hold and book lifecycle.- Every error body now carries a stable
codefield. Branch your integration oncode, not onmessagetext, which may still change wording between releases. See Error model for the full table. - SDK 5.7.0 is required for hold-mode embeds against this server. An
older SDK keeps rendering, but its own holds appear held-by-other
after a refresh and deselect no longer releases the seat (it still
frees itself at hold expiry). Upgrade with
npm install @seatbuilder/[email protected]— see Versioning.
Fixed
- A hold's expiry was sometimes never enforced: repeated holds on the same seat could silently collide internally, so the seat's expiry timer never fired and a lapsed hold could keep blocking new buyers. Hold expiry is now exact on every read and write, and each hold's expiry is tracked independently.
- Booking a general-admission reservation used to clear the token that made the booking, leaving that booking impossible to release afterward. Booked rows now keep their owning token internally (it is never exposed in responses or webhooks).
- Releasing an individual seat did not check who was releasing it — any caller could release a seat held or booked by someone else. Release now always verifies the caller's token against the row it's releasing.
- Holding a seat or general-admission quantity a second time with the same token — a natural retry — could fail or double-reserve capacity instead of behaving as a no-op. Hold, book, extend, and release are now all safe to retry.
- Retrying a booking call after it had already succeeded returned an error instead of the original successful result.
- Releasing several seats in one call failed the entire batch if even one of them was already free. Already-free labels are now reported separately and don't block the rest of the release.
- Extending a hold that had already been booked, had already expired, or belonged to another token returned an unhelpful generic failure. Extend now reports which of those three happened per seat.
- Documentation incorrectly stated that charts are isolated per environment. Charts are shared workspace-wide by design (you design a seat map once and reuse it across production and sandbox events); events, holds, and bookings remain strictly environment-isolated. See Authentication.
GET /statusreturned internal database identifiers, stale legacy rows, and — for any caller holding a publishable key — every buyer's hold token for the event. All three are gone from the new response shape.- Error responses had no consistent machine-readable identifier, making it impossible to branch on a specific failure without parsing human-readable text.
- Releasing a general-admission area could, under a specific legacy-data condition, be misclassified as an individual seat release, skipping the token check and freeing every buyer's reservation in that area at once. GA-vs-seat is now always decided from the event's published chart, never from ambiguous stored data.
Added
POST /events/{eventKey}/reset— secret key, sandbox events only. Wipes an event's seat state (holds and bookings) in one call, for repeatable automated test runs. See Hold and book lifecycle.- Release by token.
objectLabels[]is now optional wheneverholdTokenis given — omit it to release everything that token holds (and, for a secret key, holds or has booked) on the event. mine/mineQuantityon/status. Pass?holdToken=<yours>to have your own live holds flagged in the response, replacing the old per-row token comparison integrators had to do client-side.
Error model
The exact runtime envelope every SeatBuilder error response carries, the stable machine-readable code table, and what each HTTP status means.
Integration walkthrough
End-to-end integration — create a chart, publish it, run an event, render the SDK, hold a seat, book it, and verify the webhook delivery.