Migrate from OpenAI
Move an OpenAI client to Cruxy: change the base URL and the key, pass a tier as the model, and know what carries over and what differs.
Cruxy serves OpenAI-compatible chat completions at POST /v1/chat/completions
and an OpenAI-compatible model list at GET /v1/models. Both take OpenAI's
request and return OpenAI's response, so code written for the official openai npm package moves
over with three changes: the base URL, the API key, and model.
Change three settings
| Setting | Set it to |
|---|---|
| Base URL | https://api.cruxy.ai/v1 |
| API key | Your Cruxy API key, read from CRUXY_API_KEY. It travels as Authorization: Bearer <key>, as on OpenAI. |
model | A Cruxy tier, kavi, vaani, or mira, or auto to let the router pick one. |
The key needs the chat endpoint family, the same as for
POST /api/v1/chat; a key without it is 403. On a server, use a developer
API key from the Cruxy Console:
Get your API key covers the kinds of key and
how to create one. Authentication, rate limits, and billing work as they do on
POST /api/v1/chat.
Before and after
The same request, first to OpenAI, then to Cruxy. Only the base URL, the key,
and model change; the request body and the response you read stay the same.
# OPENAI_MODEL is the OpenAI model id you call today.
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "'"$OPENAI_MODEL"'",
"messages": [
{ "role": "user", "content": "Hello" }
]
}'curl https://api.cruxy.ai/v1/chat/completions \
-H "Authorization: Bearer $CRUXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kavi",
"messages": [
{ "role": "user", "content": "Hello" }
]
}'import OpenAI from 'openai'
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
const completion = await client.chat.completions.create({
// The OpenAI model id you call today.
model: process.env.OPENAI_MODEL ?? '',
messages: [{ role: 'user', content: 'Hello' }],
})
console.log(completion.choices[0].message.content)import OpenAI from 'openai'
const client = new OpenAI({
apiKey: process.env.CRUXY_API_KEY,
baseURL: 'https://api.cruxy.ai/v1',
})
const completion = await client.chat.completions.create({
model: 'kavi',
messages: [{ role: 'user', content: 'Hello' }],
})
console.log(completion.choices[0].message.content)Pass a tier as model
model takes a tier or auto. Any other value, an OpenAI model id included,
is 400 invalid_request. These docs map no OpenAI model to a tier: the model
behind a tier is never exposed, and it can change while the tier stays the
same. Choose a tier by its profile, then test it on your own prompts.
model | Profile | Default reasoning_effort | max_tokens cap |
|---|---|---|---|
kavi | Deep | high | 8192 |
vaani | Balanced | low | 4096 |
mira | Fast | none | 2048 |
auto | The router picks a tier for each request. | Chosen per request | The cap of the tier that serves |
The response's model is the tier that served the request. With auto, it is
the tier the router chose, which a budget can also have downgraded, so read it
rather than assume it. Choosing a tier
compares the tiers, and Auto routing covers what
the router does.
GET /v1/models returns OpenAI's list shape,
{"object": "list", "data": [...]}, and its ids are the tiers. For each
tier's context window, output cap, tool support, and reasoning defaults, read
GET /api/v1/models; see Models.
What carries over unchanged
- The request.
messageswith thesystem,user,assistant, andtoolroles, plustemperature,top_p,max_tokens,stream,stream_options,tools,tool_choice,response_format, andreasoning_effort. - The response. A
chat.completionobject withid,object,created,model,choices[].message,choices[].finish_reason(stop,tool_calls,length, orcontent_filter), andusagewithprompt_tokens,completion_tokens, andtotal_tokens. - Streaming.
chat.completion.chunkframes that end withdata: [DONE], and a usage frame only when you setstream_options.include_usage. - Tool calling.
toolsandtool_choiceon the request,tool_callson the assistant message, and atoolmessage withtool_call_idfor each result. - Errors. OpenAI's envelope,
{"error": {"message", "type", "param", "code"}}, which the SDK raises as anAPIError.
What differs
modelis a tier, and the response'smodelnames the tier that served.- Fields Cruxy does not implement are ignored, not rejected:
n,stop,seed,presence_penalty,frequency_penalty,logit_bias,logprobs,user, and the rest. A request that sends them still runs, and they have no effect. max_tokensis clamped to the tier's cap, and thinking spends it.kavithinks by default, so a smallmax_tokensonkavican end withfinish_reason: "length"and empty content.reasoning_efforttakesnone,low,medium, orhigh. Any other value, OpenAI'sminimalincluded, is400invalid_request.- A non-streaming call has a deadline: 90 seconds on
kaviandvaani, 45 onmira. Past it, the call is504upstream_timeout. - An error's
codeis a Cruxy code, andparamis alwaysnull. - The route carries none of the Cruxy-native chat features: no memory, no
standing rules, no vaults, no
file_ids, and nocruxyblock. For those, callPOST /api/v1/chat; see Chat. - The request ID is only in a header,
X-Request-Id, and the response'susagecarries no cost. - A tool call's
idmay not start withcall_. Treat it as opaque.
OpenAI SDK compatibility covers each of these parameter by parameter, with streaming, errors, and how the SDK retries.
Next steps
- OpenAI SDK compatibility: what the route supports, ignores, and rejects.
- Choosing a tier: pick the tier to pass as
model. - Reasoning effort: keep thinking from spending your
max_tokens. - Known limitations: the limits to plan around.