# 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

1. Issue a `spend` key on the **API keys** page. See [API keys](https://inference.ai.exchange/docs/keys.md).
2. Use a client that sends the OpenAI format. Set its base URL to `https://router.inference.ai.exchange/v1`, and send the key as `Authorization: Bearer sk-liquid-…`. See [OpenAI SDK](https://inference.ai.exchange/docs/connect-openai-sdk.md).
3. Remove the `x-portkey-*` headers. The `x-liquid-job-id` response header gives the id of each job.
4. Change each `model` to a name that `GET /v1/models` lists. See [Model names](#model-names).

Then:

1. Move each config to an `exchange` object or a preset. See [Settings that change](#settings-that-change) and [Portkey features](#portkey-features).
2. Run a dry run. See [Check a request without a purchase](https://inference.ai.exchange/docs/move-from-openrouter.md#check-a-request-without-a-purchase).
3. Read [What is different about money](https://inference.ai.exchange/docs/move-from-openrouter.md#what-is-different-about-money). The exchange never lowers an output limit that you set. See [Output limits](https://inference.ai.exchange/docs/errors.md#output-limits).

## What differs

- **Providers.** You hold no provider key. A name such as `@provider-slug/model-name` does not work. To limit the providers, use `exchange.candidates.providers.allow` with 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](#portkey-features).

## Example

A Portkey config with a fallback, a retry and a cache:

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

```bash
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](https://inference.ai.exchange/docs/rules.md) and [Presets](https://inference.ai.exchange/docs/rules.md#presets).         |
| `x-portkey-metadata`                                                        | `x-liquid-tag` with one label. See [Cost tags](https://inference.ai.exchange/docs/rules.md#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](https://inference.ai.exchange/docs/connect-overview.md).

### 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](https://inference.ai.exchange/docs/errors.md#auction).                                                                            |
| `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](https://inference.ai.exchange/docs/rules.md#strategy).                                                                  |
| `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](https://inference.ai.exchange/docs/rules.md#headers).                                                                             |
| `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](https://inference.ai.exchange/docs/rules.md#service). |
| `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](https://inference.ai.exchange/docs/response-caching.md).   |
| `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](https://inference.ai.exchange/docs/guardrails.md).                                                                                                             |
| `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](https://inference.ai.exchange/docs/move-from-openrouter.md#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](https://inference.ai.exchange/docs/move-from-openrouter.md#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](https://inference.ai.exchange/docs/rules.md).                    |
| `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](https://inference.ai.exchange/docs/market.md).                     |
| `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](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.                                  |
