Quick Start
Ask the router which model to use, then call it:- cURL
- TypeScript
- Python
/v1/router/multimodel hands Morph your model list and returns the one to call. /v1/router/classify returns the raw classifier labels and leaves the mapping to you.
/router/multimodel
Hand the router your candidate models (or whole providers) plus a policy. It classifies the prompt and returns the single best model to call, with no mapping table to maintain. Requestallowed_models and allowed_providers are unioned: a model qualifies if it matches either. Leaving both empty considers the whole catalog, which is the right way to explore the API and the wrong way to run it in production. Two or three candidates is the shape that holds up. See Best practices.
Model catalog
Policies
Selection scores every candidate on how far it sits from the request’s difficulty and ambiguity tier, whether it covers the domain, and what it costs. The policy sets the weights:
balanced and cost_efficient have domain weight 0, so they return the same model for every domain at a given difficulty and ambiguity.
Where each request lands
With the full catalog allowed under balanced:
GLM-5.2 owns the medium tier outright and takes hard work at low ambiguity. Opus keeps hard x med-ambiguity. High ambiguity always escalates to Fable 5, the strongest model for prompts whose intent is unclear.
Switch to
cost_efficient and five cells move: medium x low drops to deepseek-v4-pro, hard x med drops to glm-5.2, and the high-ambiguity column above easy goes to kimi-k3, which covers the same cells as Fable 5 at less than half the price. Restrict to allowed_providers: ["anthropic"] and you get Sonnet everywhere except hard x med (Opus) and high ambiguity (Fable 5).
- cURL
- Python
- TypeScript
model is what you call next. The classifier signals are echoed back so you can act on them too, e.g. show a “let’s clarify” prompt when difficulty is needs_info. ambiguity and domain are present only when those heads cleared their threshold; treat a missing field as “no signal.” If the prompt resolves to needs_info and you passed a default_model, that model is returned as-is.
Best practices
Routing saves money by moving requests off models they don’t need. It loses money when the switching itself costs more than the tier difference. Four rules keep it on the right side of that line.Route between two or three models, not ten
Every extra candidate is another prefix cache, another failure mode, and another surface to evaluate. Two tiers, cheap and strong, capture most of the available savings. A third earns its place only when it owns a cell the other two are genuinely bad at. Passing the whole catalog looks like more optimization and is usually less: it maximizes how often the model changes, which is the thing that costs you (see the next rule). In production, pin a set:Be cache-aware: a model switch is a full re-prefill
The router returns a model per call, but calling it per turn is usually wrong. Switching models mid-session invalidates the upstream KV prefix cache, so the next turn re-prefills the entire conversation from scratch at the full input rate. The size of that mistake: on Morph’s own models, cached input is 1.10/M uncached, an 80% discount you forfeit on every switch. A 60k-token agent session that “saves” money by moving from a 6/M model pays for 60k tokens of fresh prefill to do it, and can come out behind. Classify at session and task boundaries. Not every turn.Know your cache-breaking events
A switch is free when the cache was already cold, and expensive when it wasn’t. These are the moments that decide which:
Details on all of these: Prompt Caching.
Pin the model once the context is expensive
Past roughly 60k tokens, prefill dominates any per-token rate difference. Stop re-classifying and hold whatever the session is already on. Morph’s own Claude Code proxy ships this as a context lock: once a turn’s context passes the threshold it skips the classify call entirely and keeps the route, so the prefix cache keeps hitting. If your context is the problem, shrink it rather than re-route around it. Compact cuts 50-70% and is itself a clean boundary to re-route on.Labels
The classifier heads return these labels./v1/router/multimodel maps them for you; /v1/router/classify hands them over raw.
Difficulty
Ambiguity
Domain
/router/classify
Runs the requested classifier heads against your prompt and returns the raw labels. Use this when you already have a model mapping you trust and only want the signals. Request- cURL
- Python
- TypeScript
label, class_id, confidence, and meets_threshold (whether confidence cleared the head’s threshold). When difficulty does not meet its threshold, treat it as needs_info: the prompt is too ambiguous to size confidently.
Production example
Route once per session, then reuse the decision for every turn in it. This is the shape that actually saves money, because the model only changes when the cache was going to be cold anyway.- TypeScript
- Python
default_model already covers the needs_info case, not a transport failure.
Integrate with Claude Code
Route every Claude Code turn through the router with no change to how developers work. A local proxy sits between Claude Code and Anthropic (ANTHROPIC_BASE_URL points at it), classifies each turn, and picks the cheapest Claude model, and reasoning effort, that can handle it, following your org’s routing policy.
Requires macOS or Linux, Node 22+, and the claude CLI.
1. Install with your Morph API key:
~/.morph/ccr-router, and gives you a morph-claude command. Re-running the one-liner upgrades in place.
2. Authenticate upstream. By default the proxy uses your Claude Pro/Max subscription: run claude login once. To use your org’s Anthropic key instead, add it to the install:
morph-claude instead of claude:
{model, effort}, then clamped to the models that user is permitted. One decision per turn, 1.5s classify timeout; if the classify ever fails, routing fails open to your default model.
The proxy is cache-aware by default. Once a turn’s context passes the context lock threshold (60k tokens out of the box) it stops classifying and holds the session’s current model, so the upstream prefix cache keeps hitting instead of being thrown away for a cheaper per-token rate that no longer pays for itself.
Set the policy. Admins configure the routing matrix and per-user permissions in the dashboard under Administration → Model Router. Edits reach every developer within the hour, with no redeploy and no reinstall. The Analytics tab shows the model mix, turn volume, and estimated savings vs sending every turn to Opus. Prefer to own the policy? Point the proxy at a local router-matrix.json or an endpoint you host via MORPH_MATRIX_FILE / MORPH_MATRIX_URL.
Routing metrics are metadata-only. No prompt or completion text ever leaves the machine. Set
MORPH_METRICS_DISABLED=1 to send nothing at all.Edge / Cloudflare Workers
fetch is available natively at the edge, so you can call the router from a Cloudflare Worker, Vercel Edge Function, or Deno with no SDK:
The
@morphllm/morphsdk/edge build ships a RawRouter helper, but it targets the legacy /router/raw endpoint. For the current endpoints, call them directly with fetch as shown above.API Reference
Both endpoints arePOST https://api.morphllm.com/... with an Authorization: Bearer YOUR_API_KEY header.
- /router/multimodel
- /router/classify
413. An unknown classes value or a default_model outside the allow filter returns 400.
When to Use
Use the router when:- Processing varied user requests (simple typo fixes to complex architecture tasks)
- You want to minimize API costs without manually classifying prompts
- Building cost-conscious AI products with mixed complexity workloads
- All tasks need the same model tier (e.g., always Opus for agentic coding)
- The ~180ms routing latency matters more than cost savings
- You need deterministic model selection for testing or compliance
Performance
- Latency: ~180ms average, one call per routing decision
- Parallel: Can run in parallel with other work
- HTTP/2: Connection reuse for subsequent calls
Deprecated endpoints
/router/raw
Returns just a difficulty label. Use/v1/router/classify instead for new code.
- cURL
- Python
- TypeScript SDK
{ "difficulty": "easy", "confidence": 0.93 }balanced (default) balances cost and quality; aggressive optimizes harder for cost, pushing more prompts to easy. Returns difficulty (easy | medium | hard | needs_info).
For edge environments (Cloudflare Workers, Vercel Edge, Deno), use @morphllm/morphsdk/edge:
/router/
Returns a provider-specific model name directly instead of a difficulty label. Registered foropenai, anthropic, and gemini only; there is no /v1/router/zai, /v1/router/deepseek, or /v1/router/moonshot. Use /v1/router/multimodel with allowed_providers instead: it does the same model selection with control over the candidate set and policy.
Under the hood these now call the multimodel router constrained to that provider, so they keep working with no changes on your side.
{ "model": "claude-haiku-4-5-20251001", "confidence": 0.93 }
The SDK still exposes morph.routers.anthropic.selectModel(), morph.routers.openai.selectModel(), and morph.routers.gemini.selectModel() for backwards compatibility. Migrate to /v1/router/multimodel.
See Also
- Prompt Caching — what a model switch costs you, and how to keep hits
- Compact — shrink context instead of routing around it
- Enterprise Model Routing — org-wide policy for Claude Code
- Open Source Models — the models Morph serves directly