Skip to content

Gateway

Token Intelligence

The Gateway scores every request 0–100, takes the lowest tier that clears the score, and picks a curated model inside it. The score, the tier and the model come back on the response.

Routing runs on every request, whether you sent "auto" or named a model. What changes is the ceiling: a named model caps how high the router may reach, and auto lets it reach as high as your plan allows.

The algorithm is versioned. This page describes ti-1.0.0, and the version changes when a signal, its test, its points or a tier threshold changes. It is deterministic for a given input on a given version: the same text and the same conversation length always produce the same score. The model preference lists are not part of the version — they move with the catalogue — so a decision is reproducible against a version and the catalogue of the day, not against the version alone.

This is not the differentiatorOpenRouter, LiteLLM and Requesty all route by complexity, and LiteLLM does it free and open source. Do not choose Lobstack because it routes. Choose it, if you do, because the routing decision and the price come back on the response instead of living in a dashboard, and because a saving comes back labelled with the model it was measured against and why.

How complexity is scored

The scorer is a heuristic over the last user message, plus the number of messages in the conversation. It is not a model call, it costs nothing, and it is deterministic — the same text always scores the same number. Points accumulate and the total is capped at 100.

SignalTestPoints
Message lengthunder 50 characters5
50 to 199 characters15
200 to 499 characters30
500 characters or more45
Codea fenced block, or function / class / import / const / let / var / def / async20
Analysisanalyze, compare, evaluate, explain why, reason, think through, trade-off20
Multi-stepfirst, then, next, after that, finally, step N, or a numbered list15
Attachmentthe text contains [Attached file:15
Creative writingwrite, draft, compose, create, story, essay, article, blog10
Conversation depthmore than 10 messages10
more than 20 messages10

The two banded signals behave differently. Length picks exactly one band. Depth is cumulative: a conversation of more than twenty messages scores both rows, for 20 points. Every signal at its top band would total 145, which is why the cap at 100 exists and why a maximal request and a merely very large one both score 100.

Two consequences are worth knowing. Only the last user message is scored, so a long conversation whose latest turn is "yes, do that" scores as a short message with a depth bonus. And the word tests are word tests: "write a haiku" picks up the creative points, and so does "create a table".

x-lobstack-complexity carries the score on every response. If a score surprises you, the fastest way to see why is to send the same text to the preview endpoint below.

What it does not do

It is worth being blunt about the ceiling of a heuristic this small, because the value on offer is that you can audit it rather than that it is clever.

It does notBecause
call a modelThere is no inference of any kind in the scoring path: 5 regular expressions, two length comparisons and a sum.
learn anythingNo weights fitted to outcomes, no training data, no feedback loop. The points in the table above were chosen by a person, which is why they can be published.
read meaningIt cannot tell a hard question from an easy one phrased at length. A 900-character copy-paste outscores a genuinely difficult one-line problem.
predict answer qualityNor latency, nor provider load. Tier membership is a curation decision in src/lib/token-conservation.ts, not a measurement.
adapt to youTwo accounts sending identical text on the same plan get identical decisions. There is no per-account state in the selector.

The five tiers

Each tier has a maximum complexity it will accept. The router walks from nano upward and stops at the first tier whose threshold covers the score. The lead model is the first key in the tier's preference list that exists in the registry, and it is what an auto request lands on.

TierScoreLead model$/M in / outCandidatesCost ×
nano≤ 20Gemini 3.1 Flash Lite (gemini-3.1-flash-lite)$0.25 / $1.5060.10
small≤ 40Claude Haiku 4.5 (claude-haiku-4-5)$1.00 / $5.0090.30
standard≤ 70Claude Sonnet 5 (claude-sonnet-5)$2.00 / $10.00120.67
premium≤ 90Kimi K3 (kimi-k3)$3.00 / $15.00112.00
flagship≤ 100Claude Opus 5 (claude-opus-5)$5.00 / $25.0075.00

The cost multiplier is a relative weight, not a price. It is what x-lobstack-savings-pct is computed from: one minus the selected tier's multiplier over the ceiling tier's, floored at zero. That number is an estimate about tiers. The measured dollar figure is a different header and a different rule, described on Metering & cost.

The lowest tier is not the cheapest model

Within a tier the order is a quality preference, not a price sort. The router takes the first model in the list, which is frequently not the cheapest one available at that tier. That is the default; the ordering can be changed, and the next section says how and what it would cost you differently.

The standard tier leads with Claude Sonnet 5 at $2.00 in and $10.00 out per million tokens, while DeepSeek V4 Flash sits in the same tier at $0.27 and $1.10. Routing standard traffic to the cheaper one would cut the bill several times over and change what answers a customer, which is a product decision rather than a cost decision.

The same reasoning shapes the flagship tier, where Opus 5 leads rather than Fable 5.1. Fable is the more capable model, and it costs twice as much. Doubling the price of every hard request without the caller asking is not a decision to make on their behalf. Name Fable when you want it.

Price-aware ordering, and why it is off

That preference order can be replaced with a cheapest-first walk, and the switch is GATEWAY_PRICE_AWARE_ROUTING. It is off by default. Turning it on changes which model answers a paying customer, which is a decision about the product rather than a routing detail, so it is not on because it tested well.

It changes the order and nothing else. The candidate set, the availability predicate and the BYOK provider lock are untouched, so the two modes can only ever disagree about which of the same eligible models is picked — never about whether a model is eligible at all. Ties keep list order, so a decision stays reproducible.

Cheapest-first is not price alone. Sorting purely on price routed traffic to de-listed previous-generation keys that the registry keeps for callers who name them explicitly, and cheapest is not a reason to serve somebody last year’s model. Current generation first, then price.

TierList-first leadPrice-aware leadDifference
nanoGemini 3.1 Flash LiteGPT-OSS 20B77% cheaper
smallClaude Haiku 4.5Qwen 3.8 Flash89% cheaper
standardClaude Sonnet 5DeepSeek V4 Flash88% cheaper
premiumKimi K3Grok 4.650% cheaper
flagshipClaude Opus 5DeepSeek V4 Pro81% cheaper

Both columns are produced by the router’s own ordering function, called once in each mode, so this table cannot describe a sort the Gateway does not perform. Prices are blended at 3:1 input to output, and the comparison is across every managed model in the registry — every one of the 5 tiers leads with a different model under the two orderings.

A real deployment sees fewer changes than that, and the reason is worth stating rather than leaving as a footnote: a cheaper model on a provider whose key is not configured is not eligible in either mode, so it cannot be picked and cannot appear as a saving. The table above is the ceiling on what the flag can do, not a forecast of what it would do for you. What it would do for the deployment actually serving you is a question for route-preview, which narrows to the providers this deployment can reach.

A flag flip is visible in the data, not inferred from itEvery routing event records which ordering produced the decision, so the same tier and the same score yielding a different model does not read as a behaviour change when it was a setting change. The field is price_aware on Routing data.

Plan ceilings

A plan caps how high the router may reach, regardless of the score.

PlanHighest tier reachable
Freestandard
BYOKflagship
Developerflagship
Studioflagship
Scaleflagship

A priced plan carries its ceiling as data, so there is one ceiling mechanism rather than two tables that can disagree. Subscriptions on the older messages-per-month tiers have no priced plan and keep their own map: starter reaches standard, and pro, performance and enterprise reach flagship.

When a score exceeds the ceiling nothing fails: the request is served at the ceiling tier. A complexity-100 request on Free is answered by the standard tier, and x-lobstack-savings-pct reads 0 because the selected tier is the ceiling tier.

Two defaults are worth stating plainly. An org-level API key with no active subscription resolves to the legacy starter tier, because a fallback that is generous here spends real money. A tier that is neither a priced plan nor one of the four legacy names falls back to flagship, which is the opposite direction — it exists for deployments that are not selling priced plans at all.

Every fallback inside the selector is bounded by the ceiling too. A tier whose providers are all unkeyed does not silently fall through to something more expensive; it falls to the best keyed model at or below the ceiling.


What happens when you pin a model

Naming a model sets a ceiling. The effective ceiling is the lower of your model's tier and your plan's tier, and the router still walks up from nano and stops at the first tier that clears the score. You get the exact model you named only when the selected tier is that model's tier and its provider has a key configured. Otherwise you get that tier's lead model.

You sendPrompt scoresServedx-lobstack-routed
claude-opus-5100claude-opus-5false
claude-opus-55the nano lead modeltrue
claude-haiku-4-5100claude-haiku-4-5 (ceiling is small)false
autoanythingthe selected tier's lead modeltrue, always

x-lobstack-routed is true whenever you sent auto, even when the model chosen happens to be the one you would have picked. It means "the Gateway chose this", not "the Gateway substituted".

In BYOK mode routing is additionally locked to your key's provider, because a single-vendor key cannot call another vendor. A Claude key routes among Claude models only.

There is no way to force a model on every requestThe Gateway has no equivalent of "never route". If reproducibility matters, pin the model and check x-lobstack-model on each response, or raise the complexity of what you send. Pinning a nano-tier model is the one case where the pin is absolute, because there is no tier below it.

Previewing a routing decision

POSThttps://www.lobstack.ai/api/gateway/v1/route-preview

This endpoint answers "where would this go?" without spending a token. It runs the same selectModel and the same price registry the real endpoint uses, so the answer is the decision rather than a simulation of it. It is unauthenticated, runs no inference, calls no provider and writes nothing.

Requestbash
curl https://www.lobstack.ai/api/gateway/v1/route-preview \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "What time zone is Lisbon in during summer?",
    "requested_model": "claude-opus-5",
    "plan_tier": "pro"
  }'
