Migrating from OpenRouter
Replace model IDs and review provider preferences, fallback, and costs.
Keep the OpenAI SDK, replace the connection settings and model ID, then review every OpenRouter-specific option before switching traffic.
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
You need Python 3.10+, uv, and a Praxis key. Create one in Console → API 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.
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
| 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
| 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 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. Records may take a few seconds to appear. If the header is absent, check Console → Usage and costs. 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
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. The API reference owns operation and field details; do not assume OpenRouter extensions apply to Responses.
3. Verify one request
First prove the simplest Chat request, with no provider preferences:
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")
PYExpected: 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
| 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
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.