← Blog

Claude API Customer Support Ticket Triage Guide

2026-11-07 · 6 min read · SubToAPI Team

Ticket triage is a classification problem: given a subject, body, and maybe a customer history, you need to assign a category, a priority, and a destination queue before a human ever reads it. The Claude API handles this well because it can reason about ambiguous language, handle multiple languages without separate models, and return structured JSON you can feed straight into your helpdesk's routing logic.

This article walks through a practical implementation: how to design the prompt, what fields to extract, how to handle confidence and escalation, and how to run it reliably in production without reprocessing the same ticket twice or blowing your latency budget.

Why LLM-based triage beats keyword rules

Most teams start with keyword matching or regex rules in their helpdesk ("if subject contains 'refund', route to billing"). This breaks down fast:

An LLM reads the ticket the way a human triager would, and with a tight prompt it's consistent enough to automate the first pass of routing while still letting humans override.

Designing the triage prompt

The key to reliable triage output is forcing structure. Ask Claude to return a fixed JSON schema and nothing else, and give it an explicit, closed set of categories and priorities — open-ended labels lead to inconsistent tagging over time.

const systemPrompt = `You are a support ticket triage assistant.
Classify the ticket and respond with ONLY valid JSON matching this schema:

{
  "category": "billing" | "bug" | "feature_request" | "account" | "how_to" | "other",
  "priority": "urgent" | "high" | "normal" | "low",
  "sentiment": "angry" | "frustrated" | "neutral" | "positive",
  "summary": "one sentence summary for the agent",
  "suggested_team": "billing" | "engineering" | "success" | "general",
  "confidence": 0.0-1.0
}

Priority rules:
- urgent: service outage, security issue, payment failure blocking usage
- high: angry customer, churn risk, broken core feature
- normal: standard questions, minor bugs, feature requests
- low: FYI messages, compliments, non-blocking questions`;

Then send the actual ticket as the user message:

const response = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 300,
    system: systemPrompt,
    messages: [
      {
        role: "user",
        content: `Subject: ${ticket.subject}\n\nBody: ${ticket.body}\n\nCustomer plan: ${ticket.plan}`
      }
    ]
  })
});

const data = await response.json();
const triage = JSON.parse(data.content[0].text);

Including metadata like the customer's plan tier or account age in the prompt lets Claude weigh priority more accurately — a paying Scale customer reporting an outage should probably jump the queue ahead of a free-trial user asking a how-to question.

Handling confidence and escalation

Don't treat every triage result as final. Build a simple threshold: if confidence is below, say, 0.6, or the category is other, route to a human-reviewed queue instead of auto-assigning. This keeps edge cases — sarcasm, multi-language tickets, highly technical bug reports — from being silently misrouted.

function routeTicket(triage, ticket) {
  if (triage.confidence < 0.6 || triage.category === "other") {
    return assignToQueue("manual-review", ticket);
  }
  if (triage.priority === "urgent") {
    return assignToQueue(triage.suggested_team, ticket, { notify: "slack-urgent" });
  }
  return assignToQueue(triage.suggested_team, ticket);
}

This pattern — automate the clear cases, flag the ambiguous ones — is what makes triage automation trustworthy enough for support leads to actually adopt it instead of fighting it.

Batching and cost control

If you're processing a backlog or a high ticket volume, don't call the API once per ticket synchronously in your request path. Queue tickets and process them asynchronously, and batch several tickets into a single call when they're independent and short, which reduces overhead per ticket. Keep max_tokens tight (triage output is small JSON, not prose) and cache the system prompt logic client-side rather than re-sending a verbose instruction block with every request.

If you're running this across a support team rather than a single script, you also need visibility into who's calling the API and how much it's costing per day. SubToAPI gives each team member their own sub_live_ key under one billing account, so you can see per-key usage in the dashboard and catch a runaway retry loop before it shows up on the invoice. Setup is a single call documented at /docs/quickstart.

Streaming for agent-facing summaries

Triage classification itself doesn't need streaming — it's a short JSON response. But if you extend the same pipeline to generate a longer draft reply or a detailed case summary for the agent, streaming improves perceived latency significantly. The request shape is the same Messages API call with "stream": true; see /docs/streaming for the event format if you add this later.

Using tool calls for routing actions

Once triage is reliable, the natural next step is letting Claude actually take the routing action — create the ticket in your helpdesk, assign the team, tag the sentiment — instead of just returning JSON for your code to interpret. Defining a route_ticket tool with the same schema above lets Claude call it directly, which removes a layer of JSON-parsing glue code. Tool definitions and call handling are covered in /docs/tools.

Putting it together

A minimal production triage pipeline looks like:

  1. New ticket arrives via webhook from your helpdesk.
  2. Call Claude with the triage system prompt and ticket content.
  3. Parse the JSON response; validate it against your schema.
  4. Route based on confidence and priority thresholds.
  5. Log the triage result alongside the ticket ID for later accuracy review.
  6. Periodically sample a few hundred auto-routed tickets and have a human check the category/priority match — this is your actual accuracy metric, not a vibe.

Start this on a Solo plan while you validate the approach on a single support inbox, then move to Team pricing per seat once other agents or scripts need their own keys — see /pricing for current tiers, or start a free trial at /signup.

FAQ

Can Claude handle tickets in multiple languages without a separate translation step? Yes. Claude reads and classifies non-English tickets directly — you don't need to translate first. Just make sure your prompt's category and priority labels stay in English (or whatever language your team's queues use) so routing stays consistent regardless of the ticket's input language.

How do I prevent the model from inventing categories not in my schema? Use a strict enum in your system prompt (as shown above) and validate the parsed JSON against that enum in code. If a response doesn't match, route it to manual review rather than trying to coerce it — this keeps bad classifications from silently entering your queue.

Is this accurate enough to fully automate routing with no human review? Not out of the box. Treat auto-routing as a first pass: confident, low-ambiguity tickets can be routed automatically, but anything below your confidence threshold, multi-issue tickets, or anything tagged other should land in a manual-review queue until you've validated accuracy on your own ticket data over several weeks.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →