Move from Portkey
Portkey keeps its routing rules in a config that a request names in the x-portkey-config header. The exchange has no config object and reads no x-portkey-* header. It takes its rules in an exchange object in the request body, or from a preset. The move is more than a new base URL.
Change these four things
- Issue a
spendkey on the API keys page. See API keys. - Use a client that sends the OpenAI format. Set its base URL to
https://router.inference.ai.exchange/v1, and send the key asAuthorization: Bearer sk-liquid-…. See OpenAI SDK. - Remove the
x-portkey-*headers. Thex-liquid-job-idresponse header gives the id of each job. - Change each
modelto a name thatGET /v1/modelslists. See Model names.
Then:
- Move each config to an
exchangeobject or a preset. See Settings that change and Portkey features. - Run a dry run. See Check a request without a purchase.
- Read What is different about money. The exchange never lowers an output limit that you set. See Output limits.
What differs
- Providers. You hold no provider key. A name such as
@provider-slug/model-namedoes not work. To limit the providers, useexchange.candidates.providers.allowwith provider handles. - Fallback. If the awarded provider fails before any output, the exchange tries the next offer. It never does so after output starts. To compete several models, send
architect.basket. - Retries, load balancing, guardrails and cache. See Portkey features.
Example
A Portkey config with a fallback, a retry and a cache:
{
"strategy": { "mode": "fallback", "on_status_codes": [429, 503] },
"targets": [
{ "override_params": { "model": "@openai-prod/gpt-4o" } },
{ "override_params": { "model": "@anthropic-prod/claude-sonnet-4-6" } }
],
"retry": { "attempts": 3 },
"cache": { "mode": "simple", "max_age": 600 }
}
The same rules for the exchange. The basket replaces the targets: the two models compete, and the lowest estimated charge wins. The cache headers replace cache. One failure returns one error. The model names are illustrations.
curl https://router.inference.ai.exchange/v1/chat/completions \
-H "Authorization: Bearer $LIQUID_API_KEY" \
-H "x-liquid-cache: true" \
-H "x-liquid-cache-ttl: 600" \
-H "x-liquid-tag: support" \
-H "Content-Type: application/json" \
-d '{
"model": "architect.basket",
"messages": [{"role": "user", "content": "Say hello."}],
"exchange": {
"candidates": { "models": { "allow": ["gpt-oss-120b", "gpt-oss-20b"] } },
"rank": { "strategy": { "kind": "price" } },
"cap_usd": 0.05
}
}'
To save these rules, make a preset on the Presets page and send its name as model.
Reference
Settings that change
| Portkey setting | Exchange setting |
|---|---|
x-portkey-provider, provider, x-portkey-virtual-key and virtual_key |
No equal. Use exchange.candidates.providers.allow or exchange.candidates.providers.exclude with provider handles. A Portkey slug is not a handle. |
x-portkey-config and config |
An exchange object in the body, or a preset that you name as model. See Purchasing rules and Presets. |
x-portkey-metadata |
x-liquid-tag with one label. See Cost tags. |
Model names
The exchange does not use the @provider-slug/model-name form. Send a name that GET /v1/models returns, a router model, a basket or a preset. See Connect an app.
Portkey features
| Portkey feature | What the exchange does |
|---|---|
strategy.mode fallback, targets, on_status_codes |
Not read. Send architect.basket with exchange.candidates.models.allow, and rank with exchange.rank.strategy. See Errors. |
strategy.mode loadbalance, weight, conditional |
Not read or not available. The exchange awards each job to one offer by rank. Choose the model in your own code. See Purchasing rules. |
retry with attempts |
Not read. If your code retries, send the same idempotency-key on each try, so that no second job runs. See Purchasing rules. |
x-portkey-request-timeout |
Not read. exchange.limits.ttft_ms and exchange.limits.completion_ms limit which offers compete. A job that misses the deadline is still charged, and its receipt shows missed. See Purchasing rules. |
cache with mode simple |
Send x-liquid-cache: true, or set exchange.cache.response in the rules. An identical request from the same key gets the stored answer. The entry lives 1 to 86,400 seconds. See Response caching. |
cache with mode semantic |
Not available. Only an identical request matches. |
x-portkey-cache-force-refresh, x-portkey-cache-namespace |
Not read. x-liquid-cache-clear: true deletes the entry and runs the request again. A key never shares an entry with another key. |
input_guardrails, output_guardrails, before_request_hooks |
Not read. The workspace guardrails apply to every call. There is no output check. See Guardrails. |
override_params, default_params, drop_params of a target |
Not available. Put the model and the parameters in the request. |
tools, response_format, stream |
Applied. See Other OpenRouter fields. |
Troubleshooting
For 402 insufficient_balance and 400 no_eligible_offer with param set to exchange.cap_usd, see What is different about money.
| Error | Cause | Fix |
|---|---|---|
404 model_not_found |
The model has a @provider-slug/ prefix, or is not on the exchange. |
Pick a name from GET /v1/models. |
400 invalid_request, message names a field of exchange |
The exchange object has a Portkey key or an unknown field. |
Use only the fields in Purchasing rules. |
no_eligible_offer, binding_rule is allowed_providers |
No provider in the allowlist quotes the model. A Portkey slug causes this. | Use provider handles. See Market data. |
400 unsupported_parameter, param is a parameter you set |
No offer for the model supports that parameter. | Remove the parameter, or select another model. See Errors. |
402 insufficient_balance |
The cheapest eligible cap is more than the balance. | Add funds, or send a lower output limit. |