Core API
REST reference.
Plain REST and JSON over HTTPS. Two environments, one error envelope, cursor pagination and idempotent writes read this page once and the rest of the API holds no surprises.
Base URLs & environments#
Every path in this reference lives under /api/v1. Test mode runs on vt_test_ keys today; a hosted sandbox at sandbox.api.zatabox.com is on the roadmap the environment fencing already exists, so a key sent to a deployment pinned to the other environment gets a 403 WRONG_ENV rather than a quiet mistake.
Production https://api.zatabox.comTest mode same host, vt_test_ keys (hosted sandbox.api.zatabox.com on the roadmap)Authentication#
All requests authenticate with a bearer token. First-party clients send the JWT issued at login; servers send an API key vt_live_ for production, vt_test_ for test mode. Buyers never set a password: buying needs only a full name and email (the account is created automatically), and logging back in is an emailed 6-digit code via /auth/token/request + /auth/token/exchange. Guest orders also return a per-order access token for reading that order without logging in.
# First-party (a user session)curl https://api.zatabox.com/api/v1/users/me \ -H "Authorization: Bearer eyJhbGciOi…" # Server-to-server (an API key)curl https://api.zatabox.com/api/v1/events \ -H "Authorization: Bearer vt_live_…" # Buyer login no passwords, an emailed 6-digit codecurl -X POST https://api.zatabox.com/api/v1/auth/token/exchange -d '{"email":"[email protected]","code":"482913"}'API keys are scoped. Grant the narrowest set that does the job: events:read · events:write · tickets:read · tickets:write · orders:read · orders:write · attendees:read · attendees:write · checkin:write · payouts:read · payouts:write · webhooks:manage · analytics:read. The wildcard * exists but is admin-only.
Errors#
Failures always arrive in the same envelope. Log meta.request_id quote it to support and we can find the exact request.
{ "error": { "code": "TICKET_SOLD_OUT", "message": "Not enough inventory left on tkt_8f2k.", "details": null }, "meta": { "request_id": "req_…" }}VALIDATION_ERRORthe body or query string failed validation.UNAUTHORIZEDmissing, expired or malformed credentials.FORBIDDENvalid credentials, insufficient scope.NOT_FOUNDno such resource, or not yours to see. Resource-specific variants exist:ORDER_NOT_FOUND,EVENT_NOT_FOUND,TICKET_TYPE_NOT_FOUND.TICKET_SOLD_OUTthe requested quantity is no longer available.EXCEEDS_MAX_PER_ORDER/EXCEEDS_MAX_PER_CUSTOMERover a ticket type's purchase caps.PRESALE_CODE_REQUIREDthe ticket type is gated behind a presale code.IDEMPOTENCY_KEY_REUSEDsame key, different body. Returned as a409. Its siblingIDEMPOTENCY_IN_FLIGHTmeans a concurrent duplicate is still running.RATE_LIMITEDover the limit; honorRetry-After.VELOCITY_LIMIT_EXCEEDEDpurchase velocity checks tripped.PROVIDER_NOT_CONFIGUREDthat payment provider has no credentials on this deployment.WRONG_ENVa key sent to a deployment pinned to the other environment. Returned as a403.
Pagination#
List endpoints paginate with cursors: pass ?limit=20&cursor=…, read nextCursor from the response, and feed it back until it comes back null. Cursors are opaque treat them as tokens, not offsets.
{ "data": { "items": [ { "id": "evt_…", "slug": "friday-salsa-night", "status": "published" } ], "nextCursor": "…" }}Idempotency#
Send an Idempotency-Key header any UUID on every POST, PUT, PATCH and DELETE. For 24 hours, replaying the same key returns the cached original response instead of running the write again, so a retried request can never double-charge or double-create.
POST /api/v1/orders HTTP/1.1Idempotency-Key: 6f2c1e8a-4b0d-4c5f-9a37-58e21cd9b144Rate limits#
- 120 requests/min public reads.
- 20 requests/min auth endpoints (login, register, code requests).
- 30 requests/min strict writes (orders, payments and the like).
- 120 requests/min check-in scanning.
- 300 requests/min inbound provider webhooks (the /payments/webhook/* receivers). Webhook management writes use the 30/min strict tier.
- 600 requests/min admin.
- MCP tool calls hit these same endpoints and inherit their limits there is no separate MCP quota.
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 429 RATE_LIMITED adds Retry-After back off for that many seconds rather than guessing.
Endpoints#
Path parameters appear in braces. Organizer routes require an organizer JWT or an API key with the matching write scope; buyer routes under /users/me require that buyer's token. Rows with a chevron expand to the full field reference fields are JSON body fields unless tagged query, path or header, and req marks the ones you must send.
Auth
POST/api/v1/auth/registerRegister an account
Returns 201 with the user plus an accessToken / refreshToken pair.
| Field | Description | |
|---|---|---|
emailreq | string | Account email unique per deployment. |
passwordreq | string | 8–128 characters. |
firstName | string | Optional, 1–100 characters. |
lastName | string | Optional, 1–100 characters. |
phone | string | Optional, up to 20 characters. |
organizationName | string | Optional. Defaults to “<First Last>’s Events” so every account can organize without an onboarding step. |
POST/api/v1/auth/loginLog in returns a JWT
Returns an accessToken / refreshToken pair. If the account has 2FA enabled it instead returns { requires2fa: true, challengeToken, email } complete the login with POST /auth/2fa-verify.
| Field | Description | |
|---|---|---|
emailreq | string | Account email. |
passwordreq | string | Account password. |
POST/api/v1/auth/2fa-verifyComplete a 2FA login challenge
Step 2 of a 2FA login. Returns the same accessToken / refreshToken pair as /auth/login.
| Field | Description | |
|---|---|---|
challengeTokenreq | string | The short-lived challenge token returned by /auth/login when 2FA is enabled. |
codereq | string | The 6-digit TOTP code from the authenticator app, or a one-time recovery code. |
POST/api/v1/auth/token/requestEmail a buyer a 6-digit login code
| Field | Description | |
|---|---|---|
emailreq | string | Where the 6-digit code is sent. |
name | string | Used to create the account if the email is new buyers get an account on first contact. |
POST/api/v1/auth/token/exchangeExchange email + code for a JWT
Returns the same JWT pair as /auth/login no password ever exists.
| Field | Description | |
|---|---|---|
emailreq | string | The email the code was sent to. |
codereq | string | The 6-digit code from the email. |
POST/api/v1/auth/refreshRefresh an expired access token
Mints a fresh accessToken (and a rotated refreshToken) when the access token expires no re-login needed.
| Field | Description | |
|---|---|---|
refreshTokenreq | string | The long-lived refresh token from a prior login/register. |
POST/api/v1/auth/logoutLog out
Revokes the refresh token so it can no longer mint new access tokens.
| Field | Description | |
|---|---|---|
refreshToken | string | The refresh token to invalidate. |
Events public
GET/api/v1/eventsList and search public events
Returns published, public events as discovery cards with the lowest available ticket price.
| Field | Description | |
|---|---|---|
q | string · query | Free-text search across title and short description. |
category | string · query | Category slug, matched exactly. Open set any organizer slug, e.g. music, conference, workshop, sports, tech, digital-product, online-course, webinar. |
city | string · query | Case-insensitive contains match on venue city. |
country | string · query | ISO 3166-1 alpha-2, e.g. NG, US. |
venue | string · query | Case-insensitive contains match on venue name. |
date_from | datetime · query | ISO 8601. Without date filters, only upcoming events are returned. |
date_to | datetime · query | ISO 8601 upper bound on start date. |
sort | enum · query | date (default). popularity and relevance are accepted but currently fall back to date ordering. |
cursor | string · query | Opaque cursor from the previous page. |
limit | int · query | 1–100, default 20. |
GET/api/v1/events/{slug}Event detail
Includes organizer info, schedule and active ticket types. Private events 404 to anonymous callers but stay visible to their own organization authenticate with an organizer JWT or the org’s API key.
| Field | Description | |
|---|---|---|
slugreq | string · path | Event slug from a list response. |
Events organizer
POST/api/v1/organizer/eventsCreate a draft
Required: title, category, startDate, endDate, timezone, venueType and capacity every other field is optional. The event is created in draft status; add ticket types and publish it separately.
| Field | Description | |
|---|---|---|
titlereq | string | 3–200 characters. |
categoryreq | string | Category slug. Free-form (any string is stored); the organizer picker offers music, concerts, festival, conference, workshop, sports, theater, comedy, business, arts, food, tech, community, wedding, wellness, education, webinar, online-course, digital-product, online-service, religion, fashion, film, gaming, kids and other. |
startDatereq | datetime | ISO 8601; must be in the future. |
endDatereq | datetime | ISO 8601; after startDate and within 5 years of it. |
timezonereq | string | IANA timezone, e.g. Africa/Lagos. |
venueTypereq | enum | physical, online or hybrid. |
capacityreq | int | Total capacity, 1–1,000,000 (larger values are clamped). Hidden for digital / online categories, which derive it from ticket stock. |
subcategory | string | Optional finer classification, up to 100 characters. |
shortDesc | string | Up to 280 characters shown on discovery cards. |
description | string | Long-form description, 50–50,000 characters. |
tags | string[] | Up to 10 free-text tags, each ≤ 50 characters. |
visibility | enum | public (default), unlisted or private. |
coverImage | uri | Cover image URL (≤ 2048 chars). |
coverVideo | uri | Cover video URL (≤ 2048 chars). |
gallery | uri[] | Up to 20 gallery image URLs. |
doorOpenTime | datetime | When doors / access open, on or before startDate. |
venueName | string | Venue name (physical / hybrid events). |
venueAddress | string | Street address. |
venueCity | string | City powers the city search filter. |
venueCountry | string | ISO 3166-1 alpha-2. |
venueLat | number | Latitude (-90…90) feeds the map and the nearby search. |
venueLng | number | Longitude (-180…180). |
onlineLink | uri | Stream / meeting link for online and hybrid events. |
currency | enum | USD (default), NGN or ZAR the event’s settlement currency. |
absorbFees | bool | When true the organizer absorbs the platform fee (deducted from payout); when false (default) the buyer pays it on top of the ticket price. |
branding | object | Free-form branding overrides (e.g. accent color, invite code). |
seoData | object | SEO overrides (meta title, description, social image). |
entities | array | Up to 80 lineup blocks. Each: kind (artist, dj, speaker, host, guest, sponsor, partner, team or feature), name, plus optional role, imageUrl, bio, setTime and link. |
PUT/api/v1/organizer/events/{id}Update an event
Partial update accepts every field from create, all optional; omitted fields keep their value. Date or venue changes to a published event notify ticket holders.
| Field | Description | |
|---|---|---|
idreq | string · path | Event id (UUID or numeric). |
POST/api/v1/organizer/events/{id}/publishPublish a draft
No body. Fails if required fields are missing or the event has no ticket types yet.
| Field | Description | |
|---|---|---|
idreq | string · path | Event id. |
DELETE/api/v1/organizer/events/{id}Cancel an event
Cancels the event. Drafts simply disappear; published events make issued tickets eligible for refunds.
| Field | Description | |
|---|---|---|
idreq | string · path | Event id. |
reason | string | Up to 2,000 characters quoted in the notification to ticket holders. |
Ticket types
POST/api/v1/organizer/events/{id}/ticketsCreate a ticket type
| Field | Description | |
|---|---|---|
idreq | string · path | The event to attach the type to. |
namereq | string | 1–200 characters, e.g. “General Admission”. |
typereq | enum | general, reserved, vip, early_bird, group, free, multi_day, season, at_door or upgrade. |
saleStartreq | datetime | ISO 8601 when sales open. |
saleEndreq | datetime | After saleStart, at or before event end. |
price | number | Unit price excluding fees, 0–1,000,000, default 0. type=free requires price=0. |
currency | string | ISO 4217 3-letter code (any), e.g. USD, NGN, ZAR. Defaults to the event currency. |
quantityTotal | int | -1 for unlimited (default); otherwise the finite stock. |
maxPerOrder | int | Purchase cap per order, 1–1000, default 10. |
maxPerCustomer | int | Purchase cap per buyer, 1–1000, default 10. |
transferable | bool | Default true holders can pass tickets on. |
refundable | bool | Default false. When true, refundDeadline is required. |
refundDeadline | datetime | Last moment a refund request is accepted; at or before event start. |
presaleCode | string | 3–50 characters gates purchase behind a code (PRESALE_CODE_REQUIRED otherwise). |
waitlistEnabled | bool | Opens the waitlist when this type sells out. |
description | string | Up to 2,000 characters, shown under the type. |
accessUrl | uri | Digital delivery link (download / join / enrolment) for online & digital-product tickets. Delivered to the buyer with their ticket never shown on the public page. ≤ 2048 chars, nullable. |
accessNote | string | Short note delivered alongside accessUrl (e.g. access code or instructions). ≤ 500 chars, nullable. |
sectionId | string | Reserved-seating section this type maps to, for events with a seating map. |
sortOrder | int | Display order among the event’s ticket types, default 0. |
GET/api/v1/events/{id}/ticketsList ticket types
Returns each type with live availability available is null when quantityTotal is -1 (unlimited).
| Field | Description | |
|---|---|---|
idreq | string · path | Event id. |
Orders & payments
POST/api/v1/ordersCreate an order guest checkout needs only name + email
Returns 201 with the order plus a per-order accessToken for guest reads. Free orders complete instantly tickets are issued at creation.
| Field | Description | |
|---|---|---|
Idempotency-Key | uuid · header | Any UUID makes retries safe (see Idempotency above). |
itemsreq | array | 1–20 line items, one per ticket type. |
items[].ticketTypeIdreq | string | The ticket type to buy. |
items[].quantityreq | int | 1–100, within the type’s purchase caps. |
items[].attendeeName | string | Per-ticket holder name when it differs from the buyer. |
items[].attendeeEmail | string | Per-ticket holder email. |
guestEmail | string | Guest checkout where tickets and the receipt go. Creates the account on first contact. |
guestName | string | Name on the order, up to 200 characters. |
promoCode | string | Applied before totals, up to 50 characters. |
presaleCode | string | Unlocks presale-gated ticket types. |
GET/api/v1/orders/{id}Get an order
| Field | Description | |
|---|---|---|
idreq | string · path | Order id. |
token | string · query | Per-order access token from create guest reads without a session. |
POST/api/v1/orders/{id}/payPay provider: nowpayments (crypto), paystack or flutterwave
nowpayments returns the generated crypto deposit details (payAddress, payAmount, payCurrency, network, payinExtraId); the redirect providers return an authorizationUrl to open. 409s: ALREADY_PAID, ORDER_NOT_PAYABLE, and NOTHING_TO_PAY for free orders.
| Field | Description | |
|---|---|---|
idreq | string · path | Order id. |
provider | enum | nowpayments (crypto, default), paystack or flutterwave. |
payCurrency | string | For nowpayments only the crypto coin ticker to generate a deposit for (e.g. btc, eth, sol, usdttrc20, usdcbsc). Defaults to btc. Call GET /payments/crypto/currencies for the live list. |
token | string | Guest access token when the order was created without a session. |
POST/api/v1/payments/verifyActively verify a payment no inbound webhook needed
Confirms the charge with the provider server-side and issues tickets. Idempotent and poll-safe call it after the buyer returns from checkout, or on an interval until status is completed.
| Field | Description | |
|---|---|---|
orderIdreq | string | The order to verify. |
token | string | Guest access token, if applicable. |
GET/api/v1/payments/{orderId}Read payment / order status
Returns the order status, total, currency and the list of payment attempts (provider, status, amount). Read-only use /payments/verify to actively confirm and issue tickets.
| Field | Description | |
|---|---|---|
orderIdreq | string · path | The order to read payment status for. |
token | string · query | Guest access token when the order has no session owner. |
GET/api/v1/payments/crypto/currenciesList supported crypto coins
Returns the live list of crypto coins NOWPayments can generate a deposit for (ticker, label, symbol) the source for the payCurrency value on /orders/{id}/pay. Cached ~10 minutes.
POST/api/v1/orders/{id}/cancelCancel an unpaid order
Pending / processing orders only releases held inventory. Paid orders go through the refund flow instead.
| Field | Description | |
|---|---|---|
idreq | string · path | Order id. |
token | string | Guest access token, if applicable. |
POST/api/v1/events/{eventId}/issueIssue tickets you sold elsewhere (developer-handled payment)
Fee: a 3% platform fee on the ticket face value is deducted from your org wallet for every NON-FREE (paid) ticket free tickets (price 0) incur NO fee and never touch the wallet. This is the reduced developer rate; paid orders bought through Zatabox checkout pay 5%. Flow: you collected the payment yourself, so we mint the tickets and a completed (externally-paid) order, then debit the 3% in the ticket currency fund the wallet first (Wallet → Fund). The whole issuance fails atomically with 402 INSUFFICIENT_FUNDS if the wallet can't cover the fee (nothing is minted). Honours the Idempotency-Key header so a retry never double-issues. Returns 201 with the order, the minted tickets and the exact fee charged.
| Field | Description | |
|---|---|---|
eventIdreq | string · path | The event's public id. The API key must belong to its organization. |
itemsreq | array | [{ ticketTypeId, quantity, attendeeName?, attendeeEmail? }] the ticket types and counts to issue. |
buyer | object | { email?, name? } the recipient. With an email we create a passwordless account so the tickets land in their wallet. |
reference | string | Your own payment/order id, stored and echoed back for reconciliation. |
sendEmail | boolean | Email the buyer their tickets. Defaults to true when an email is supplied. |
Check-in
POST/api/v1/checkin/scanValidate a QR, barcode or door code
Denials are 200s, not errors: status comes back success or denied_duplicate, denied_cancelled, denied_expired, denied_wrong_event, with a deniedReason field.
| Field | Description | |
|---|---|---|
qrDatareq | string | The rotating HMAC-signed QR payload or a typed 6-character door code through the same field. |
eventIdreq | string | The event being scanned UUID or numeric id. |
gateName | string | Which gate, for per-gate stats. |
deviceId | string | Scanning device identifier. |
method | enum | qr_scan (default), barcode, manual or nfc. |
geoLat / geoLng | number | Optional scan location. |
GET/api/v1/checkin/event/{id}/manifestHashed guest-list manifest for offline scanning
Ticket hashes + statuses a gate device caches locally, so it keeps admitting with zero connectivity.
| Field | Description | |
|---|---|---|
idreq | string · path | Event id. |
since | datetime · query | ISO 8601 returns only tickets changed since then, for delta sync. |
POST/api/v1/checkin/batchSync queued offline scans
| Field | Description | |
|---|---|---|
eventIdreq | string | The event the scans belong to. |
scansreq | array | Up to 500 queued offline scans. |
scans[].qrData | string | One of qrData, ticketCode or shortCode is required per scan. |
scans[].ticketCode | string | Full ticket code, 6–50 characters. |
scans[].shortCode | string | The 6-character door code. |
scans[].scannedAtreq | datetime | Capture time with offset each scan is re-validated against it. |
scans[].gateName | string | Gate at capture time. |
scans[].deviceId | string | Device that captured the scan. |
GET/api/v1/checkin/event/{id}/statsCheck-in totals
Totals, capacity %, entry rate and per-gate breakdown. Per-gate slice: GET /checkin/event/{id}/gate/{gate}.
| Field | Description | |
|---|---|---|
idreq | string · path | Event id. |
SSE/api/v1/checkin/event/{id}/liveLive check-in stream (SSE)
Server-Sent Events a stats snapshot every 2 seconds, keep-alive comments every 30. Point an EventSource at it.
| Field | Description | |
|---|---|---|
idreq | string · path | Event id. |
Community
POST/api/v1/community/reviewsReview an event checked-in ticket holders only
Only checked-in tickets can review the pair ticketCode + email is the proof, no login needed.
| Field | Description | |
|---|---|---|
ticketCodereq | string | The code on the ticket proves attendance passwordlessly. |
emailreq | string | Must match the ticket holder’s email. |
ratingreq | int | 1–5 stars. |
bodyreq | string | 10–2,000 characters. |
authorName | string | Display name shown next to the review, up to 120 characters. |
POST/api/v1/community/orgs/{orgId}/followFollow an organizer
Re-subscribes a prior opt-out; every announcement email carries a one-click unsubscribe link.
| Field | Description | |
|---|---|---|
orgIdreq | string · path | Organization UUID, numeric id or slug. |
emailreq | string | Where new-event announcements go. |
name | string | Subscriber name, up to 120 characters. |
POST/api/v1/community/events/{eventId}/waitlistJoin a waitlist offers fire on cancellations
No payment at join time. When inventory frees up, the next entries get a time-limited purchase link.
| Field | Description | |
|---|---|---|
eventIdreq | string · path | Event UUID, numeric id or slug. |
emailreq | string | Where the offer email goes. |
namereq | string | 1–120 characters. |
ticketTypeId | string | Wait for a specific ticket type instead of any. |
Growth organizer
POST/api/v1/organizer/growth/events/{eventId}/compsBulk-mint and email comp tickets
| Field | Description | |
|---|---|---|
eventIdreq | string · path | Event UUID or numeric id. |
ticketTypeIdreq | string | The type to mint from comps draw down its remaining quantity. |
recipientsreq | array | 1–200 entries; each gets a real ticket by email at no charge. |
recipients[].emailreq | string | Recipient email. |
recipients[].name | string | Recipient name. |
note | string | Internal note on the batch, e.g. “press list”. Up to 500 characters. |
POST/api/v1/organizer/growth/events/{eventId}/comps/import-csvImport attendees from CSV
Returns imported and skipped counts rows with invalid emails are skipped, not fatal.
| Field | Description | |
|---|---|---|
eventIdreq | string · path | Event UUID or numeric id. |
ticketTypeIdreq | string | The type each imported attendee receives. |
csvreq | string | Raw CSV text header row must contain name and email columns (any order), up to 500 data rows. |
POST/api/v1/organizer/growth/events/{eventId}/broadcastEmail a broadcast with reply threads
Sends real email to every matching attendee; replies thread back to the organizer inbox.
| Field | Description | |
|---|---|---|
eventIdreq | string · path | Event UUID or numeric id. |
subjectreq | string | Up to 200 characters. |
bodyreq | string | Plain text or simple HTML, up to 5,000 characters. |
tagFilter | string | Only send to attendees whose ticket carries this tag. |
POST/api/v1/organizer/growth/tagsTag attendees
Additive existing tags stay. Tags power broadcast tagFilter and CRM segments. DELETE the same path removes one.
| Field | Description | |
|---|---|---|
orgIdreq | string | Organization UUID or numeric id. |
ticketIdsreq | array | 1–500 ticket ids (from the attendee list). |
tagreq | string | Short lowercase label up to 60 characters, e.g. “vip”, “press”. |
Buyers
/api/v1/users/meProfileGET/api/v1/users/me/ticketsTicket wallet
| Field | Description | |
|---|---|---|
cursor | string · query | Opaque cursor from the previous page. |
limit | int · query | Page size. |
GET/api/v1/users/me/exportGDPR data export
No parameters. Returns one JSON download of everything on the account: profile, orders + items, tickets, refund requests, reports and message threads.
POST/api/v1/users/me/refundsRequest a refund
Eligibility = the ticket type’s refundable flag plus its deadline; the organizer approves or denies. 409 when not eligible.
| Field | Description | |
|---|---|---|
ticketIdreq | string | The ticket to refund. |
reasonreq | string | 10–2,000 characters specific reasons fare better with organizers. |
message | string | Optional message to the organizer, up to 2,000 characters. |
evidenceUrls | array | Up to 5 supporting URLs. |
POST/api/v1/users/me/reportsFile a report
harassment and fraud route straight to platform admins; the rest go to the organizer first.
| Field | Description | |
|---|---|---|
categoryreq | enum | misleading_info, did_not_happen, harassment, fraud, accessibility or other. |
descriptionreq | string | 20–5,000 characters. |
eventId | string | One of eventId / organizationId is required. |
organizationId | string | Report an organizer rather than a single event. |
evidenceUrls | array | Up to 10 supporting URLs. |
POST/api/v1/users/me/tickets/{ticketId}/messageMessage an organizer about a ticket
Rate-limited to 10 messages per hour per organizer. Replies land in GET /users/me/messages.
| Field | Description | |
|---|---|---|
ticketIdreq | string · path | The ticket whose organizer you’re messaging. |
bodyreq | string | 1–5,000 characters. |
attachmentUrls | array | Up to 5 attachment URLs. |
API keys
POST/api/v1/organizer/integrations/org/{orgId}/api-keysCreate an API key
The response returns the full plaintext secret EXACTLY ONCE only its prefix is stored afterwards. Send it as a Bearer token (Authorization: Bearer vt_live_…). Requires an organizer JWT (owner / admin).
| Field | Description | |
|---|---|---|
orgIdreq | string · path | The organization the key belongs to. |
namereq | string | Label for the key, 2–120 characters. |
environment | enum | live (default) → vt_live_ prefix, or test → vt_test_. A vt_test_ key is rejected on a live deployment and vice-versa. |
scopes | string[] | Up to 40 scope strings, e.g. events:write, orders:read, payouts:read, webhooks:manage. Omit for a full-access key. |
ipAllowlist | string[] | Up to 40 IPs/CIDRs; when set, requests from other addresses are rejected. |
expiresAt | datetime | Optional ISO 8601 expiry; null / omitted never expires. |
GET/api/v1/organizer/integrations/org/{orgId}/api-keysList API keys
Returns each key’s prefix, masked display, scopes, environment, status, last-used and expiry never the secret.
| Field | Description | |
|---|---|---|
orgIdreq | string · path | The organization to list keys for. |
PUT/api/v1/organizer/integrations/org/{orgId}/api-keys/{keyId}Update a key (rename, pause, re-scope)
| Field | Description | |
|---|---|---|
orgIdreq | string · path | The organization. |
keyIdreq | string · path | The key to update. |
name | string | New label, 2–120 characters. |
status | enum | active, paused or revoked. |
scopes | string[] | Replaces the scope list. |
expiresAt | datetime | New expiry; null clears it. |
POST/api/v1/organizer/integrations/org/{orgId}/api-keys/{keyId}/rotateRotate a key’s secret
No body. Returns a new plaintext secret exactly once and invalidates the old one immediately.
| Field | Description | |
|---|---|---|
orgIdreq | string · path | The organization. |
keyIdreq | string · path | The key to rotate. |
DELETE/api/v1/organizer/integrations/org/{orgId}/api-keys/{keyId}Revoke a key
Revokes the key it stops authenticating immediately.
| Field | Description | |
|---|---|---|
orgIdreq | string · path | The organization. |
keyIdreq | string · path | The key to revoke. |
Webhooks
POST/api/v1/webhooksCreate an endpoint
The response contains the full whsec_ signing secret exactly once it is masked on every later read. Store it immediately.
| Field | Description | |
|---|---|---|
urlreq | uri | HTTPS endpoint that receives deliveries private-network targets are rejected. |
eventsreq | array | 1–64 event types from the catalog, or ["*"] for everything. |
name | string | Friendly label, up to 120 characters. |
orgId | string | Required with a user JWT (query or body); an API key implies its own org. |
GET/api/v1/webhooksList endpoints
| Field | Description | |
|---|---|---|
orgId | string · query | Required with a user JWT; implied by an API key. |
PUT/api/v1/webhooks/{id}Update an endpoint
| Field | Description | |
|---|---|---|
idreq | string · path | Webhook endpoint id. |
url | uri | New delivery URL. |
events | array | Replaces the subscribed event list. |
name | string | New label; null clears it. |
status | enum | active or disabled pause without deleting. |
DELETE/api/v1/webhooks/{id}Delete an endpoint
Deliveries stop immediately and the signing secret is invalidated recreating issues a new one.
| Field | Description | |
|---|---|---|
idreq | string · path | Webhook endpoint id. |
POST/api/v1/webhooks/{id}/testSend a test event
No body fires a signed test event at the URL so you can verify your handler end to end.
| Field | Description | |
|---|---|---|
idreq | string · path | Webhook endpoint id. |
POST/api/v1/webhooks/{id}/rotate-secretRotate the signing secret
No body. Issues a new whsec_ signing secret and returns it exactly once (the old one stops verifying immediately). Store it right away it is masked on every later read.
| Field | Description | |
|---|---|---|
idreq | string · path | Webhook endpoint id. |
GET/api/v1/webhooks/{id}/deliveriesDelivery history
Each delivery shows event type, response status, latency, retry count and the error detail on failures.
| Field | Description | |
|---|---|---|
idreq | string · path | Webhook endpoint id. |
cursor | string · query | Opaque cursor from the previous page. |
limit | int · query | Page size. |
POST/api/v1/webhooks/deliveries/{id}/replayReplay a delivery
| Field | Description | |
|---|---|---|
idreq | string · path | Delivery id from the delivery history. |
GET/api/v1/webhooks/catalogAll 27 event types
No auth, no parameters every event type the platform can emit, for building subscription UIs.
Worked examples#
Create an order
An order holds one or more ticket types and starts life pending. Pay it with POST /api/v1/orders/{id}/pay, then confirm with POST /api/v1/payments/verify verification is an active call, so no inbound webhook is required to complete a purchase. Free orders complete instantly. A successful create returns 201 plus a per-order access token for guest reads.
curl https://api.zatabox.com/api/v1/orders \ -H "Authorization: Bearer vt_test_…" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "items": [{ "ticketTypeId": "tkt_8f2k", "quantity": 2 }], "guestName": "Alice Johnson" }'{ "data": { "id": "ord_31xq", "orderNumber": "ORD-001", "status": "pending", "total": "42.00", "currency": "USD", "items": [ { "ticketTypeId": "tkt_8f2k", "quantity": 2 } ] }}| Field | Description | |
|---|---|---|
itemsreq | array | Line items one entry per ticket type. |
items[].ticketTypeIdreq | string | The ticket type to buy. |
items[].quantityreq | int | How many of that type. |
guestEmail | string | Where tickets and the receipt are sent. |
guestName | string | Name on the order. |
promoCode | string | Optional promo code, applied before totals. |
Scan a ticket at the gate
qrData carries whatever the gate captured the rotating signed QR payload, or a typed 6-character door code through the same field. The call answers 200 with a status of success, denied_duplicate, denied_wrong_event, denied_cancelled or denied_expired a denied scan is data, not an error. Full field reference under Endpoints → Check-in.
curl https://api.zatabox.com/api/v1/checkin/scan \ -H "Authorization: Bearer vt_test_…" \ -d '{ "qrData": "VTA-9F2K….1781119200.a1b2c3d4e5f6a7b8", "eventId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9", "gateName": "Gate A" }'# qrData is the rotating QR value; a 6-char door code works too.# Manual entry of a typed code uses POST /checkin/event/{id}/manual.{ "data": { "status": "success", "ticketCode": "VTA-9F2K…", "holderName": "A. Bello", "idCheck": false, "checkedInAt": "2026-06-10T18:42:09Z" }}