№ 01For developers
The box office
is an API.
Events, tickets, orders, payments, check-in and payouts as clean REST resources so ticketing becomes a feature of your product, not a redirect to someone else's.
$ curl https://api.zatabox.com/api/v1/organizer/events \ -H "Authorization: Bearer vt_live_…" \ -H "Idempotency-Key: 1f6a…" \ -d '{ "title": "Warehouse Sessions 004", "startDate": "2026-08-22T20:00:00Z", "capacity": 450 }' HTTP/2 201 { "data": { "id": "evt_92k1", "slug": "warehouse-sessions-004", "status": "draft" } }White-label by default your buyers never see our name
№ 02The primitives
Eight resources. The whole machine.
If you can model it in your head, you can build it: every box-office concept is a resource with predictable verbs.
Events
01POST /api/v1/organizer/events
create, update, publish, cancel
Ticket types
02POST /api/v1/organizer/events/{id}/tickets
GA, VIP, early-bird, comps, presale codes
Orders
03POST /api/v1/orders
carted items, guest checkout, promo codes
Payments
04POST /api/v1/orders/{id}/pay
nowpayments (crypto) · paystack · flutterwave
Check-in
05POST /api/v1/checkin/scan
QR or 6-digit short code, offline-safe
Live stats
06GET /api/v1/checkin/event/{id}/live
server-sent events, entry rate per gate
Buyers
07GET /api/v1/users/me/tickets
wallet, refunds, reports, messaging
Webhooks
08POST /api/v1/webhooks
27 signed event types, replayable
OpenAPI 3.1 spec SDKs are generated from it, so they never drift
№ 03Developer experience
Built the way you'd build it.
The unglamorous decisions that make 2 a.m. integrations boring in the best way.
01
Idempotency on every write
Send an Idempotency-Key (any UUID) with any POST, PUT, PATCH or DELETE. Replays within 24 hours return the original response a double-clicked checkout can't double-charge.
POST /api/v1/ordersIdempotency-Key: 9f1c7e2a-44b1-4b9e-bb1a-0c6e2f6a2d11 # same key + same body → cached 201, same order# same key + new body → 409 IDEMPOTENCY_KEY_REUSED02
Errors you can program against
Every error is the same envelope: a stable machine code, a human message, field-level details, and a request_id you can hand to support.
{ "error": { "code": "TICKET_SOLD_OUT", "message": "General Admission is sold out", "details": { "ticketTypeId": "tkt_8f2k" } }, "meta": { "request_id": "req_01J9…" }}03
Cursor pagination, rate-limit headers
Lists paginate by cursor no page drift under load. Every response carries your remaining budget; 429s include Retry-After.
GET /api/v1/events?limit=20&cursor=eyJpZCI6… X-RateLimit-Limit: 1000X-RateLimit-Remaining: 873X-RateLimit-Reset: 173987280004
Test keys that can't touch money
Mint vt_test_ keys from the portal and point your integration at test-mode providers webhooks, MCP and all four processors in test mode. Environment fencing rejects a test key on a live deployment (and vice versa) with 403 WRONG_ENV. A dedicated sandbox host is rolling out.
# flip one prefix, nothing elseZATABOX_API_KEY=vt_test_… # → test modeZATABOX_API_KEY=vt_live_… # → production№ 04Least privilege
Keys that grant exactly enough.
Scoped API keys with a granular grammar your check-in kiosk can scan tickets and do nothing else.
events:readevents:writetickets:readtickets:writeorders:readorders:writeattendees:readattendees:writecheckin:writepayouts:readpayouts:writewebhooks:manageanalytics:read* org admins only№ 05Widgets & SDKs
Or skip the build entirely.
Drop-in web components under 30KB, skinnable with CSS variables, WCAG 2.1 AA out of the box. Wrappers for React, Vue and Svelte.
npm install @zatabox/nodeon the roadmap generated from the OpenAPI specon the roadmap generated from the OpenAPI specon the roadmap generated from the OpenAPI specon the roadmap generated from the OpenAPI spec<script src="https://zatabox.com/v1/widgets.js" defer></script> <!-- pre-styled card for listings and landing pages --><zatabox-event-card org-id="9f2b…" event-id="5e6f…"></zatabox-event-card> <!-- opens the hosted passwordless checkout in a popup --><zatabox-checkout org-id="9f2b…" event-id="5e6f…" label="Get tickets"></zatabox-checkout>// Works in any framework the widgets are plain web components.// React/Vue/Svelte wrapper packages follow with the selector widget.useEffect(() => { document.querySelector('zatabox-checkout') ?.addEventListener('zatabox:opened', (e) => analytics.track('checkout', e.detail))}, [])