Move from OpenRouter
The exchange serves the OpenAI Chat Completions format, so most OpenRouter code changes in two places: the base URL and the API key. The exchange is an auction. It does not send your request to a fixed provider, so read What is different about money before you send traffic.
Change these two things
- Issue a
spendkey on the API keys page. See API keys. - Set the base URL to
https://router.inference.ai.exchange/v1. Send the key asAuthorization: Bearer sk-liquid-….
from openai import OpenAI
client = OpenAI(
base_url="https://router.inference.ai.exchange/v1",
api_key="sk-liquid-...",
)
Then check three more things:
- Change each
modelto a name thatGET /v1/modelslists. A name such asopenai/gpt-oss-120bdoes not work. - Move each
providersetting to theexchangeobject. See Routing settings. - Run a dry run, then send a small request. See Check a request without a purchase.
What differs
- Model names. The exchange has no
vendor/modelnames. To limit the models to one creator, useexchange.candidates.creators.allow. - Fallbacks. The
modelslist is ignored. Sendarchitect.basketwithexchange.candidates.models.allow. - Suffixes. Use
:web(beta) instead of:online.:nitroand:floorreturn404 model_not_found. To rank the offers, use:fastestor:cheapest. See Axis routing. - Money. The exchange fixes a price cap per job. See below.
What is different about money
The exchange fixes a price cap before the first token. The cap is the most the job can cost. The exchange holds that amount from your workspace balance, charges the tokens used, and returns the rest. See Quickstart.
- Send
exchange.cap_usdto set your own cap. An offer with a higher worst-case charge does not compete. - The
x-liquid-cap-usdheader andexchange.charge.cap_usdgive the cap.exchange.charge.total_usdgives the charge. - If the cheapest eligible cap is more than the balance, the response is
402 insufficient_balance.needed_usdis the lowest cap that an offer holds for your limit. If the key reached its spending limit, the response is402 budget_exhausted. - If an output limit that you set does not fit your cap, the response is
400 no_eligible_offerwithparamset toexchange.cap_usd. The message gives the lowest cap that fits. - The exchange never lowers an output limit that you set. It refuses the request.
- If you set no output limit, the exchange chooses one. It lowers the limit to what an offer allows, and halves it when your balance or cap excludes every offer. It stops at 1,024 tokens.
Output limits has the exact rule.
Check a request without a purchase
POST /v1/exchange/dry-run shows which offers would compete, in award order, and why the others are left out. It places no job and holds no funds. It takes model and an exchange object, and it does not read provider.
curl https://router.inference.ai.exchange/v1/exchange/dry-run \
-H "Authorization: Bearer $LIQUID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-oss-120b",
"exchange": { "candidates": { "providers": { "allow": ["acme"] } }, "cap_usd": 0.05 }
}'
Example
An OpenRouter request:
{
"model": "openai/gpt-oss-120b",
"messages": [{ "role": "user", "content": "Say hello." }],
"provider": {
"only": ["acme"],
"ignore": ["slow-*"],
"max_price": { "prompt": 1, "completion": 2 },
"data_collection": "deny",
"sort": "price"
}
}
The same request for the exchange. The model name is an illustration:
{
"model": "gpt-oss-120b",
"messages": [{ "role": "user", "content": "Say hello." }],
"exchange": {
"candidates": {
"providers": { "allow": ["acme"], "exclude": ["slow-*"] }
},
"limits": { "price": { "input_usd_per_mtok": 1, "output_usd_per_mtok": 2 } },
"privacy": { "retention": "none" },
"rank": { "strategy": { "kind": "price" } },
"cap_usd": 0.05
}
}
Reference
Model names
The model field accepts a model, a router model, a basket or a preset. Connect an app has the table. An unknown name returns 404 model_not_found with suggestions. The exchange never uses a different model.
Routing settings
The exchange reads a provider object and applies each setting that it can apply exactly. Purchasing rules has the only table of every setting. In short:
- The exchange maps
max_price.prompt,max_price.completion,only,ignore,order,data_collection: "deny",zdr: trueandsort: "price". ordertries its providers first, then the rest of the market. Withallow_fallbacks: false, only the providers inordercompete. See Preferred providers.require_parametersandallow_fallbacks: truechange nothing.- It refuses every other setting. One
400 unsupported_parameteranswer lists all of them. providercan only narrow whatexchange, the key preset and the workspace guardrails allow.- An
ignorename must be a provider handle, or a pattern with*.
A refused setting can have an exchange field to use instead:
Refused provider setting |
Use instead |
|---|---|
preferred_min_throughput |
exchange.limits.min_tps (a limit, not a preference) |
preferred_max_latency |
exchange.limits.ttft_ms or exchange.limits.completion_ms (limits) |
quantizations, max_price.request, max_price.image, allow_fallbacks: false without order, any other sort, unknown settings |
No equal. Remove the setting, or rank with exchange.rank.strategy. |
Other OpenRouter fields
| OpenRouter feature | What the exchange does |
|---|---|
models (fallback list) |
Ignores it. Use architect.basket with exchange.candidates.models.allow. See Purchasing rules. |
route, transforms, plugins, usage (request field) |
Ignores them. The response has the OpenAI usage object, and the exchange block has the cap and the charge. |
Model suffix :online |
Use :web (beta). The exchange searches the web for the last user message and puts the results in the prompt. See Axis routing and Quickstart. |
Model suffixes :nitro, :floor |
Part of the model name, so 404 model_not_found. Use :fastest or :cheapest. See Axis routing. |
HTTP-Referer and X-Title headers |
Labels for your receipts. They never change a price, a route or an authorization. |
reasoning |
Reads reasoning.effort, reasoning.max_tokens, reasoning.exclude and reasoning_effort. Matches offers that support the setting, or refuses with unsupported_parameter. exclude hides the reasoning, and the exchange still bills it. |
response_format, tools, tool_choice, cache_control |
Applied. Only an offer that declares support can win. See Structured output and Prompt caching. |
top_p, top_k, min_p, repetition_penalty, stop, seed, logprobs, top_logprobs, logit_bias, frequency_penalty, presence_penalty |
Passes them to a provider that declares support. If no offer supports one, unsupported_parameter. |
n greater than 1 |
Refuses it. |
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
400 unsupported_parameter, param: "provider" |
A provider setting cannot map, or a price is negative or not a number. |
Read the message. See Routing settings. For provider.ignore, use a provider handle. See Market data. |
400 unsupported_parameter, param is another field |
No offer for the model supports that parameter. | Remove the parameter, or select another model. |
404 model_not_found |
The model is not on the exchange. |
Pick a name from GET /v1/models. |
no_eligible_offer |
The limits of the request, key or workspace excluded each offer. | Read eligibility.excluded. Raise cap_usd, or relax a limit. Do not retry the same request. See Errors. |
402 insufficient_balance |
The cheapest eligible cap is more than the balance. | Add funds, or send a lower output limit. |