The honest summary: Portkey, LiteLLM, and OpenRouter are good tools, and the right pick depends on where you want the routing decision to live. Portkey and LiteLLM read the route from configuration — server-side and proxy-side respectively — while OpenRouter reads provider preferences on a request it bills you for through its own accounts. unhardcoded takes a different position: the routing decision is a policy_ir you send in the request body, evaluated over your own provider keys, and written to an auditable trace on every call. If you want routing as shared infrastructure config, the others fit well. If you want each call to carry and record its own rules, that is the gap unhardcoded fills. This is the pattern we call policy-based LLM routing, and the rest of this post is a fair, point-by-point comparison so you can decide.
The short answer
All four occupy the same seat in your stack — an OpenAI-compatible endpoint your application calls instead of hitting a provider directly. They diverge on four questions you should answer before you pick one:
- Where does the routing decision live? In a config file someone edits out of band, or in the request itself?
- Who holds the provider keys and the billing relationship? You, with your own accounts — or the gateway, reselling tokens with a markup?
- What does "cheapest" actually optimize? The cheapest route to a model you named, or the cheapest model that still clears a quality floor you set?
- What gets recorded per request? Logs of what happened, or the rules the decision was evaluated against — replayable later?
If "config-side routing, your keys, build-your-own audit" describes what you want, a mature gateway like Portkey or a self-hosted LiteLLM is a sound choice. If "the decision travels with the call, over my keys, recorded and replayable" is what you're after, that is what unhardcoded is built for.
At a glance: the seven dimensions that matter
This table is a fair characterization of each tool's default posture, not a scorecard — every row is a tradeoff, and the right column for you depends on the questions above. "Config" is not a flaw; it's a design choice that suits teams who want one shared route for all traffic.
| Dimension | Portkey | LiteLLM | OpenRouter | unhardcoded |
|---|---|---|---|---|
| Where routing lives | Server config | Proxy config | Provider preferences | policy_ir sent with the call |
| Provider billing | Your keys | Your keys (self-host) | Token-resale markup | Your keys, your pricing |
| Cost optimization | Manual fallbacks | You wire the order | Cheapest route, may miss floor | Cheapest that meets rules |
| Quality floor | Config level | Config level | Provider controls | Enforced per call, fails loud |
| Per-request audit | Logs & analytics | Build it yourself | Generation logs | Replayable by fingerprint |
| Open format | Proprietary config | Open-source proxy | Closed | Open policy_ir & interpreter |
| Run & maintain | Hosted SaaS | You operate it | Hosted SaaS | Self-host free or maintained Cloud |
Portkey: a hosted gateway with config-side routing
Portkey is a mature, hosted AI gateway with a strong feature set: a unified API across providers, observability dashboards, guardrails, caching, and a config object where you define routing strategies like load balancing and fallbacks. It runs over your own provider keys, so you keep your billing relationship, and its logging and analytics are genuinely useful for understanding traffic after the fact.
The design choice to be aware of is that the route lives in a config — defined on the server side and shared across traffic. That is exactly what you want when one routing strategy should apply broadly and change rarely. It is less of a fit when you need a specific call to carry rules that differ from the default, because changing behavior means editing the config rather than describing the requirement in the request. Portkey records what happened in its logs; it does not encode the per-request decision rule as a portable object you can replay. For many teams that distinction won't matter. For teams whose routing requirements vary call-by-call and need to be auditable as rules, it will.
LiteLLM: an open proxy you operate
LiteLLM is the open-source workhorse of this category: a proxy and SDK that normalize over 100+ providers behind an OpenAI-compatible interface, with routing, fallbacks, budgets, and rate limits configurable in YAML. It is open source, self-hostable, and runs entirely over your own keys — which is why it's the default reach for teams who want full control and no third party in the request path.
The tradeoffs are operational, not philosophical. You run it: the proxy is yours to deploy, scale, patch, and keep current as providers change. Routing order and fallbacks are real, but you wire them in config, and the cost-versus-quality logic is whatever you express there. There's no built-in per-decision audit trail that records why a given model was chosen against a stated floor — if you want that, you build it on top of the logs. None of this is a knock on LiteLLM; it's a deliberate "give me the primitives and get out of the way" posture. unhardcoded keeps the open-and-self-hostable property but moves the decision into an open policy_ir sent with the call, so the route is an object the request carries rather than a proxy configuration you maintain out of band. If you want the conceptual breakdown, see what a policy_ir is and how a decision is evaluated.
OpenRouter: one account, many models, token resale
OpenRouter's pitch is convenience: a single account and a single API key give you access to a large catalog of models across many providers, with automatic provider selection and the ability to express provider preferences per request. For prototyping, for breadth of model access without signing up with every provider, and for one-key simplicity, it's hard to beat.
The structural difference is billing. OpenRouter routes through its own provider accounts and bills you for tokens, typically with a markup — you are not using your own provider keys or your own pricing. Its "cheapest" optimization picks an inexpensive route to a model, which is great for cost but doesn't enforce a quality floor you defined; if you don't pin the model, a request can land somewhere beneath your bar. OpenRouter offers generation logs, but the platform is closed and the routing decision isn't an open object you control. It's an excellent breadth-and-convenience layer; it is not the tool for "keep my keys, enforce my floor, and hand me a replayable record of the rule."
Where unhardcoded differs: the decision rides the call
The single idea that separates unhardcoded from all three is that the routing decision is data in the request, not configuration on a server. Your backend generates a policy_ir — a small JSON term stating the requirements for this call — and sends it with the request. The router admits it, evaluates it over the current catalog on your own keys, and routes to the cheapest model that meets your rules. The order is what makes the floor a guarantee rather than a hope:
- Filter first. Candidates lacking a required capability, missing the quality floor, or exceeding the price ceiling are eliminated — a failing model is dropped, never silently substituted.
- Rank survivors by cost. Among the models that cleared the floor, cheapest wins.
- Select the top, fall back in order. One model is chosen deterministically; if it errors or times out, the router moves to the next passing candidate and records the hop.
Here is the shape of it. The old hardcoded baseline below is struck through to show what you're leaving behind — it never appears in the live decision:
// generated in your backend, at request time
const policy_ir = ["policy",
["and", ["meets_req"], ["not",["is","disabled"]],
["cmp", "bench_intelligence", "ge", 0.5],
["cmp", "price_out", "le", 5]], // floor
["neg", ["normalize", ["field", "price_out"]]], // cheapest that passes
["argmax"], ["id"],
["always", { action: "next_candidate" }]];
const res = await client.chat.completions.create({
model: "gpt-5.5", // the old hardcoded baseline — gone
model: "policy:support", // free-form trace label, not the route
policy_ir, // the decision, sent with the call
messages, // unchanged
});
Two consequences follow that the config-based tools don't give you by default. First, pricing is per run, not per token: you bring your own provider keys, pay your providers directly, and unhardcoded prices the routing per run — it never resells inference, so there's no token markup between you and your provider. Second, because the policy is admitted and hashed before it runs, every decision is written to a replayable, auditable trace: the rules sent, the candidates considered, why each was kept or dropped, the model selected, the fallback path, the latency, and the cost — keyed by a fingerprint you can replay later. That's the difference between a log of what happened and a record of the rule that decided it. For the broader pattern and the full mechanics, the pillar — policy-based LLM routing: the complete guide — maps the whole territory, and what an LLM policy router actually is covers the runtime piece.
The line that matters: the other three answer "how should this gateway be configured to route?" unhardcoded answers "what does this call require, and can I prove which rule chose the model?" Both are legitimate; only you know which question your team is actually asking.
How to choose
A fair recommendation, by what you're optimizing for:
- Pick OpenRouter if you want the widest model catalog behind one key and you're comfortable with token-based billing through its accounts — ideal for prototyping and breadth.
- Pick LiteLLM if you want an open, self-hosted proxy over your own keys and you're happy to operate it and wire the routing yourself.
- Pick Portkey if you want a polished hosted gateway over your keys with strong observability and a config-driven routing strategy that applies broadly.
- Pick unhardcoded if you want the routing decision to travel with each call over your own keys, the cheapest model that still clears your floor, per-run pricing, and a replayable, auditable record of the rule behind every decision. You can see the product, read the docs, or compare pricing on the pricing page.
If you're coming from a hardcoded model name and weighing your first move off it, the related read is the 5-minute quickstart — it shows the full before-and-after at a single call site.
Bottom line: these are all credible tools. The deciding question isn't "which is best" — it's "where do I want the routing decision to live, and do I need to prove which rule chose the model?" Config-side routing is a fine answer for many teams. If your answer is "in the call, over my keys, and on the record," that's the seat unhardcoded was built to fill.