Sell tickets through AI agents

External AI assistants like Claude and ChatGPT can discover your events, show live availability, and hold tickets for buyers directly in chat through Zunaro's Model Context Protocol server — with nothing for you to build. Here's how it works, the tools it exposes, and when to choose it over the REST API.

A buyer asks ChatGPT "find me two seats at a jazz night in Brooklyn next Saturday" — and the assistant searches your events, shows the buyer a seat map, holds the seats, and hands back a checkout link. That flow runs through Zunaro's agentic commerce layer: a Model Context Protocol (MCP) server that lets an external AI assistant read your live inventory and act on a buyer's behalf, all inside the chat.

What this is (and what it isn't)

Agentic commerce means an AI assistant built by someone else — Anthropic's Claude, OpenAI's ChatGPT — can talk to Zunaro at runtime, discover your events, and book tickets for its user. You don't build the assistant. You don't host the model. The assistant calls Zunaro through a standard protocol, the same way it calls any other tool.

It's worth separating this from two things that sound similar:

  • Ask Zunaro is the in-app assistant inside your dashboard — it helps you, the organizer, manage events and look at sales. It is not buyer-facing, and an external chat assistant cannot reach it.
  • The REST API is for your own code, authenticated with an API key you create. The MCP server is for someone else's AI assistant, acting on behalf of a buyer.

The REST API answers "how do I sell tickets from my own code?" The MCP server answers "how does a buyer's AI sell my tickets?"

How it works

Zunaro runs an MCP server at /api/mcp that speaks the Model Context Protocol over Streamable HTTP. An MCP server is just a typed, live API that an AI client calls at runtime — it doesn't train on your data or "learn" anything. When a buyer's assistant needs to find events or hold seats, it calls the server's tools, and the server reads your live database and returns the result.

Three pieces fit together:

  1. Tools — typed actions the assistant can call (search_events, get_event, add_to_cart, and the rest below). Each tool has a precise input schema and returns structured output, so the model knows exactly what it can ask for.
  2. HTML widgets — self-contained snippets the server hands the assistant so it can render an interactive event list, a ticket picker, or a seat map inside the chat. The widgets are sandboxed and load nothing remote.
  3. The booking flow — the assistant holds tickets into an anonymous cart and returns a checkout link. The buyer pays on your normal web checkout.

The tools exposed

The server advertises a small, focused set of tools. Read-only tools are safe for any assistant to call; write tools mutate a cart.

| Tool | Type | What it does | | --- | --- | --- | | search_events | Read | Free-text search across upcoming events; returns a buyable URL, date, venue, and "from" price for each. | | get_event | Read | Full detail for one event — performances, the ticket-tier price ladder, per-order limits, refund policy, and an admission flag (general vs reserved). | | get_availability | Read | Advisory live "N left" / seat status for one or more performances. | | get_seatmap | Read | Opens the interactive seat map for a reserved-seating performance so the buyer can tap seats. | | add_to_cart | Write | Holds tickets — general-admission by quantity, or reserved seats by id — into an anonymous cart, and returns a checkout URL. | | get_cart | Read | The priced cart: line items, fees, taxes, discounts, the all-in total, and when the hold expires. | | remove_from_cart | Write | Drops one held ticket or seat from the cart by its item id. |

Each tool is annotated so the assistant knows which are safe to run freely (read-only) and which need a buyer's go-ahead (writes). A get_event response for a general-admission event tells the assistant to use quantity steppers; a reserved-seating event tells it to open the seat map instead.

A worked example

Here's what a real exchange looks like, at the tool level. A buyer tells their assistant they want two tickets to your show; the assistant drives the loop:

  1. search_events({ query: "jazz" }) returns matching events with a buyable URL, the venue, a "from" price, and an id.
  2. get_event({ eventId }) returns the full detail — performances, ticket tiers (each with its price), per-order min/max, and an admission flag of reserved.
  3. get_seatmap({ eventId, performanceId }) opens the seat map. The widget renders inside the chat; the buyer taps two seats.
  4. add_to_cart({ selections: [{ performanceId, ticketTypeId, quantity: 2, seatIds: [...] }] }) holds those seats and returns a checkoutUrl plus an internal cartToken.
  5. The assistant shows the buyer the checkout URL. The buyer opens it on your site and pays.

The assistant surfaces the checkout link as the next step — it doesn't quote a total of its own. That's deliberate: the "from" prices and seat-map prices the assistant sees are face prices that exclude fees. The number the buyer actually pays is the all-in total from get_cart, which adds your service fees and tax. Routing the buyer to your checkout guarantees the total they pay is the same one a web buyer sees.

What the buyer sees

Inside the chat, the buyer doesn't see raw tool calls — they see the widgets the server hands the assistant. An events search renders a row of event cards with your artwork, date, venue, and a "from" price. Opening an event shows a ticket picker with quantity steppers (general admission) or an interactive seat map (reserved seating) they can tap. Your brand color and logo follow the widgets, so the experience reads as your store, not as Zunaro or as the chat app.

When the buyer is ready to pay, they tap the checkout link and land on your web checkout — the same page a web buyer reaches, with the same Stripe or PayPal options, the same fee breakdown, and the same receipt and e-ticket emails afterward. The agent surface is the front door; your checkout is still the till.

Two surfaces, one server

The same /api/mcp endpoint serves two different views depending on the host it's reached on:

  • Per-store surface — reached on your store's subdomain. The assistant sees only your events and books against your store. The organizer is resolved from the host, so the agent never has to pass (or guess) which store it means. This is the surface an assistant uses when a buyer is already talking about your store.
  • Global surface — reached on the main Zunaro host, with no single organizer. search_events here spans every store's public events at once (backed by a search index), and each result carries the store it belongs to. The assistant then drills into one store for detail and booking. This is how a buyer's AI discovers you when they didn't name you — "jazz in Brooklyn next Saturday" returns your event alongside others, and the buyer never has to switch connectors.

A buyer adds the global connector once and can search and book across all stores. A buyer who reaches your store directly gets your per-store tools. Either way, the booking runs against your store's own inventory and your own checkout.

How a booking plays out

The full loop, end to end:

  1. The assistant calls search_events to find matching events, then get_event for detail and the ticket tiers.
  2. For general admission, the assistant calls add_to_cart with a quantity per ticket type. For reserved seating, it calls get_seatmap to show the buyer the seat map, the buyer taps seats, and the assistant calls add_to_cart with the chosen seat ids.
  3. add_to_cart returns a cartToken (internal — the assistant reuses it for further adds) and a checkoutUrl.
  4. The assistant shows the buyer the checkout URL. The buyer opens it, which resumes the held cart on your normal web checkout, where they enter their details and pay with Stripe or PayPal — exactly as a web buyer would.

The cart behaves like your storefront cart in every way that matters: holds expire after a short window (so seats return to the pool if the buyer abandons), access codes and promo codes apply, and auto-discounts like early-bird or group pricing recompute on every change. The buyer can keep adding to the same cart by reusing the token, or remove a held seat they changed their mind about.

What you need to do

For the buyer-facing surfaces described above: nothing. Your store's subdomain already serves the MCP endpoint, your published events are already discoverable, and the anonymous cart model is the same one your storefront uses. There is no API key to issue for buyers and no OAuth handshake for them to complete — a buyer's assistant simply connects to your store's /api/mcp.

The things that materially help discovery are the same things that help Google:

  • Publish your events. Only published, on-sale events are returned by search_events and get_event.
  • Set your branding. The widgets the assistant renders inside chat pick up your store's brand color and logo, so a booking in ChatGPT still looks like your store, not a generic platform.
  • Fill in venue and description. Free-text search and the global index lean on title, venue, and category — richer copy is more findable.

How it stays safe

Because the server is public and anonymous, it's defended the same way your storefront is:

  • Rate limits apply per IP and per store (and per cart token for writes), so a misbehaving agent can't hammer your inventory or lock up seats. Write calls in a single request are capped.
  • Holds expire. A held-but-unpaid seat returns to the pool after a short window, so an agent can't lock inventory indefinitely. The FOR UPDATE seat lock at the moment of add is the only true double-book guard — the same one your web checkout relies on.
  • No secrets leak. Tool errors return safe, buyer-facing messages; internal database detail never reaches the assistant. The cartToken and transaction number are internal — the assistant is instructed not to surface them to the buyer.
  • Amount integrity is server-side. An agent can't pass its own prices. The checkout total is computed from your cart, and your payment gateway refuses any session whose line items don't sum to the expected amount.

When to use this vs the REST API

Use the MCP server when you want external AI assistants to be able to sell your tickets to their users, in their chat, without you writing or hosting any integration. You don't control the assistant, and you don't take payment inside it — but you get discovered and booked inside surfaces you could never reach on your own.

Use the REST API when your own code needs to drive ticketing — syncing events to a CRM, recording box-office sales, embedding checkout in your own app, or building workflows the assistant surfaces don't cover. The API authenticates with a key you create in SettingsDevelopers and moves money as offline tenders; see Get started with the Zunaro API to create one.

The two are complements, not alternatives: the API is your hands on the keyboard, and the MCP server is the door you leave open for a buyer's AI to walk through.