# Move from the OpenAI API

The exchange serves Chat Completions and a stateless part of Responses. Most OpenAI code changes in two places: the base URL and the API key. Model names, and features that need OpenAI to keep state, need more work. For setup, see [OpenAI SDK](https://inference.ai.exchange/docs/connect-openai-sdk.md).

## Change these three 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` (or `OPENAI_BASE_URL`). Send the key as `Authorization: Bearer sk-liquid-…` (or `OPENAI_API_KEY`).
3. Change each `model` to a name that `GET /v1/models` lists. See [Model names](#model-names).

Then check these:

- Remove each parameter that the exchange refuses. See [OpenAI features](#openai-features).
- 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).
- 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

- **Model names.** An unknown name, or a fine-tuned model, returns `404 model_not_found`.
- **`n` greater than 1.** Refused. OpenAI bills every choice, and the exchange serves one.
- **State in Responses.** `previous_response_id`, `conversation` and `background: true` are refused. Send the full conversation in `input`.
- **Hosted tools.** The exchange runs none. It removes `web_search` with the header `x-liquid-notice: web_search_dropped` and refuses the others. To search the web, add `:web` (beta) to the model name.
- **Not available.** The Batch, Files, Assistants and Moderations APIs, and image edits.
- **Retries.** The SDK retries a `5xx` response, and each try is a new request. To get the first response again, send the same `idempotency-key` header. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md#headers).

With `with_raw_response`, the SDK returns the headers, including `x-liquid-cap-usd`.

## Example

An OpenAI request, then the same request for the exchange. The new request changes the client and the model, removes `n=2` (refused) and `store` and `service_tier` (ignored), and adds a cap and a data rule. `user` stays, because it names the end user for guardrail limits. The model name is an illustration.

```python
from openai import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Say hello."}],
    max_completion_tokens=500,
    n=2,
    store=True,
    service_tier="auto",
    user="user-123",
)
```

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://router.inference.ai.exchange/v1",
    api_key=os.environ["LIQUID_API_KEY"],
)

response = client.chat.completions.create(
    model="gpt-oss-120b",
    messages=[{"role": "user", "content": "Say hello."}],
    max_completion_tokens=500,
    user="user-123",
    extra_body={"exchange": {"cap_usd": 0.05, "privacy": {"retention": "none"}}},
)
```

## Reference

### Model names

Send a name that `GET /v1/models` returns. The `model` field accepts a model, a router model, a basket or a preset. See [Model names](https://inference.ai.exchange/docs/move-from-openrouter.md#model-names). The exchange never uses a different model.

`max_completion_tokens`, `max_tokens` and `max_output_tokens` (Responses) are all accepted. Reasoning tokens count toward the limit.

### OpenAI features

| OpenAI feature                                                                                                                                     | What the exchange does                                                                                                                                       |
| -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `stream`, `stream_options`                                                                                                                         | Applied. Set `stream_options: {"include_usage": true}` to get token usage in a stream.                                                                       |
| `tools`, `tool_choice`                                                                                                                             | Applied. Only an offer that declares tool support can win.                                                                                                   |
| `parallel_tool_calls`, `service_tier`, `include`, `truncation`, `safety_identifier`, `metadata`                                                    | Ignored. For `metadata`, send `x-liquid-tag` to label spend. See [Cost tags](https://inference.ai.exchange/docs/rules.md#cost-tags). An input too long for an offer excludes it.              |
| `response_format`                                                                                                                                  | Applies `json_schema` and `json_object`. Only an offer that declares support can win. See [Structured output](https://inference.ai.exchange/docs/errors.md#structured-output).                |
| `reasoning_effort`, `reasoning.effort` (Responses)                                                                                                 | Applied to offers that support the level, or `unsupported_parameter`.                                                                                        |
| `temperature`                                                                                                                                      | Applied.                                                                                                                                                     |
| `top_p`, `top_k`, `min_p`, `repetition_penalty`, `stop`, `seed`, `logprobs`, `top_logprobs`, `logit_bias`, `frequency_penalty`, `presence_penalty` | Passed to a provider that declares support. If no offer supports one, `unsupported_parameter`. `/v1/responses` refuses `logprobs` and `top_logprobs`.        |
| `n` greater than 1                                                                                                                                 | `400 unsupported_parameter`, `param` is `n`.                                                                                                                 |
| `store`                                                                                                                                            | Ignored. The exchange keeps no response. To limit what a provider keeps, send `exchange.privacy.retention: "none"`. See [Data handling](https://inference.ai.exchange/docs/data-handling.md). |
| `user`                                                                                                                                             | Names the end user for the limits of a [guardrail](https://inference.ai.exchange/docs/guardrails.md#end-users). The `X-Liquid-End-User` header wins.                                          |
| `prompt_cache_key`                                                                                                                                 | Read into the cache plan of the job. See [Prompt caching](https://inference.ai.exchange/docs/caching.md).                                                                                     |
| `previous_response_id`, `conversation`, `background: true` (Responses)                                                                             | `400 unsupported_parameter`. Send the full conversation in `input`.                                                                                          |
| Hosted tool `web_search` (Responses)                                                                                                               | Removed, with `x-liquid-notice: web_search_dropped`. No search runs. Add `:web` (beta) to the model name. See [Quickstart](https://inference.ai.exchange/docs/quickstart.md#web-access).      |
| Other hosted tools: `file_search`, `code_interpreter`, `mcp`                                                                                       | `400 unsupported_parameter`, `param` such as `tools[1]`. Declare your own function.                                                                          |
| Embeddings, image generation, speech and video endpoints                                                                                           | Served. See [Embeddings](https://inference.ai.exchange/docs/embeddings.md), [Modalities](https://inference.ai.exchange/docs/modalities.md) and [Move from the Sora API](https://inference.ai.exchange/docs/move-from-sora.md).                                  |
| Batch, Files, Assistants, Moderations, image edits, fine-tuning (`ft:` names)                                                                      | Not available. Send each request on its own.                                                                                                                 |

### 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` is an OpenAI name that the exchange does not list.                  | Pick a name from `GET /v1/models`.                                                                                      |
| `400 unsupported_parameter`, `param` is `n`                    | The request asks for more than one choice.                                      | Remove `n`.                                                                                                             |
| `400 unsupported_parameter`, `param` is `previous_response_id` | The Responses request asks the exchange to keep state.                          | Send the full conversation in `input`.                                                                                  |
| `400 unsupported_parameter`, `param` is `tools[...]`           | The request has a hosted tool that the exchange does not run.                   | Remove the tool, or declare your own function.                                                                          |
| `400 unsupported_parameter`, `param` is another field          | 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.                                                                                |
| `429 rate_limited`                                             | Too many requests from the address, or a guardrail rate limit refused the call. | Wait for `Retry-After`. OpenAI limits do not apply here. See [Guardrails](https://inference.ai.exchange/docs/guardrails.md#rate-limits).                 |
| `503 provider_failed` or `503 model_unavailable`               | The awarded provider failed, or no seller can serve the model now.              | Set `max_retries` yourself, and send the same `idempotency-key` on a retry. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md#headers). |
