Jev Router: When Routing Beats Pinning Jev 1.13
OpenRouter lists two TypeSafe entries that both carry the name Jev, and they are not the same product. One is the decision model this site has covered from the start — typesafe/jev-1.13, which answers typed questions with probabilities. The other is typesafe/jev-router, which does not answer your question at all. It decides who should answer it, then gets out of the way.
That distinction is worth an article because the two have opposite failure modes. Pin jev-1.13 and you get a typed, machine-checkable answer, but you also get a model that is deliberately bad at writing prose and open-ended reasoning. Call jev-router and you get a normal text response from whatever model the router selected, with the routing decision itself being made by Jev. Reach for the wrong one and you spend a week fighting a tool that was never meant for the job.
Everything below is drawn from OpenRouter’s own Jev Router documentation and from the OpenRouter models API, both read on 2026-11-03. Where a number appears, it is the number the API returned that day.
TL;DR:
typesafe/jev-routeris a drop-in model slug: no plugin needed, it works across Chat Completions, Responses and Messages, and the response’smodelfield tells you who answered. Add the optionaljev-routerplugin only when you need to passcost_tier(low,medium,high), include preferences (models/allowed_models) or hard exclusions (excluded_models). Includes are soft — they fall back when they leave no routable candidate; exclusions are hard. A cost tier is a routing preference, not a fixed model, a guaranteed score, or a per-request price cap. If you need a typed decision you can branch on, pinjev-1.13instead; the router is for ordinary generation with cost control.
Two products, one name
Start with what each one returns, because that determines everything downstream.
Jev 1.13 (typesafe/jev-1.13) | Jev Router (typesafe/jev-router) | |
|---|---|---|
| What comes back | A typed decision: chosen answer plus a probability | Ordinary text from the selected model |
| Who composes the answer | Jev itself | Whichever model the router picked |
| What you send | State plus a question phrased as Noul, Choice or Score | A normal completion / responses / messages request |
| Typed output shape | Decision JSON | Whatever the serving model emits |
| Best fit | Branching logic, gates, scoring, routing decisions inside your code | One endpoint across many models with cost policy and preferences |
| Where the model is named | In your config, pinned by you | In the response’s model field, decided per request |
The catalog data backs the split. When I read the models API on 2026-11-03, the typesafe/jev-1.13 entry reported output modality decisions and its endpoint listed a 262,144-token context with 235,929 maximum completion tokens. The typesafe/jev-router entry reported text output, a 1,000,000-token context, 65,536 maximum completion tokens, input across text, image, file, audio and video — and an empty endpoints array, because there is no single provider sitting behind it. Its pricing fields are -1 for both prompt and completion, which means no fixed per-token rate is published for the router itself: you are billed for whatever model it selects.
That last point deserves emphasis. The router is not a model with its own price list. It is a decision procedure wrapped around other people’s models, so your bill is the sum of the routed models’ charges, not a router rate. Budget against the models in your pool, not against a router number.
What the router actually decides
OpenRouter describes the mechanism in three steps.
Assess the request. Jev evaluates the conversation to identify what the task needs. This is the decision model doing the thing it was built for: judging the shape and difficulty of a request rather than producing output.
Select a model and effort. The router applies the chosen cost policy, the available models’ capabilities, and any restrictions you set on models or providers. Effort here refers to reasoning effort, which matters for models that expose it.
Adapt across turns. In supported agentic workflows the router can retain the model, adjust its effort, or switch models as the task progresses. When the policy permits it, an expert advisor can assist the model doing the work.
Two caveats come straight from the documentation and they prevent most bad expectations. First: the available models and routing policies can change over time. Second, and more important operationally: a cost tier is a routing preference, not a fixed model, guaranteed score, or per-request price cap. If you need a hard ceiling on spend, a router tier is not that mechanism — you want a spend limit on the account or a fixed model, not a preference.
Two request shapes
The simplest integration ignores the plugin entirely. Set the model slug and send your request:
curl https://openrouter.ai/api/v1/responses \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-router",
"input": "Explain the difference between a mutex and a semaphore."
}'
The response’s model field reports the model that served the request. The router works with Chat Completions, Responses and Messages, streaming and non-streaming — so it slots into an existing client without a new SDK.
The second shape adds the plugin when you want routing configuration to differ per request:
curl https://openrouter.ai/api/v1/responses \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-router",
"plugins": [
{ "id": "jev-router", "cost_tier": "medium" }
],
"input": "Summarize this incident report."
}'
Plugin parameters:
| Parameter | Type | Behaviour |
|---|---|---|
id | string | Required. Must be "jev-router". |
cost_tier | string | "low", "medium" or "high". Omit for Jev’s default routing policy. |
models | string array | Include preferences with fallback, applied before dynamic pool selection. |
allowed_models | string array | Alias of models, same fallback behaviour. Entries from both fields are combined. |
excluded_models | string array | Hard exclusions. Matching models are never selected, including during include fallback. |
All fields except id are optional. Unknown fields and invalid values return 400 — the documentation’s example is that allowed_model (singular) is not accepted as an alias. That is the kind of typo that costs an afternoon, and it fails loudly rather than silently, which is the good outcome.
Cost tiers in practice
| Tier | Intent |
|---|---|
low | Prioritise lower estimated cost. |
medium | Balance estimated cost and capability. |
high | Favour more capable models when the expected benefit justifies the added cost. |
cost_tier selects the routing policy, not the model’s reasoning effort — the two are separate knobs and the documentation is explicit about it. Your model and provider restrictions still apply on top of the tier. Omitting cost_tier means Jev’s default policy; setting "medium" explicitly is how you pin the balanced policy rather than accept the default. An unrecognised tier name returns 400.
One consequence worth designing around: changing cost tiers can change the selected model even within the same conversation. If your application depends on response style or output format staying constant across turns, a per-turn tier change will break that expectation. Pick the tier per workflow, not per call, unless you genuinely want the swap.
Model preferences: soft includes, hard exclusions
This is where most of the operational detail lives, and where the semantics are easy to get wrong.
models and allowed_models are include preferences with fallback, not hard allowlists. The router first tries to build a routable pool inside the combined include set. If that pool is empty, it retries without the includes. So models: ["anthropic/*"] prefers Anthropic but can fall back to another author when the include list leaves no routable candidates. The documentation says it plainly: do not use an include list to enforce an author restriction.
excluded_models is the opposite — a hard restriction that stays enforced in both attempts. Two failure modes follow directly:
- Includes that match no catalog model are never relaxed. If no entry in
modelsorallowed_modelsmatches a public text model in the catalog — a misspelled slug, or a slug for an image-only model — the request fails with 404 naming the field instead of quietly routing to the whole pool. This is a genuinely good design: a typo produces an error, not a silent cost surprise. - Exclusions are never ignored. If
excluded_modelsremoves every pool model, the request fails with 404 rather than routing to something you excluded.
For dynamic pools, the filters apply before ranking and before the pool cap. Excluding a higher-ranked model can therefore free a slot for another eligible model. Includes never bypass eligibility checks, endpoint policy, capabilities, or token budgets, and explicit operator pools and built-in pools keep their configured membership.
One more subtlety that will bite anyone tuning cost: the lists can lower the capability tier. When a hard request needs stronger models than the lists leave available, the router can drop its capability requirement, and the stage reports this as list_tier_cap. That is not the same thing as the low / medium / high cost tier, and confusing the two will send you debugging the wrong knob. If no eligible model remains in the pool at all, the request fails with 404 and the error names the fields you sent — advice like widen allowed_models or remove excluded_models.
Pattern syntax and its limits
Each entry in a model preference list can be one of four things:
- An exact model slug, such as
openai/gpt-6-luna. It matches every dated revision of that slug. - A dated revision, such as
openai/gpt-6-luna-20260922, which matches only that revision. - A wildcard pattern, such as
anthropic/*or*flash*. Matching is case-sensitive. - A
~author/family-latestalias, such as~openai/gpt-luna-latest, which matches every revision of that family.
Size limits are documented: up to 1,024 patterns per list, at most 1,024 characters per pattern, and 65,536 characters total per list. Case sensitivity is the detail people trip over — *Flash* and *flash* are different patterns, and only one of them matches a lowercase slug.
Reasoning effort is a hint, not a setting
The router normally chooses reasoning effort. You can supply the standard top-level reasoning.effort field as a hint, independently of cost_tier:
{
"model": "typesafe/jev-router",
"plugins": [{ "id": "jev-router", "cost_tier": "medium" }],
"reasoning": { "effort": "high" },
"input": "Analyze the tradeoffs in this design."
}
On Chat Completions the legacy top-level reasoning_effort field is also accepted. But the router can select a different effort or adapt it across turns, so a hint is not a guarantee of the setting sent to the final model. And two fields are rejected outright: reasoning.max_tokens and reasoning.mode: "pro" return 400. If you have a config that sets either, strip it before pointing at the router.
Seeing what the router did
A router that silently picks models is impossible to debug, so OpenRouter exposes the decision. The response’s model field gives you the headline answer. Add the header X-OpenRouter-Metadata: enabled and the jev-router entry inside openrouter_metadata.pipeline carries the details:
| Field | Meaning |
|---|---|
resolved_models | Models the router selected, in fallback order. |
candidates | Routed candidates with model, effort, capability tier, and estimated_cost. Estimates are not final billed costs. |
reason | The policy’s reason for selecting or retaining a route. |
pipeline | "default" or "agentic". |
advisor_model | The advisor, when one is selected. |
models, excluded_models | The lists the router applied; models includes allowed_models entries. |
list_fallback | "models_ignored" when the include list matched catalog models but no pool model. |
list_tier_cap | The tier the lists capped the request at, when they lowered it. |
max_fallback | "deep" when a request that would get an advisor was served without one. |
The estimated_cost caveat matters for billing work: candidates carry estimates, and estimates are not final billed costs. If you are reconciling spend, use the actual usage records rather than summing router estimates.
Practical advice: log model and reason for every routed call during a rollout. When someone asks why a request became expensive or why the tone changed between two weeks, reason is the field that answers it, and it is not reconstructible after the fact unless you captured it.
Availability, errors, and the regional restriction
Three restrictions apply before any of the above matters.
Global data region only. Jev Router is available only in the global data region and only in workspaces that allow it. Requests using regional routing are among the documented 400 cases.
Access to the router and to the selected models. You still need access to the router and to whichever models get selected. A pool full of models your workspace cannot call will surface as failures, not as degraded routing.
Do not combine router models. typesafe/jev-router must not be combined with another router model in the same request; doing so returns 400. In practice this means no stacking the Jev Router with the auto router or with an author router in one call.
| Status | Documented examples |
|---|---|
| 400 | Unknown cost tier or plugin field, unsupported reasoning settings, regional routing, combining router models. |
| 404 | No eligible model remains after applying model preferences and request requirements, or you do not have access to the router. |
| 503 | Jev Router is unavailable. |
Handle 503 as transient — the router itself being unavailable is not a problem in your request body. Handle 404 as a configuration bug in your preference lists or entitlements. Handle 400 as something you shipped and can fix in a deploy.
Author routers
There is a second way to get Jev’s routing: when available in the catalog, openai/auto, anthropic/auto and other <author>/auto models run Jev with that author’s models. They use the same pool discovery, task policy, reasoning effort and cost tiers as Jev Router.
Two properties to keep straight. First, author scope is a hard restriction: caller models and allowed_models remain include preferences with fallback, excluded_models remains a hard restriction, and all three operate inside the author’s pool. If includes leave no routable candidates, Jev retries without them within the same author; exclusions stay enforced. An author with no eligible model returns 404. Second, the scope is the model author, not the provider — an OpenAI model may still be served by another provider such as Azure. If you need a provider restriction, that is a separate setting.
openrouter/auto is a separate router and does not use this author-alias behaviour. And aliases are catalog artefacts: an alias requires its own catalog router row and, if private, access to that row. The API does not create aliases on request, so you cannot define your own yourauthor/auto by asking nicely.
When the router is the wrong tool
Three situations where pinning jev-1.13 is the correct answer, not the conservative one.
You need a typed decision you can branch on. If the code does if answer.confidence > 0.8 then act, a router that returns prose from an arbitrary model is useless. The decision model exists precisely so there is no text to parse. The router does not produce decisions; it produces text.
You need reproducibility across batches. Scoring, moderation and reranking pipelines compare results over time. The router’s documented behaviour — models and policies can change, tiers can swap models mid-conversation — is the opposite of what a benchmark or a scoring rubric needs. Pin the slug, pin the question phrasing, and treat any change as a deliberate migration.
You need a hard price ceiling per request. A tier is a preference, and the docs say so explicitly. Budget control belongs at the account level (spend limits) or at the model level (a fixed, cheaper slug), not in a routing preference.
Conversely, the router earns its place when you have a mixed workload and no appetite to select models per task: one endpoint, one client, a policy that adjusts to task difficulty, and optional preferences when a particular workflow needs to stay within a family or away from an expensive family.
A safe rollout checklist
- Confirm the slug. Read
typesafe/jev-routeron the OpenRouter model page before shipping. Slugs and model populations change. - Start model-only. No plugin, no preferences. Confirm the response’s
modelfield is populated and that your downstream parsing tolerates a changing value. - Then add
cost_tier, one workflow at a time, and record which tier each workflow uses. Never let two workflows share a tier constant by accident. - Turn on metadata logging (
X-OpenRouter-Metadata: enabled) for at least a week and persistmodel,reason,candidates[].tierandlist_fallback. This is your only forensic trail. - Add preferences last, and prefer exclusions over inclusions. Exclusions are hard; inclusions silently fall back. If the intent is “never this model”, use
excluded_models. If the intent is “prefer this family”, usemodelsand accept the fallback. - Watch for 404s after any preference change. A 404 almost always means the list no longer leaves an eligible model, or a slug stopped matching.
- Do not stack routers. One router per request. If you need author scoping, use the author alias instead of combining.
The failure pattern this checklist prevents is the expensive one: an include list that falls back silently, a tier changed mid-conversation, and a bill that nobody can explain because the metadata was never captured.
Where to go next
If you are still deciding whether Jev is the right shape of tool for your problem, start with what Jev AI is. If you have decided and want the first call working, first API call and get an API key are the shortest path. For the cost picture before you route anything, cost and latency covers the arithmetic; for picking between the official channel and OpenRouter, api channels. And if a routed request starts failing with codes you did not expect, troubleshooting is organised by symptom.
One honest note about scope: this page describes the router as documented by OpenRouter on 2026-11-03 and does not claim anything about TypeSafe’s own direct endpoints. If the official API exposes the router, its authoritative behaviour belongs to typesafe.ai documentation, not to a third-party write-up.