Moving from seats.io
Most of what you built on seats.io maps one to one. Bring your charts across with their labels, point your calls at venuery, swap the renderer, then move live events with their bookings. You can do it one venue, or one event, at a time.
1. Bring your charts
In the dashboard, open a venue and use From seats.io: enter the chart key, your region and your seats.io secret key (used once, to read the chart, and never stored), or upload the drawing's JSON. Through the API, send the drawing as the chart's document:
# the drawing, from seats.io (use your region: eu, na, sa, oc)
curl https://api-eu.seatsio.net/charts/CHART_KEY/version/published -u SEATSIO_SECRET: > drawing.json
curl https://www.venuery.com/v1/charts -u sk_test_…: \
-d '{"venue":"ven_…","name":"Main stand","document":'"$(cat drawing.json)"'}'Sections, rows, seats, tables, booths, general admission areas, categories, accessibility flags, zones, floors, focal points, shapes and text come across. Background images don't: upload them in the designer to trace over. Seats keep their seats.io labels, so labels you've stored still work. Check the chart in the designer, then publish it.
2. Map labels to ids (optional)
Every call here takes labels, so you can keep using them. Labels can change when a venue renames a row, though, and ids never do. To store ids instead, fetch the label map of a published version:
curl "https://www.venuery.com/v1/charts/cht_…/versions/1?expand=labels" -u sk_test_…:
{ "labels": { "A-1-12": "s_4k9q2m8f1x7c", "T1": "t_8f1x7c4k9q2m", "Lawn": "p_lawn", … } }3. Point your calls at venuery
Authentication works the same way: HTTP Basic with the secret key as the username is accepted, as well as bearer tokens. Change the base URL to https://www.venuery.com/v1 and map the calls:
| seats.io | venuery |
|---|---|
POST /charts (then draw) | POST /v1/charts, with the drawing as document |
POST /charts/{key}/version/draft/actions/publish | POST /v1/charts/{id}/publish |
POST /events {chartKey, eventKey} | POST /v1/events {chart, key} |
POST /events (several) | POST /v1/events/bulk |
GET /events/{eventKey} | GET /v1/events/{key} |
POST /hold-tokens | POST /v1/holds (the hold is the token and its seats) |
POST /hold-tokens/{token} {expiresInMinutes} | PATCH /v1/holds/{id} {ttl_seconds} |
POST /events/{key}/actions/hold {objects, holdToken} | PATCH /v1/holds/{id} {add: {objects}} |
POST /events/{key}/actions/book {objects, holdToken, orderId} | POST /v1/events/{key}/book {hold or objects, order_ref} |
POST /events/{key}/actions/release {objects} | POST /v1/events/{key}/release {objects or order_ref} |
POST /events/{key}/actions/change-object-status {status} | POST /v1/events/{key}/block {status}, and /unblock |
POST /events/{key}/actions/mark-as-not-for-sale | POST /v1/events/{key}/block (status not_for_sale) |
POST /events/actions/change-object-status (batch) | POST /v1/status_changes/batch |
POST /events/{key}/actions/book-best | POST /v1/events/{key}/best_available {action: "book"} |
GET /events/{key}/objects/{label} | GET /v1/events/{key}/objects?labels=… |
GET /reports/events/{key}/byStatus | GET /v1/events/{key}/availability |
Webhooks | POST /v1/webhook_endpoints |
A few things work differently, mostly in your favour:
- Holds are objects. Create one with the seats and get its id, rather than making a token and then holding seats with it. Extend it any time, up to two hours from creation.
- POSTs can be retried safely. Send an
Idempotency-Key; a retried booking replays its first response rather than failing because the seats are already booked. - Errors list the seats. A 409 names every conflicting seat in
conflicts, with its status. - Release by order.
releasewith anorder_reffrees everything in that order, GA places included. - Events pin a chart version. Publishing a new version never moves seats under events already on sale.
| seats.io term | venuery |
|---|---|
workspace secret key | secret key (sk_live_…); test keys (sk_test_…) replace the sandbox workspace |
workspace public key | publishable key (pk_live_…) |
holdToken | hold id (hld_…) |
orderId | order_ref |
extraData | metadata |
free / reservedByToken / booked | available / held / booked |
forSale: false | status not_for_sale |
custom status | custom status (lowercase, letters, digits and _) |
object label (A-1-12) | label (A-1-12) or stable id (s_…) |
4. Swap the renderer
venuery.js takes the same shape of configuration. The biggest difference: every pick is held straight away, so there's no session to manage.
| seats.io renderer | venuery.js |
|---|---|
new seatsio.SeatingChart({ divId, workspaceKey, event }) | new venuery.SeatingChart({ container, publicKey, event }) |
pricing: [{ category, price }] | pricing: { categoryKey: "$25" } |
session: "continue" | always on: picks are held as they're made |
maxSelectedObjects | maxSelectedObjects |
onObjectSelected / onObjectDeselected | onObjectSelected / onObjectDeselected |
onHoldFailed, onHoldTokenExpired | onHoldFailed, onHoldExpired |
chart.holdToken | chart.holdId |
chart.selectedObjects | the selection passed to onSelectionChanged |
5. Move live events
Create the event here on the imported chart, then bring its state across from a seats.io event report. Booked seats are booked here under the same orderId with their extraData; custom statuses become blocked statuses; seats not for sale stay off sale. Do a dry run first:
# the event's state, from seats.io (keep only the fields the import reads)
curl https://api-eu.seatsio.net/reports/events/EVENT_KEY/byStatus -u SEATSIO_SECRET: \
| jq 'map_values(map(select(.status != "free" or .forSale == false or .objectType == "GeneralAdmissionArea")
| {label, status, orderId, extraData, forSale, objectType, numBooked, numNotForSale}))' > report.json
curl https://www.venuery.com/v1/events/EVENT_KEY/import -u sk_test_…: \
-d '{"dry_run":true,"report":'"$(cat report.json)"'}'The answer counts what would be booked and blocked and lists labels the chart doesn't have. When it looks right, send it again without dry_run. The import is all or nothing, and refuses seats already taken here, so it's safe to retry.
- Statuses meaning someone owns the seat default to
bookedandresale. If you used custom statuses for sales (saysold), passbooked_statuses. - seats.io reports a GA area's total booked, not each order's share, so by default it becomes one booking. To keep orders apart, pass
ga_bookingswith each order's quantity from your own records. - Held seats aren't imported: holds are short-lived, so finish or cancel checkouts on seats.io before you switch.
- Imported bookings aren't billed again and don't send webhooks.
- Very large reports: keep each request under 4 MB by splitting by status or by order.