Migrating an existing integration
One line changes. Nothing downstream does.
The migration is a base URL and a model id. Everything the SDK hands back afterwards — the streaming frames, the usage block, the tool-call structure, the error envelope — has the shape your code already parses, which is why this is an afternoon rather than a sprint.
Priced 36–42% below the model publisher's own rate, with both rates printed on the same row.
- No platform fee
- OpenAI SDK compatible
- Pay as you go
- 7 models callable today
The objection is effort, not price
Nobody stays on an expensive rate card because they like it. They stay because the integration works, the retries are tuned, the error handling has been beaten into shape by production, and none of that is written down anywhere except in the code. The cost of switching is not the switch — it is the risk that something subtle in the response shape breaks a path that only fires at three in the morning.
So the design constraint here was compatibility rather than cleverness. This is an OpenAI-compatible endpoint: the request body is the same body, the streaming response is the same sequence of server-sent events terminated the same way, the usage block appears in the same place with the same field names, and errors come back in the same envelope with codes your handler can branch on. Any framework built on the OpenAI SDK — which is most of them — inherits that compatibility without knowing anything about us.
What follows is the honest version of the migration: the line that changes, the things you should check before you trust it, and the specific behaviours that are not identical. A migration guide that claims everything is identical is a migration guide that gets found out during the cutover.
What the one line is worth
Effort is only half of the decision; the other half is what the effort buys. Every model below is priced under its own publisher's list rate, and the gap is on the row rather than in a footnote. DeepSeek V4 Flash runs $0.090/M input and $0.18/M output against a published $0.14/M and $0.28/M.
Read the output column first if you generate more than you read, and the input column first if you summarise, classify or retrieve. The spread between models in this table is wider than the spread between vendors for any one model, which is the argument for making the switch cheap enough that you can afford to try more than one.
| Model | Input | Output | Cached input | Publisher’s rate | Below publisher |
|---|---|---|---|---|---|
| Kimi K2.6 | $0.55/M | $2.30/M | $0.11/M | $0.95 / $4.00/M | 42% |
| Kimi K3 | $1.85/M | $9.00/M | $0.37/M | $3.00 / $15.00/M | 40% |
| GLM-5.2 | $0.82/M | $2.55/M | $0.16/M | $1.40 / $4.40/M | 42% |
| DeepSeek V4 Pro | $0.28/M | $0.55/M | $0.055/M | $0.43 / $0.87/M | 37% |
| DeepSeek V4 Flash | $0.090/M | $0.18/M | $0.018/M | $0.14 / $0.28/M | 36% |
Cached input is billed automatically when a prefix repeats — no header, no flag, no plan. It is not in this table, so a repeated-prefix workload pays less than the figures above suggest.
Price the switch before you make it
If you are building the case for spending an afternoon on this, the number that wins the argument is a monthly figure rather than a per-token one. Put your volume in and the calculator prices it across every model we serve.
The result is shareable as a URL, which exists specifically because the person who approves the change is usually not the person reading this page. Send the link rather than a screenshot of it.
An agent or chat loop resends the same system prompt on every turn. That repeated prefix is billed at the cached rate, and on most models that is a fifth of the standard input rate. Set this to zero if every request you send is different.
That is $7.00 a month, or $84.00 a year, on the same tokens through the same model.
An estimate, not a quote — but it is computed from the same rate table the API bills from, so the only assumptions in it are the ones you set above. Third-party figures are those providers’ own published rates; where a provider does not publish one, the cell is blank rather than guessed.
The actual diff
Three changes, in the order to make them. The first is the only one that touches code you did not write yourself.
- 1
Base URL
Set the client's base URL to https://router.xark.io/api/v1. In the OpenAI SDK that is a constructor argument in every language binding; in a framework it is usually one configuration value. Nothing else about the client changes, including how you pass the key.
- 2
Model id
Ids are namespaced by publisher — moonshotai/kimi-k3, z-ai/glm-5.2, deepseek/deepseek-v4-flash. The bare model name is also accepted, so an integration that predates the namespacing keeps resolving. Unknown ids fail with a specific error rather than a substitution.
- 3
Shadow, then cut over
Run both clients against a slice of live traffic, diff the outputs on your own evaluation set, and compare cost per request in the dashboard. Cut over when the comparison holds. Rolling back is the same one line in reverse, which is the property that makes this safe to try.
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://router.xark.io/api/v1" # ← the only line that changes
)
response = client.chat.completions.create(
model="moonshotai/kimi-k3",
messages=[{"role": "user", "content": "Hello"}]
)
Everything after that line is unchanged: the same method, the same message array, the same streaming iterator, the same usage fields on the response.
What is identical, and what is not
The compatible surface is the one your code touches on every request. The differences are real but small, and all of them are things you would rather read here than find in an incident.
- Identical: request and response shape
- Chat completions take the same body and return the same object, including the choices array, finish reasons and the usage block. Streaming emits the same server-sent event sequence and the same terminating frame.
- Identical: tool and function calling
- Tool definitions, tool_choice, and the assistant message carrying tool calls all match the shape the SDK expects, so an agent loop written against OpenAI runs unmodified.
- Identical: error envelope
- Errors return the same nested error object with a type and a code. Rate limiting comes back as a 429 with rate_limit_exceeded, which is deliberately distinct from the 429 with insufficient_credits you get when the balance is empty.
- Different: the model catalogue
- Open-weight models only, 7 callable of 24 catalogued. If your code names a closed model, that path has no destination here and has to stay where it is.
- Different: no automatic fallback
- We do not retry a failed request against a different model. Substituting a model changes the output, the price and the licence terms of what you receive, so the request fails with an honest status your code can branch on instead.
- Different: no bring-your-own-key
- Routing your own negotiated provider contract through us is designed and on the roadmap but not shipped. Today every request bills against your prepaid balance at the published rate.
The rollback plan, written before the migration
The reason a one-line migration is safe is not that it always works. It is that it is symmetrical: reverting is the same edit in the other direction, with no schema to unwind, no data to move and no contract to exit. Keep the old client construction behind whatever configuration switch you already use for environments, and the rollback is a deploy rather than a project.
Keep the old provider's key valid for a billing cycle after the cutover. It costs nothing on a pay-as-you-go account and it removes the only genuinely irreversible step from the plan.
Compatibility questions
The specific ones, answered specifically. Vague reassurance about compatibility is how a migration goes wrong.
Does the official OpenAI SDK work unmodified?
What about LangChain, LlamaIndex or the Vercel AI SDK?
Does streaming behave the same way?
Do function and tool calls work?
Is the usage block on the response?
What do error responses look like?
How do I map my current model ids?
What happens if I request a model you do not serve?
Can I run both providers at once during the migration?
How do I roll back?
Are there rate limits I should design around?
Will the outputs be identical to what I get today?
How long does this actually take?
Try it on one service before you commit anything
The change is symmetrical and the account is pay as you go, so the worst case is an afternoon and a rollback deploy.
We guarantee at least 20% below the model publisher's own rate on every model we serve. The smallest discount in the catalogue today is 36%, so the guarantee has room in it by design.
Related: Migration guide · API reference · Model catalogue