M Logo
Michael Lynn
Claude Triage API

Claude Triage API

A reference customer-support triage service on the Claude API — eleven labs, a fake outdoor retailer where you can file a ticket and watch it get classified live, and an open-source version you can point at your own helpdesk.

By Michael Lynn8/29/2026
Live DemoView on GitHub
Share:

Claude Triage API

Before anything else, go do this: twenty support tickets, unsorted, the way an agent sees them at 9am. One of them needed a human that day. See how long it takes you to find it.
That page is the whole argument. Everything below is how I built around it.
Most Claude API tutorials show you one thing at a time. Structured outputs in one repo. Tool use in another. Streaming in a third. Fine for a hello world. Not fine for production, where the same request might need a validated schema, a policy lookup, a streamed reply, and a cost estimate before anyone hits send.
I built the version I wanted when I was learning this early on. I kept finding the same gap: a useful API example would end just before the uncomfortable part. The output had to be reliable enough for code to consume. The tool had to be bounded. Somebody had to notice when a cost optimization quietly stopped working. And eventually someone had to decide whether the thing was safe enough to put in front of customers. Those are the decisions I wanted to practice in one place.

A note about Cursor and Claude

I work at Cursor. This is a personal educational project, and it is not affiliated with, sponsored by, or endorsed by Cursor. The views here are mine.
That is worth saying plainly because this project teaches the Claude API, and I do not want the fact that I work at Cursor to make the project seem more mysterious than it is. I picked Claude because I wanted to teach a backend service built around the Messages API. The triage service is a request/response application: a customer message comes in, the service classifies it, sometimes checks a few facts, and returns a recommendation or a draft. Claude is the API sitting inside that example.
Cursor belongs elsewhere in the picture. A model API is what I would reach for when I am adding an AI feature to an application I own. An agent runtime is useful when the job itself is to work in a development environment: inspect a repository, use a shell, work with MCP servers, make a change, and open a pull request. Those are different starting points. Sometimes they meet in the same system, but they are not substitutes wearing different names.
There is a companion architecture note and a sibling implementation that make this distinction concrete. I wrote them to hold the service, policy, schemas, and evaluation cases still while looking at how different primitives shape the implementation. It is not a benchmark, and it is not a vendor scorecard. I am much more interested in helping someone recognize which kind of tool they need before they start building than in producing a table that declares a winner.
If you are here for Cursor, I hope the useful part is not a sales pitch. It is the same habit the course tries to teach everywhere else: be specific about the job, make the boundaries visible, and measure the behavior that matters. If you are here for the Claude API, I hope the same distinction keeps the course honest. The API is a means to build this service, not a reason to stop thinking about the rest of the system.

Three things, one scenario

Everything hangs off of a fictional company called Northwind Outfitters. Imagine that they sell outdoor gear on a lifetime workmanship guarantee, which sounds nice until you hear they take 4,100 support tickets a week. And once they left a child's injury report sitting unrouted for three days because it opened with "probably nothing."
This closely mirrors a company, and a use case I encountered when I was executing Developer Days interviews back at MongoDB.
That incident in question was not fiction... but it shaped the way I looked at implementing support case triage and helped me crystalize how I wanted to create a learning lab. It is precisely why the triage schema was created with a calibrated confidence score. It is also why /v1/resolve returns the full tool trace. And it is why the eval set includes a deliberately ambiguous case that is allowed to fail.
WhatWhere
The servicesrc/ — runs locally, not deployed
The coursetriage.mlynn.dev — eleven labs, solutions, instructor guide, interactive playgrounds
The scenario, made realnorthwind.mlynn.dev — browse the catalog, file a ticket, watch your words get classified live
The storefront calls Claude for real. Rate-limited and spend-capped through MongoDB Atlas. Five requests per IP per ten minutes on the support form, separate windows for the other paid pages so the injection playground can't starve the thing that actually has to work, a global daily ceiling on top of both, and it fails closed rather than running uncapped.

Four routes

Each route builds on the one before it.
RouteCapabilityWhat it teaches
POST /v1/triageStructured outputsThe model's output contract is your type system
POST /v1/resolveTool useClaude queries your systems and shows its work
POST /v1/draftStreamingToken-by-token delivery over SSE, with real cost accounting
POST /v1/estimateToken countingKnow the bill before you pay it
Prompt caching on a ~1,400-word policy handbook runs through all of it. So does usage and cost accounting on every response, typed error handling, and an eval harness with both deterministic scoring and an LLM judge.
The structured output route is the one I keep coming back to. One Zod schema in src/schemas.ts is the model's output constraint, the runtime validator, and the TypeScript type your consumers get. No JSON.parse in a try/catch. No "respond only with JSON" in the prompt. No repair loop.

