№ 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.
A drop-in web component under 20KB, skinnable with CSS variables, WCAG 2.1 AA out of the box. Works in plain HTML and any framework.
Five official SDKs Node, Python, Ruby, PHP and Go each zero-dependency and installed straight from GitHub, no package registry. One public repo, generated from the API spec so they never drift.
pip install "git+https://github.com/mysmartrobot/zataboxtickets#subdirectory=python"go get github.com/mysmartrobot/zataboxtickets/gogem "zatabox", git: "https://github.com/mysmartrobot/zataboxtickets.git", glob: "ruby/*.gemspec"git clone …/zataboxtickets && npm i ./zataboxtickets/nodecomposer path repo, or require php/src/autoload.php<script src="https://widget.zatabox.com/v1/widgets.js" defer></script> <!-- your organization's events as a grid; click a card for the full-screen event page + in-page checkout --><zatabox-events org-id="9f2b…" search location></zatabox-events>// Works in any framework it's a standard custom element.useEffect(() => { document.querySelector('zatabox-events') ?.addEventListener('zatabox:purchased', (e) => analytics.track('order', e.detail))}, [])