Event Pass

Admission to an event as ticket stubs, with seats, the holder, each ticket's state and a scannable entry code from the provider.

Experience · Shares patterns with Flight Ticket, QR Code

≈ 1,434 tokens per use (tool 985 · guidance 317 · typical payload 132)How token costs are estimated

Example: Event Pass

Show my ticket for tonight's concert

Juniper Avenue: Late Light tour

· Pier 9 Music Hall

General admission

Entrance
Main doors
Doors
7:00 PM

Name: Maya Cohen

Entry rules: No re-entry · Bags under 30×20 cm

When to use

  • The user wants to see their ticket, badge or pass for an event and the agent has it from the ticketing provider: "show my ticket for tonight", "my conference badge", "tickets for the match".
  • They want to add it to their wallet, transfer a ticket or download it.

When not to use

  • The agent doesn't have the tickets from a tool result. Never invent seats or entry codes.
  • The user asks about the event itself: the agenda, dress code or RSVP. Use Event Details.
  • It's a flight. Use Flight Ticket.

How the agent should use it

  • Pass code.value exactly as the provider returns it. If the agent only knows the code lives in the provider's app, use format: "barcode-placeholder" with a label saying where.
  • Set ticketState for used, transferred and refunded tickets. Their codes are never shown.
  • Pass times with the venue's UTC offset and its IANA timeZone. Never convert them to the user's timezone.
  • Every action is a handoff. On add-to-wallet, transfer or download, open the provider's or your own flow for that ticketId. Never transfer a ticket without confirming.
  • Lifecycle: set expiresAt on the call to the event's end, so the pass turns inert in the Renderer once the event is over. When the provider reissues the tickets (a seat change, a new code), supersede the old call. fetchedAt isn't needed.

Layout

  • Header: the event title, then one quiet line with the date and time in the venue's timezone and the venue name. With several tickets, arrows on the right page between them ("1 of 3"); on touch screens, swipe. The first usable ticket shows first.
  • Pass fields, large: section, row, seat, gate, entrance and doors time, whichever the ticket has. Unseated tickets show their type ("General admission") above. Then the holder's name.
  • Perforated divider, then the entry code, with the entry rules in one line beneath it.

Entry codes

A qr code is drawn from the provider's payload with qrcode-generator, encoded as UTF-8 at error correction level M. It sits on a white tile with the standard four-module quiet zone, in dark mode too, because scanners need the contrast. It's an image labelled "Entry code for ticket 1". Tell users to turn up their screen brightness at the gate in your own copy if your audience needs it. The code is only drawn once the tool call has finished streaming, so it never changes under a scanner.

A barcode-placeholder shows a neutral placeholder with the agent's label ("Barcode in the Acme Tickets app"); the component never generates a code.

Codes are never shown for used, transferred or refunded tickets. The stub dims and the code area says why: "This ticket was used at 7:42 PM", "Transferred to Noa Levi", "This ticket was refunded". Those tickets have no actions.

Actions

Each usable ticket offers, in order: Add to wallet (when walletAvailable), Transfer (when transferable) and Download. They act on the ticket shown; the first two are buttons and a third goes into a "More actions" menu. Each returns { action, ticketId } once, locks its button for that ticket, and the footer confirms it quietly ("Opening the transfer for ticket 2…"); the other actions and tickets stay available.

Installation

npx shadcn@latest add https://your-site.com/r/event-pass.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { eventPassTool } from "@/components/genui/event-pass.schema"

const tools = {
  // No execute: the user's first action is the tool result.
  [eventPassTool.name]: tool({
    description: eventPassTool.description,
    inputSchema: eventPassTool.inputSchema,
  }),
}

Render the tool call and hand each action to your own flow:

components/message.tsx
import { EventPass } from "@/components/genui/event-pass"

function ToolPart({ part, addToolResult }) {
  if (part.type !== "tool-show_event_pass") return null

  return (
    <EventPass
      {...part.input}
      status={part.state === "input-streaming" ? "streaming" : undefined}
      result={part.state === "output-available" ? part.output : undefined}
      onResult={(result) => {
        if (result.action === "add-to-wallet") openWalletPass(result.ticketId)
        if (result.action === "download") downloadTicket(result.ticketId)
        if (part.state !== "output-available") {
          addToolResult({ tool: "show_event_pass", toolCallId: part.toolCallId, output: result })
        } else {
          sendMessage({ text: `Event pass action: ${JSON.stringify(result)}` })
        }
      }}
    />
  )
}

The agent receives one of:

{ action: "add-to-wallet", ticketId: "RFC-20931-07" }
{ action: "transfer", ticketId: "RFC-20931-08" }
{ action: "download", ticketId: "RFC-20931-07" }

With LangGraph

Push it from a graph node with ui.push({ name: "show_event_pass", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

When a conversation is reloaded, pass the stored result back as result, or an array when several actions were taken. Those actions show as done for their tickets and stay locked; they never call onResult again.

<EventPass
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
event{ title, start, end?, timeZone, venue: { name } }ISO 8601 times with the venue's offset, or local time. Data-bound
holder{ name }?Data-bound
ticketsTicket[]1–6, in the provider's order. Data-bound
tickets[].id, typestringtype like "General admission", "VIP", "Day pass"
tickets[].section, row, seat, gate, entrancestring?
tickets[].code{ format: "qr", value } | { format: "barcode-placeholder", label }?value is the provider's payload, never generated
tickets[].ticketState"valid" | "used" | "transferred" | "refunded"?Default valid. Only valid tickets show a code
tickets[].usedAtstring?ISO 8601, for "used at 7:42 PM"
tickets[].transferredTostring?Who a transferred ticket went to
doorsOpenstring?ISO 8601. Data-bound
rulesstring[]?Up to 4. Data-bound
transferable, walletAvailableboolean?Add Transfer and Add to wallet. Data-bound

Host props: status (streaming, ready, submitting, done), locale, onResult, result and className.

Source

On this page