The course

It started at six labs. It's eleven now, and the split matters more than the count.
Day 1 is the course. Four hours, and someone who does only this has learned the Claude API and has a working service to show for it.
  1. Establish your evaluation baseline (20 min)
  2. Your first call, and reading usage (20 min)
  3. Structured outputs and schema design (35 min)
  4. Tool use and the agentic loop (45 min)
  5. Streaming and SSE (30 min)
  6. Prompt caching and cost (35 min)
  7. Eval design and LLM-as-judge (45 min)
Day 2 is three more hours and it's optional. It's for people who are going to ship, and it covers the decisions that only show up once something is real. Which model. What happens when the input is hostile. What separates a demo from a service. Skip it without loss if you're here to learn the API.
Then there's a capstone, Lab 10, and it's the odd one. It's a reading lab that works through Ask Northwind, the assistant already running on both public sites, instead of building another route. No key, no terminal. You can hand it to someone who doesn't write code.
There's also a Python track covering the four things that genuinely differ between the SDKs. I found those four by porting the service to FastAPI rather than by remembering them, which is why it's four and not twenty.
The site at triage.mlynn.dev has inline knowledge checks, seven interactive playgrounds, and an auto-scored assessment. The playgrounds render results from real runs, not illustrations of results. The markdown source lives in curriculum/ in the repo and syncs into the site. Edit the markdown on GitHub, not the generated docs.
If you're going to teach this rather than take it, the instructor guide has timing, the failure modes learners actually hit, and what to do when a lab goes sideways. There's a minute-by-minute run of show, and the opening two segments have a deck at triage.mlynn.dev/talk. Press P and the speaker notes open in a second window that stays in step with the slides, so a mirrored projector doesn't show the room your notes.
Start with the scenario page before Lab 1. Most of the questions the labs ask only make sense once you know why a refund cap exists or why the trace has to come back with the decision.

Quickstart

If you want to run the service locally:
bash code-highlightgit clone https://github.com/mrlynn/claude-triage-api.git
cd claude-triage-api
npm install
cp .env.example .env
# add your key from console.anthropic.com
npm run smoke
npm run smoke exercises all four routes in-process and prints the prompt-cache hit on the second call. It costs about $0.10. Full setup and troubleshooting are in the repo's curriculum/setup.md.

What I was trying to get right

A few details mattered more than the feature list.
Two identical-prefix calls to /v1/triage came back 81% cheaper on the warm call. But the cold call cost more than no caching at all, because of the write premium. Caching a one-shot prefix loses money. That is Lab 5, not a footnote.
Usage on /v1/resolve is summed across every turn. Report only the final message's usage and you under-report a five-turn loop by roughly 5×. The tool trace comes back with the decision because in support tooling, "show your work" is an audit requirement. Not a nice-to-have.
Three eval runs scored the tone judge at 3/4, 1/4, and 2/4 on the same four-case sample. Same route, same rubric, same model. CI gates on the deterministic half of the eval, not the judge. Lab 6 explains why.
The only case that flips between runs is the one labelled deliberately ambiguous. It scores 0.45–0.50 confidence both times, against ~0.84 on the cases that pass. A confidence field that behaves like that supports threshold routing. One that reports 0.9 on everything does not.

After the course

The service in src/ is Northwind's on purpose. Hardcoded taxonomy, one company's handbook, order data from a JSON fixture, and nothing writes back anywhere because there's no ticketing system to write back to. All four of those are right for a course. Parameterizing the taxonomy would have turned the structured-outputs lab into a lesson about config files. All four are wrong for production.
So there's a second repo. triage-api is the same technique with those four things moved behind a config seam: policy packs you copy and edit, a data provider interface, signed webhook ingest with connectors for Chatwoot, Zammad, Zendesk, GitHub Issues and a plain signed POST, a reviewer queue, and a conformance suite for writing your own adapter.
The guardrails are literally the same code. What's new is the stuff that only shows up once something has to run. My favorite is the one I got wrong first: if a deployment has no customer lookup, the rolling refund ceiling can't run at all. The old behavior was to quietly approve the refunds it couldn't check while the summary still read "0 violations." Now it escalates them and says so on /readyz. A guardrail that stops running without telling you is worse than not having one.
It ships with no sinks configured. It classifies, it stores, it recommends, and it writes nothing back until you turn that on deliberately.
If you're looking for enablement content for your product, I hope you'll find this refreshing and perhaps a source of inspiration. Feel free to reach out to discuss your specific enablement requirements and how we might work together.