# 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](#what-is-different-about-money) before you send traffic.

## Change these two things

1. Issue a `spend` key on the **API keys** page. See [API keys](https://inference.ai.exchange/docs/keys.md).
2. Set the base URL to `https://router.inference.ai.exchange/v1`. Send the key as `Authorization: Bearer sk-liquid-…`.

```python
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 `model` to a name that `GET /v1/models` lists. A name such as `openai/gpt-oss-120b` does not work.
- Move each `provider` setting to the `exchange` object. See [Routing settings](#routing-settings).
- Run a dry run, then send a small request. See [Check a request without a purchase](#check-a-request-without-a-purchase).

## What differs

- **Model names.** The exchange has no `vendor/model` names. To limit the models to one creator, use `exchange.candidates.creators.allow`.
- **Fallbacks.** The `models` list is ignored. Send `architect.basket` with `exchange.candidates.models.allow`.
- **Suffixes.** Use `:web` (beta) instead of `:online`. `:nitro` and `:floor` return `404 model_not_found`. To rank the offers, use `:fastest` or `:cheapest`. See [Axis routing](https://inference.ai.exchange/docs/axis-routing.md).
- **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](https://inference.ai.exchange/docs/quickstart.md).

- Send `exchange.cap_usd` to set your own cap. An offer with a higher worst-case charge does not compete.
- The `x-liquid-cap-usd` header and `exchange.charge.cap_usd` give the cap. `exchange.charge.total_usd` gives the charge.
- If the cheapest eligible cap is more than the balance, the response is `402 insufficient_balance`. `needed_usd` is the lowest cap that an offer holds for your limit. If the key reached its spending limit, the response is `402 budget_exhausted`.
- If an output limit that you set does not fit your cap, the response is `400 no_eligible_offer` with `param` set to `exchange.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](https://inference.ai.exchange/docs/errors.md#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`.

```bash
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:

```json
{
  "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:

```json
{
  "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](https://inference.ai.exchange/docs/connect-overview.md) 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](https://inference.ai.exchange/docs/rules.md#openrouter-provider) 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: true` and `sort: "price"`.
- `order` tries its providers first, then the rest of the market. With `allow_fallbacks: false`, only the providers in `order` compete. See [Preferred providers](https://inference.ai.exchange/docs/provider-preferences.md#openrouter-requests).
- `require_parameters` and `allow_fallbacks: true` change nothing.
- It refuses every other setting. One `400 unsupported_parameter` answer lists all of them.
- `provider` can only narrow what `exchange`, the key preset and the workspace guardrails allow.
- An `ignore` name 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](https://inference.ai.exchange/docs/rules.md#the-basket).                                                                                                                      |
| `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](https://inference.ai.exchange/docs/axis-routing.md#web-search) and [Quickstart](https://inference.ai.exchange/docs/quickstart.md#web-access).                                   |
| Model suffixes `:nitro`, `:floor`                                                                                                                  | Part of the model name, so `404 model_not_found`. Use `:fastest` or `:cheapest`. See [Axis routing](https://inference.ai.exchange/docs/axis-routing.md).                                                                                                                         |
| `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](https://inference.ai.exchange/docs/errors.md#structured-output) and [Prompt caching](https://inference.ai.exchange/docs/caching.md).                                                                                                |
| `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](#routing-settings). For `provider.ignore`, use a provider handle. See [Market data](https://inference.ai.exchange/docs/market.md). |
| `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](https://inference.ai.exchange/docs/errors.md).                  |
| `402 insufficient_balance`                            | The cheapest eligible cap is more than the balance.                      | Add funds, or send a lower output limit.                                                                                                   |
