Search the academy
Search articles by title or content
Browse for event organizers
Sell tickets with the API
The full lifecycle from your own code — create a published event with one POST, check live availability, record a sale with an Idempotency-Key, then fetch and check in the tickets.
Everything below runs against https://api.zunaro.ai/v1 with a key that has the write scope — see Get started with the Zunaro API first. The requests and responses shown are from a real end-to-end run.
Create an event
One POST /v1/events creates the venue, event, performances, and ticket types together, published and purchasable immediately (send "publish": false for a draft):
curl -X POST https://api.zunaro.ai/v1/events \
-H "Authorization: Bearer zun_live_..." \
-H "Content-Type: application/json" \
-d '{
"title": "API Demo Night",
"venue": { "name": "API Demo Hall" },
"performances": [ { "starts_at": "2026-08-05T19:30:00Z", "capacity": 40 } ],
"ticket_types": [
{ "name": "General", "price_minor": 2500 },
{ "name": "VIP", "price_minor": 6000 }
]
}'
venueis either aname(found or created), an existing venueid, or the string"online".price_minoris minor units: 2500 is $25.00, and 0 makes the ticket free. A ticket type can also carry aquantitysale cap.ends_atdefaults to three hours afterstarts_at;category_name(found or created) defaults to "General";timezonedefaults to your organizer settings.- A duplicate title answers 409.
The 201 response is the canonical event shape. Keep the ids — each entry in ticket_types carries its prices per performance:
{
"id": "84ed3e51-3c35-4163-8c2f-c8793529afb8",
"object": "event",
"title": "API Demo Night",
"timezone": "America/New_York",
"ticket_types": [
{
"id": "4dc8ec44-5fe5-4940-9047-77e1e9ee80d0",
"object": "ticket_type",
"name": "General",
"prices": [
{
"performance_id": "a01e6c36-13ad-467d-831e-85d73a013608",
"price_minor": 2500,
"limited_quantity": null,
"is_active": true
}
]
}
]
}
List performances with GET /v1/events/84ed3e51-.../performances, and check what's left to sell — the same math storefront buyers see (capacity minus sold minus active cart holds):
curl "https://api.zunaro.ai/v1/performances/a01e6c36-.../availability" \
-H "Authorization: Bearer zun_live_..."
Place an order
POST /v1/orders records an offline sale: money never moves through the API. "tender": "cash" records payment received in full; "tender": "pay_later" creates an unpaid order to settle later; "tender": "free" places a zero-total order (free tickets) with no payment recorded — the API rejects it with a 400 if the priced total isn't exactly 0. Always send an Idempotency-Key so a retry can't sell the seats twice:
curl -X POST https://api.zunaro.ai/v1/orders \
-H "Authorization: Bearer zun_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sale-2026-07-06-001" \
-d '{
"items": [
{ "performance_id": "a01e6c36-...", "ticket_type_id": "4dc8ec44-...", "quantity": 2 },
{ "performance_id": "a01e6c36-...", "ticket_type_id": "addf79e1-...", "quantity": 1 }
],
"buyer": { "first_name": "Jane", "last_name": "Doe", "email": "[email protected]" },
"tender": "pay_later"
}'
The 201 response includes the tickets. Note the total: 2 × $25 + 1 × $60 came to "total_minor": 14887 because this store adds service fees ("service_fees_minor": 3887) — surcharges and taxes are itemized the same way:
{
"id": "84a40659-a5b7-4eae-9e8a-03be476fd58f",
"object": "order",
"order_no": "ZNR-8NZFPRZ8",
"status": "unpaid",
"channel": "box_office",
"currency": "USD",
"total_minor": 14887
}
A fresh order also emails the buyer a receipt and their e-tickets. Retrying the same request with the same Idempotency-Key replays the stored response (whatever its status — even a 500) with an idempotent-replayed: true header instead of re-executing; the same key with a different payload answers 409. Keys are kept for 24 hours; a genuinely new attempt needs a new key.
Fetch and check in tickets
Read tickets any time with GET /v1/orders/84a40659-...?expand[]=items or GET /v1/tickets?order_id=84a40659-.... At the door, admit by ticket id:
curl -X POST https://api.zunaro.ai/v1/tickets/3d6331a9-.../check_in \
-H "Authorization: Bearer zun_live_..."
Success answers 200 with "status": "checked_in" and a checked_in_at timestamp; a repeat answers 409 ("Ticket is already checked in.") — the same atomic guards as the gate scanner.