← Blog

How to Build a Claude-Powered Support Ticket Router

2026-11-04 · 5 min read · SubToAPI Team

How to Build a Claude-Powered Support Ticket Router

A Claude-powered support ticket router is a service that reads incoming tickets, classifies them by category and urgency, and routes them to the right team or agent — without a human touching the first step. You build it by sending ticket text to Claude with a structured prompt, parsing the model's JSON output, and mapping that output to your existing helpdesk routing logic.

The rest of this article walks through the architecture, the prompt design, the code, and the edge cases that actually matter in production: malformed output, cost control, and latency under load.

Why route tickets with an LLM instead of rules

Keyword-based routers break the moment a customer phrases a billing problem as "my card keeps failing" instead of "payment issue." Claude handles the paraphrasing problem natively because it reasons about intent, not just string matches. You get three practical wins:

The tradeoff is latency and cost per ticket, both of which are manageable if you design the prompt tightly and cap output length.

Architecture overview

A production router has four pieces:

  1. Ingestion — webhook from your helpdesk (Zendesk, Intercom, Freshdesk, or a custom form) fires on new ticket creation
  2. Classification call — ticket subject + body sent to Claude, returns structured category, priority, and a short summary
  3. Routing logic — your code maps the classification to a team, queue, or assignee
  4. Fallback — if the model call fails or returns malformed data, the ticket goes to a default queue instead of being lost

The classification step is the only part that touches Claude. Everything else is standard backend logic you likely already have.

Designing the classification prompt

The key to a reliable router is forcing structured, predictable output. Ask Claude to return JSON only, define the exact fields you need, and constrain the possible values for category and priority so your routing logic can switch on them deterministically.

System: You are a support ticket classifier. Return ONLY valid JSON,
no explanation, no markdown formatting.

Fields:
- category: one of ["billing", "technical", "account", "feature_request", "other"]
- priority: one of ["low", "medium", "high", "urgent"]
- summary: one sentence, max 20 words
- suggested_team: one of ["billing_team", "support_team", "engineering_team"]

Pass the ticket subject and body as the user message. Keep the prompt short — classification doesn't need chain-of-thought, it needs a fast, cheap, consistent answer.

Example implementation

Here's a minimal Node.js router using SubToAPI, which exposes Claude through a standard HTTPS API with an sub_live_... key — useful if you want ticket routing live behind your own endpoint without managing provider auth directly.

const CLASSIFY_PROMPT = `You are a support ticket classifier. Return ONLY valid JSON.
Fields: category (billing|technical|account|feature_request|other),
priority (low|medium|high|urgent),
summary (max 20 words),
suggested_team (billing_team|support_team|engineering_team)`;

async function classifyTicket(subject, body) {
  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-3-5-sonnet",
      max_tokens: 200,
      system: CLASSIFY_PROMPT,
      messages: [
        { role: "user", content: `Subject: ${subject}\n\nBody: ${body}` }
      ]
    })
  });

  const data = await response.json();
  const raw = data.content[0].text;

  try {
    return JSON.parse(raw);
  } catch {
    return { category: "other", priority: "medium", suggested_team: "support_team" };
  }
}

Wire classifyTicket into your helpdesk webhook handler, then route based on suggested_team and priority:

app.post("/webhook/new-ticket", async (req, res) => {
  const { subject, body, ticketId } = req.body;
  const classification = await classifyTicket(subject, body);

  await assignTicket(ticketId, classification.suggested_team, classification.priority);
  await logClassification(ticketId, classification);

  res.sendStatus(200);
});

This pattern works with any webhook-capable helpdesk. For the request/response shape and model options, see /docs/messages and the full setup walkthrough in /docs/quickstart.

Handling malformed output and failures

LLMs occasionally wrap JSON in markdown fences or add stray text despite instructions. Don't let that break your router:

If you expect high ticket volume, streaming isn't useful here — you want the full JSON object before acting, not a token-by-token UI. See /docs/streaming only if you're building a live triage dashboard alongside the router.

Cost and latency considerations

Classification prompts are short, so each call is cheap and fast — typically under a second with a capable model. At scale, batch non-urgent tickets and process them every few minutes instead of synchronously on every webhook if your helpdesk allows delayed assignment. For teams running this across multiple support queues or client accounts, SubToAPI's per-key usage metadata (see /docs) makes it straightforward to track classification cost per team or per customer without building your own usage logging.

Adding priority escalation

Once routing works, extend the same call to trigger escalation. If priority returns "urgent", fire a Slack alert or page the on-call engineer in addition to normal assignment. This requires zero extra Claude calls — you're just branching on the field you already classify.

Scaling beyond one Claude key

If multiple teams or client workspaces route tickets independently, give each one its own API key rather than sharing a single key across the whole routing service. This isolates usage, cost, and rate limits per team. SubToAPI supports Team (€19/seat) and Scale (€49/seat) plans built exactly for this — multiple keys, shared billing, per-seat usage visibility. Compare plans at /pricing or start a free trial at /signup.

questions

Does the router need a different Claude model for classification vs. summarization? No — a single fast model handles both if you keep the prompt tight and max_tokens low. Reserve larger models for tickets that need deeper reasoning, like detecting fraud patterns.

What happens if Claude returns an unexpected category value? Validate the response against your enum list before routing. If it doesn't match, fall back to a default team and log the raw response for prompt tuning.

Can this router handle multi-language tickets without translation? Yes. Claude classifies intent directly in the original language, so you don't need a separate translation step before classification.

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 →