Responsejson
{
  "object": "routing_preview",
  "requested_model": "claude-opus-5",
  "plan_tier": "pro",
  "complexity": 5,
  "tier": "nano",
  "routed": true,
  "reason": "Trivial query → nano tier",
  "model": {
    "key": "gemini-3.1-flash-lite",
    "label": "Gemini 3.1 Flash Lite",
    "provider": "google",
    "context_window": 1048576,
    "price_per_mtok": { "input": 0.25, "output": 1.5 },
    "managed_key_configured": true
  },
  "token_estimate": {
    "input": 11,
    "output": 64,
    "method": "~4 characters per token; output assumed at half the prompt",
    "estimated": true
  },
  "cost_usd": 0.000099,
  "baseline": {
    "model": "claude-opus-5",
    "label": "Claude Opus 5",
    "cost_usd": 0.001655,
    "saving_usd": 0.001556
  }
}

The body accepts prompt (required, up to 8,000 characters), requested_model (default auto), plan_tier (default pro), conversation_length and expected_output_tokens. An unknown model is a 400.

plan_tier still takes the four legacy tier names rather than the plan ids in the table above. starter reproduces a standard ceiling and the other three reproduce flagship; anything else, including "free", is ignored and answered as pro. Use starter to preview what a ceiling-capped plan would do.

