Developers
Quickstart
SeatPublic keeps the seating chart, every seat's live status and the seat map. You keep checkout, payments, customers and tickets. The integration is three parts: your server creates the event and confirms each sale, and the buyer's browser shows the map and holds the seats they pick.
Everything below works with test keys against the sample theatre (demo-theatre). The API lives at https://api.seatpublic.com; every path starts with /v1, bodies are JSON, and errors look like { "error": "code" }.
Keys
Create an account in the dashboard and name your company: it gets test keys straight away. Send a key as the X-Api-Key header.
| Key | Where it goes | What it can do |
|---|---|---|
sk_test_… / sk_live_… | Your server only | Create events, set prices, confirm sales, release seats |
pk_test_… / pk_live_… | Web pages | Read the event and map, receive live updates, hold seats |
Test keys only see test events, and live keys only live ones. Live keys need a confirmed email. Never put a secret key in a web page, a log or a repository.
Step 1Create an event
Once per performance, from your server. venueId picks the venue's seating chart; the event keeps that chart version for as long as it's on sale.
curl -X POST https://api.seatpublic.com/v1/events \
-H "X-Api-Key: $SEATPUBLIC_SECRET_KEY" -H "Content-Type: application/json" \
-d '{ "venueId": "demo-theatre", "startsAt": "2026-12-01T19:30:00Z" }'
→ 201 { "eventId": "ev_…", "chartId": "…", "chartVersion": 1, "environment": "test" }Store eventId with your own event.
Step 2Set prices
Charts have price categories; your event decides what they cost. Overrides are optional and keyed by seat id or section id. Push your own prices here so the map shows exactly what checkout will charge.
{ "categories": { "1": 120, "2": 78, "3": 45 },
"overrides": { "orchestra-A-5": 150 } }Change them whenever you like: open maps update straight away. At checkout, ask the server for the real total with POST /v1/events/{eventId}/quote { "seatIds": [...] } instead of trusting prices sent from the browser.
Step 3Show the map
Load the script and add the tag with your publishable key. The map loads the chart, availability and prices, and stays live as other buyers hold and buy seats.
<script type="module" src="https://seatpublic.com/sdk/seatmap/v0/index.js"></script>
<seat-map api-base="https://api.seatpublic.com" event-id="ev_…"
api-key="pk_test_…" max-selectable="8" no-orphan
style="display:block; height:600px"></seat-map>The element fires seatSelect, seatDeselect, holdCreated, holdExpired and error. It follows the reader's light or dark setting, and colours can be changed with --sc-* CSS properties.
Step 4Hold seats
When the buyer goes to pay, hold what they picked. A hold is all or nothing: either every seat is held or none are.
const map = document.querySelector("seat-map");
try {
const hold = await map.live.holdSelection(); // { holdToken, expiresAt, seatIds }
// send hold.holdToken to your server with the order
} catch (e) {
// e.seats: the seats someone else just took. Nothing was held.
}- 1 to 10 seats per hold. Test holds last 2 minutes, live holds 8.
- Before opening a payment page, extend the hold from your server:
PATCH /v1/events/{eventId}/holds/{holdToken} { "extendSeconds": 1800 }. With the secret key a hold can run up to 60 minutes, enough for a hosted checkout. - If the buyer gives up,
DELETE /v1/events/{eventId}/holds/{holdToken}. Expired holds release their seats on their own.
Step 5Confirm the sale
Only after the payment has succeeded, from your server:
{ "holdToken": "…", "orderRef": "your-order-id" }
→ 200 { "status": "sold", "seatIds": [...], "replayed": false }
→ 404 { "error": "hold_not_found_or_expired" } not sold: refund or void the paymentConfirming is safe to repeat. After a timeout, call it again with the same hold and order: you get "replayed": true and the same seats, never a second sale.
Step 6Refunds
Refund in your system, then put the order's seats back on sale with POST /v1/events/{eventId}/seats/release { "orderRef": "your-order-id" }. GET /v1/events/{eventId}/orders/{orderRef} shows what an order holds right now.
Webhooks
Register an HTTPS endpoint with POST /v1/webhooks { "url": "…", "events": ["hold.expired", "hold.confirmed", "seats.released"] }. The response includes a secret, shown once.
- Each delivery carries
SeatPublic-Event,SeatPublic-Delivery(unique: use it to ignore repeats) andSeatPublic-Signature: t=…,v1=…. - To verify, compute HMAC-SHA256 with the secret over
"<t>.<raw body>", hex-encoded, and compare it withv1in constant time. Reject anything older than 5 minutes. - Answer with any 2xx quickly. Failed deliveries are retried with backoff.
Events: event.created, hold.created, hold.confirmed, hold.expired, seats.released, seat.status_changed, chart.version_published, event.cancelled, event.chart_changed, webhook.ping.
Box office screen
For staff, your server makes a short-lived pass for one event and one person with POST /v1/events/{eventId}/manager-tokens, and your page opens the seat manager with it. Staff can block seats, name holds, change categories and undo. Passes can never sell or refund: the screen hands those back to your own system.
Your own venues
Draw a venue in the chart editor, from a template, from scratch or traced over a floor plan. Sign in to the dashboard and the editor publishes for your organization; no key needed. Charts are shared: if someone has already published the venue, you'll be offered theirs, and improvements go in as reviewed edits.
- Adding venues and public charts needs a confirmed email.
- Your first public charts are unlisted until we've had a look: your own events can use them straight away, other companies see them once they're listed.
- Something wrong with a chart?
POST /v1/charts/{chartId}/report { "reason": "…" }and we'll review it.
Going live
- Confirm your email, then create live keys in the dashboard (owners and admins).
- Swap
sk_test_/pk_test_forsk_live_/pk_live_. Nothing else changes. - Keep the live secret key in your server's secret store.
API reference
| Call | Key | What it does |
|---|---|---|
POST /v1/events | secret | Create an event for a venue |
GET /v1/events/{id} | either | The event, its chart and version |
GET|PUT /v1/events/{id}/pricing | either / secret | Prices by category, overrides by seat or section |
GET /v1/events/{id}/availability | either | Every seat that isn't free; supports ETag |
POST /v1/events/{id}/quote | either | Prices and total for a list of seats |
POST /v1/events/{id}/holds | either | Hold 1–10 seats, all or nothing |
PATCH|DELETE /v1/events/{id}/holds/{token} | either | Extend or release a hold |
POST /v1/events/{id}/confirm | secret | Sell a hold to an order; safe to repeat |
GET /v1/events/{id}/orders/{orderRef} | secret | The seats an order holds now |
POST /v1/events/{id}/seats/release | secret | Put an order's seats back on sale |
PATCH /v1/events/{id}/seats | secret | Block, unblock or sell at the box office |
POST /v1/events/{id}/cancel | secret | Cancel the event; lists the orders to refund |
POST /v1/events/{id}/manager-tokens | secret | A staff pass for the box office screen |
POST /v1/webhooks | secret | Register a webhook endpoint |
GET /v1/charts/{id}/versions/{v} | either | A chart: sections and seats |
Questions? Write to hello@seatpublic.com.