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 }
    ]
  }'
  • venue is either a name (found or created), an existing venue id, or the string "online".
  • price_minor is minor units: 2500 is $25.00, and 0 makes the ticket free. A ticket type can also carry a quantity sale cap.
  • ends_at defaults to three hours after starts_at; category_name (found or created) defaults to "General"; timezone defaults 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.