Token counts here are estimates at roughly four characters per token, and the response says so in token_estimate.estimated. Output length is unknowable before generation, so it defaults to half the prompt unless you supply it. The billed figure always comes from the provider's own usage block on the real request.

baseline here is the named case only: it is null when you send auto. The live endpoint does more than this one — on auto it reports a plan_ceiling baseline, labelled as such — so treat a null here as "the preview has nothing to compare" rather than as what the real request will report. The preview also quotes provider list prices in cost_usd, where the live receipt quotes the Lobstack rate. See Metering & cost.

The preview reports managed_key_configured because it runs against the whole registry while the live path narrows to providers whose key is actually set on the deployment.

The Token Intelligence page runs the same selector in your browser and shows the per-signal breakdown alongside it, so you can watch a decision decompose without writing any code.


Reproducing a decision

Every routing decision is a pure function of five things, and all five are either on the response or on this page. Given a response you can re-derive the model it was served by:

StepDo thisCheck against
1Take the last user message and the number of messages in the conversation—
2Score it against the signal table above and cap the sum at 100x-lobstack-complexity
3Take the lower of your plan's ceiling and the tier of any model you named—
4Walk the tiers from nano upward; stop at the first whose threshold covers the score, or at the ceilingx-lobstack-tier
5Take the first key in that tier's preference list the registry serves and whose provider has a keyx-lobstack-model

The one input not carried on the response is the conversation length, because the Gateway is stateless and counts what you sent it. If a re-derivation disagrees with a header, the fastest check is to post the same message to /api/gateway/v1/route-preview and compare — it runs the same selector with no inference and no state, so a difference is a difference in the input.

Tier membership and the model preference order live in MODEL_TIERS, and the signals in COMPLEXITY_SIGNALS. Both are exported data, and the table on this page is rendered from them rather than transcribed, so this document cannot drift from the code it describes. A build check re-scores a corpus against the previous implementation and fails if any total moves.

Lobstack

One key over every frontier model, a receipt on every call, and an agent that waits before it changes anything.

© 2026 LobstackAll rights reserved  Status