# Migrating from OpenRouter (/en/docs/cookbook/migration/from-openrouter)



Keep the OpenAI SDK, replace the connection settings and model ID, then review every OpenRouter-specific option before switching traffic.

```diff
 client = OpenAI(
-    base_url="https://openrouter.ai/api/v1",
-    api_key=os.environ["OPENROUTER_API_KEY"],
+    base_url="https://api.deepraxis.com/openai/v1",
+    api_key=os.environ["PRAXIS_API_KEY"],
 )
-model = "openai/gpt-4o"
+model = "deepseek-flash"
```

## 1. Prepare [#1-prepare]

You need Python 3.10+, [uv](https://docs.astral.sh/uv/getting-started/installation/), and a Praxis key. Create one in [Console → API Keys](https://console.deepraxis.ai/keys) and securely inject it into the current terminal as `PRAXIS_API_KEY`. Never put the key in code or logs. Keep your original service configuration and key for rollback. Run the commands below in Bash on macOS or Linux.

```bash
test -n "${PRAXIS_API_KEY:-}"
```

Expected: exit code 0. Exit code 1 means the variable is missing; set it before continuing.

## 2. Replace model IDs [#2-replace-model-ids]

| OpenRouter selection     | Praxis selection                                                                           |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| `openai/gpt-4o`          | Start with `deepseek-flash`; this is a different model, not an alias                       |
| `vendor/model`           | Copy the exact ID from the Praxis catalog; do not assume removing the prefix is sufficient |
| An ID ending in `:nitro` | Select a catalog ID without the suffix; the suffix is unsupported                          |
| `openrouter/auto`        | Choose a specific model, or configure a `models` fallback list                             |

Expected: `client.models.list()` includes your chosen ID. All entries in a fallback list must be valid Praxis IDs offering the operation you call.

## Behavior differences [#behavior-differences]

| OpenRouter option                                    | Migration action                                                                                                                                                                                           |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider.order`, `provider.only`, `provider.ignore` | Available only with your own upstream key (BYOK). With platform credentials, remove them or receive `400 provider_selection_not_allowed`                                                                   |
| `provider.allow_fallbacks`, `provider.sort`          | Have no effect; remove them and reassess any routing assumptions                                                                                                                                           |
| `provider.data_collection`, `provider.zdr`           | Have no effect; do not treat accepted requests as enforcement of these policies                                                                                                                            |
| `models`                                             | A model fallback list is available; replace every ID and verify the returned `model`                                                                                                                       |
| `provider.require_parameters: true`                  | Checks support for tool parameters and declared sampling parameters; returns 400 when a requested parameter is not declared as supported. Unsupported sampling parameters are otherwise ignored by default |

The parameter check covers tool parameters and declared sampling parameters, not arbitrary provider features. See [Chat Completions field support](/en/docs/api/compatible/openai/createChatCompletion) for the precise scope. If your application requires a data policy controlled by an ignored option, keep it on the original service until you have separately confirmed the required policy.

Chat's `usage.cost` is the amount charged; Responses and Messages currently omit it. When `X-Praxis-Generation-Id` is present, use `GET /model/v1/generation?id=GENERATION_ID` as described in the [generation record API](/en/docs/api/native/model/getGeneration). Records may take a few seconds to appear. If the header is absent, check [Console → Usage and costs](https://console.deepraxis.ai/usage). Neither the response body's `id` nor `X-Request-Id` is a substitute for a generation ID. Missing cost does not mean zero cost.

## Unsupported operations and alternatives [#unsupported-operations-and-alternatives]

`vendor/model`, `:nitro`, and `openrouter/auto` are not supported model selectors. There is no automatic mapping. For absent OpenAI endpoints, use the [unsupported endpoint table and alternatives](/en/docs/cookbook/migration/from-openai#unsupported-endpoints-and-alternatives). The [API reference](/en/docs/api/compatible/openai) owns operation and field details; do not assume OpenRouter extensions apply to Responses.

## 3. Verify one request [#3-verify-one-request]

First prove the simplest Chat request, with no provider preferences:

```bash
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy
uv run -q --no-project --with openai --with anthropic python - <<'PY'
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.deepraxis.com/openai/v1",
    api_key=os.environ["PRAXIS_API_KEY"],
    timeout=90.0,
    max_retries=0,
)
models = client.models.list()
assert "deepseek-flash" in {m.id for m in models.data}
raw = client.chat.completions.with_raw_response.create(
    model="deepseek-flash",
    messages=[{"role": "user", "content": "Reply OK."}],
    max_tokens=1024,
)
reply = raw.parse()
text = reply.choices[0].message.content
print("HTTP", raw.status_code)
print("X-Request-Id:", raw.headers.get("x-request-id"))
print("X-Praxis-Generation-Id:", raw.headers.get("x-praxis-generation-id"))
print(text)
assert raw.status_code == 200 and text and text.strip()
assert raw.headers.get("x-request-id")
PY
```

Expected: exit code 0, `HTTP 200`, nonempty text (usually `OK.`), and an `X-Request-Id`. Assertions check both model availability and a real reply, not just connectivity. Keep the request ID for troubleshooting, never the key. The example incurs a small usage charge.

Then add back only the options your application needs, one at a time. Expected: each request succeeds and returns the intended model; an ignored field is not proof that a policy was applied.

## Failures [#failures]

| Observed error                       | Action                                                                                                    |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `400 provider_selection_not_allowed` | Remove provider selection when using platform credentials, or configure BYOK before selecting an upstream |
| `404` with `does not exist`          | Replace the namespaced ID, suffix, or automatic selector with a catalog ID                                |
| `404 model_protocol_not_offered`     | Choose a model offering the requested operation                                                           |

## 4. Roll back [#4-roll-back]

Restore `https://openrouter.ai/api/v1`, the original `OPENROUTER_API_KEY`, your original model ID, and your saved OpenRouter options. Restart the client and run the original smoke request. Do not send the Praxis key to OpenRouter.

Expected: the original request returns usable text with the intended model. Any calls already charged on Praxis remain charged.

[API reference →](/en/docs/api/compatible/openai) · [Cookbook: OpenAI SDK →](/en/docs/cookbook/sdk/openai-sdk)
