Claude API Query Classification Routing System
A query classification routing system sits in front of your Claude API calls and decides, per request, which model, system prompt, or downstream handler should process it. Instead of sending every request through the same pipeline, you classify the query first — by intent, complexity, or task type — and then route it to the cheapest, fastest, or most accurate path for that category.
This matters because not every request needs the same treatment. A one-line FAQ lookup doesn't need the same model or context window as a multi-step reasoning task or a code generation request. Building a classification-and-routing layer lets you cut cost on simple traffic, reduce latency where it's not needed, and reserve your most capable (and expensive) model calls for queries that actually require them.
When you actually need this
If you're building a single-purpose chatbot with one system prompt and one model, you don't need this. You need it when:
- You serve multiple query types (support questions, code help, data lookups, creative writing) through one entry point
- You want to send cheap/simple queries to a faster model and complex ones to a stronger one
- You run different system prompts or tool sets depending on what the user is asking
- You need per-category usage and cost visibility across a product with many request shapes
Architecture overview
A practical setup has three stages:
- Classifier — a fast, cheap call that labels the incoming query
- Router — a lookup table mapping labels to a model, system prompt, and tool set
- Executor — the actual Claude call that produces the final response
request → classify() → route table → execute() → response
The classifier doesn't need to be large. Its only job is to output a category reliably, not to solve the task.
Step 1: Classify the query
The most reliable way to get a clean category out of Claude is to force structured output with tool use rather than parsing free text. Define a single tool that the model must call with a category field:
const classifierTool = {
name: "classify_query",
description: "Classify the user's query into one category",
input_schema: {
type: "object",
properties: {
category: {
type: "string",
enum: ["faq", "code", "reasoning", "creative", "data_lookup"]
},
confidence: { type: "number" }
},
required: ["category", "confidence"]
}
};
Send the query with tool_choice forced to that tool so the response is always structured, never a paragraph you have to regex. See /docs/tools for the full tool-use request format.
Keep this call short: no conversation history, no large system prompt, just the raw query and the classification tool. This keeps classification latency and token cost low regardless of how big the downstream answer will be.
Step 2: Build the route table
Once you have a category, map it to a concrete execution config:
const routes = {
faq: { model: "fast-tier", system: FAQ_PROMPT, tools: [] },
code: { model: "capable", system: CODE_PROMPT, tools: [codeExecTool] },
reasoning: { model: "capable", system: REASONING_PROMPT, tools: [] },
creative: { model: "fast-tier", system: CREATIVE_PROMPT, tools: [] },
data_lookup: { model: "capable", system: RAG_PROMPT, tools: [retrievalTool] }
};
The key design decision here is that routing isn't only about model choice. You can route to different system prompts and different tool sets while keeping the model constant, or vice versa. Most production systems route on all three dimensions at once.
Step 3: Add a confidence threshold and fallback
Classifiers are occasionally wrong or uncertain. Always define a default route for low-confidence results instead of trusting the category blindly:
function resolveRoute(classification) {
if (classification.confidence < 0.6) {
return routes.reasoning; // safest, most general-purpose path
}
return routes[classification.category] ?? routes.reasoning;
}
This avoids a bad classification silently sending a complex question to a route that lacks the right tools or context.
Step 4: Execute and log per-category
Once routed, the actual call to Claude goes through your normal request pipeline. If you're running this through SubToAPI, the execution step is a single call to /v1/messages with the system prompt and tools determined by the route:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet",
"system": "'"$ROUTE_SYSTEM_PROMPT"'",
"messages": [{"role": "user", "content": "'"$QUERY"'"}]
}'
See /docs/messages for the full request/response reference, and /docs/streaming if your routed response needs to stream back to the client.
The reason to log by category, not just by request, is that routing decisions should improve over time. If "faq" queries are consistently routed to "reasoning" because of low confidence, that's a signal your classifier prompt or category boundaries need tuning — not that your fallback is broken.
Cost and latency tradeoffs
Classification adds a network round trip before the real work starts. For latency-sensitive products, keep the classifier call as small as possible (short system prompt, no history, forced tool output) so it adds tens of milliseconds, not seconds. For cost, the savings usually come from routing a large share of simple traffic to a cheaper model tier — the classification call itself is cheap enough that it pays for itself quickly once your traffic mix has a meaningful share of low-complexity queries.
If you're already running multiple API keys per team or product surface, SubToAPI's usage metadata per key makes it straightforward to see which routes are consuming the most tokens, which is useful for deciding where to invest in better prompts or a faster model. Teams on the Team or Scale plans can give each routing category or service its own key under one dashboard — see /pricing for plan details or /signup to start a trial.
Keep the classifier simple
Resist the urge to build a classifier with dozens of fine-grained categories. Every additional category increases the chance of misclassification and the number of route configs you have to maintain. Start with four or five broad categories, measure where misroutes happen using logged confidence scores, and split categories only when you have evidence a single route is handling two genuinely different workloads.
Questions
Should classification use Claude itself or a separate cheaper method? Using Claude with a forced tool call is usually worth it because it handles ambiguous natural-language queries better than keyword rules, and the call is small and cheap compared to the downstream response. Rule-based pre-filters are worth adding only for high-volume, unambiguous patterns (e.g., exact command matches).
How do I keep classification latency low? Send only the query text with a minimal system prompt and a forced tool_choice, skip conversation history, and avoid giving the classifier any tools beyond the classification tool itself. This keeps the call small and fast regardless of how large the final answer will be.
Can I route to different system prompts instead of different models? Yes — routing doesn't have to change the model at all. Many systems keep one model and route purely on system prompt and tool set, which is simpler to maintain and still gives most of the accuracy and cost benefits of full routing.