TanStack
Tutorials

Evaluate a ticket

You have support tickets and you need to route them in code. This tutorial builds a server route that returns a queue, an urgency, and a refund flag.

1. Install

shell
npm i @tanstack/ai @tanstack/ai-typesafe

2. Set your key

Get a key from TypeSafe. Then set it in the environment:

shell
TYPESAFE_API_KEY=your-typesafe-api-key

Keep the key on the server. Do not send it to the browser.

3. Call decide() on the server

Add a POST handler. Create the adapter once. Then ask three questions about the ticket.

ts
import { decide, choice, score, boolean } from '@tanstack/ai'
import { typesafeDecider } from '@tanstack/ai-typesafe'

type Ticket = {
  subject: string
  body: string
}

const ADAPTER = typesafeDecider('jev-latest')

export async function POST(request: Request) {
  const body = (await request.json()) as { ticket: Ticket }
  const ticket = body.ticket

  const result = await decide({
    adapter: ADAPTER,
    state: ticket,
    questions: {
      queue: choice({
        instructions: 'Which team should handle this ticket?',
        options: {
          billing: 'Payments, invoices, refunds',
          tech: 'Bugs, outages, integrations',
          sales: 'Pricing, upgrades, new accounts',
        },
      }),
      urgency: score({
        instructions: 'How urgent is this ticket?',
        levels: ['low', 'medium', 'high'],
      }),
      refund: boolean({
        instructions: 'Is the customer asking for a refund?',
      }),
    },
  })

  return Response.json(result)
}

In TanStack Start, put this in src/routes/api.evaluate.ts. Start maps that file to /api/evaluate.

4. Read the answers

Call the route. Then branch on the typed fields.

ts
type Ticket = {
  subject: string
  body: string
}

async function evaluateTicket(ticket: Ticket) {
  const res = await fetch('/api/evaluate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ticket }),
  })
  return res.json() as Promise<{
    queue: { value: string; probability: number; confidence: number }
    urgency: { value: string; probability: number; score: number }
    refund: { value: boolean; probability: number }
  }>
}

const result = await evaluateTicket({
  subject: 'Charged twice for the same invoice',
  body: 'Please refund the extra payment.',
})

if (result.refund.value) {
  console.log('refund path', result.queue.value, result.urgency.value)
} else {
  console.log('route to', result.queue.value, result.urgency.value)
}

You now have a queue, an urgency, and a refund flag. Try a POST with that ticket.

5. Swap the adapter

The decide() call stays the same. Only the adapter changes.

  • OpenRouter: openRouterDecider('~typesafe/jev-latest') and OPENROUTER_API_KEY
  • Vercel AI Gateway: vercelGatewayDecider('typesafe-ai/jev') and AI_GATEWAY_API_KEY
  • Cloudflare: cloudflareDecider('typesafe/jev') and a Worker binding, or CLOUDFLARE_ACCOUNT_ID plus CLOUDFLARE_API_TOKEN

6. Try it

Run the app. Paste a ticket. Pick a provider. Click Submit.

The same app is on the Examples tab at /ai/latest/docs/framework/react/examples/evaluate.

The full example is on GitHub: TanStack/ai examples/react/evaluate.

See the Evaluate guide for abort, middleware, and the full result shape.