Swap GPT, Claude, Gemini and DeepSeek by Changing One String (Python and Node)
Switch LLM models in Python and Node with one key: GPT-6, Claude, Gemini and DeepSeek via the OpenAI SDK, with error fallback and cost comparison.
You do not need four SDKs to use four model families. Point the official OpenAI SDK at https://kadraris.com/v1 and every chat model in the Kadraris catalog answers to the same client. Changing models means changing one string, say from gpt-6-sol to claude-sonnet-5 or deepseek-v4-flash, and all usage lands on one prepaid balance.
This guide has working Python and Node code for three jobs: picking the model from config, falling back to another model on errors, and comparing several models' answers with their speed and cost. For the very basics of calling non-OpenAI models with the OpenAI SDK, read the OpenAI SDK with any model first.
Setup in three steps
- Create an account and add funds (from $10).
- Make an API key in the dashboard and set a monthly limit for it. The
sk-kd-...key is shown only once. - Store it in an environment variable, never in source code.

pip install openai requests
export KADRARIS_API_KEY=sk-kd-...
export LLM_MODEL=gpt-6-solThe model as a config value
The smallest useful version reads the model id from the environment, so switching requires no code change.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://kadraris.com/v1",
api_key=os.environ["KADRARIS_API_KEY"],
)
MODEL = os.environ.get("LLM_MODEL", "gpt-6-sol")
def ask(prompt: str, model: str = MODEL) -> str:
resp = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "Be concise."},
{"role": "user", "content": prompt},
],
)
return resp.choices[0].message.content
q = "PUT vs PATCH in REST: what is the difference?"
print(ask(q))
print(ask(q, model="claude-sonnet-5"))Start it with LLM_MODEL=deepseek-v4-flash python app.py and the same program talks to DeepSeek. Streaming (stream=True) and tool calls (tools) use one format across models; see the chat completions docs.
What sits behind the single endpoint
Each producer normally brings its own SDK, message shape, error types and billing account. Four producers mean four code paths, four keys and four invoices.
Kadraris API gives you one OpenAI-style endpoint, /v1/chat/completions, for all chat models, and an Anthropic-style /v1/messages for Claude. In code, the model is a config string. On the money side, there is one balance.
A few examples with their rates per 1M tokens (input / output):
- GPT-6 Sol (
gpt-6-sol): $2.16 / $10.80 per 1M tokens - Claude Sonnet 5 (
claude-sonnet-5): $3.24 / $16.20 per 1M tokens - Gemini 3.6 Flash (
gemini-3.6-flash): $0.79 / $3.95 per 1M tokens - DeepSeek V4 Flash (
deepseek-v4-flash): $0.135 / $0.27 per 1M tokens - GPT-6 Luna (
gpt-6-luna): $0.108 / $0.54 per 1M tokens
GLM, Kimi, MiniMax and the others are in the model catalog, on the LLM API page and at GET /v1/models?category=chat.
Falling back to another model
Any producer can have a bad day. Some catalog models already switch between infrastructure providers on their own, but a second safety net in your code helps: move to a different model. The trick is to skip the fallback for errors that a different model cannot fix.
import time
from openai import APIStatusError, APIConnectionError
CHAIN = ["claude-sonnet-5", "gpt-6-sol",
"gemini-3.6-flash"]
# key, balance, key limit, bad input: stop here
NO_RETRY = {401, 402, 403, 422}
def ask_with_fallback(messages, **kw):
last = None
for model in CHAIN:
for attempt in range(2):
try:
r = client.chat.completions.create(
model=model, messages=messages, **kw)
return model, r.choices[0].message.content
except APIStatusError as e:
if e.status_code in NO_RETRY:
raise
last = e
if e.status_code == 404:
break # bad model id, try the next
# 429 or 5xx: wait a bit, retry
time.sleep(2 * (attempt + 1))
except APIConnectionError as e:
last = e
time.sleep(2 * (attempt + 1))
raise last
msgs = [{"role": "user",
"content": "GDPR in 5 bullet points."}]
model, answer = ask_with_fallback(msgs)
print(f"[{model}] {answer}")How the code treats each status:
| Code | Cause | Try another model? |
|---|---|---|
| 401 | key wrong or deleted | no |
| 402 | not enough balance | no, add funds |
| 403 | monthly key limit reached | no, raise it in the dashboard |
| 404 | model id not found | yes, and correct the id |
| 422 | invalid parameters | no, fix the request |
| 429 | too many requests | yes, after a pause |
| 502 | provider error upstream | yes |
A failed request is not billed, so retrying after an error does not charge you twice. All codes: Errors.
Side-by-side test with cost per answer
Before committing a project to a model, I send identical prompts to several models at once and compare quality, response time and cost. Rates come from the public GET /v1/models, token counts from each response's usage.
import os, time
from concurrent.futures import ThreadPoolExecutor
import requests
from openai import OpenAI
BASE = "https://kadraris.com/v1"
client = OpenAI(base_url=BASE,
api_key=os.environ["KADRARIS_API_KEY"])
# public catalog, no key needed
catalog = requests.get(
f"{BASE}/models",
params={"category": "chat", "currency": "USD"},
timeout=30,
).json()["data"]
PRICES = {m["id"]: m["pricing"] for m in catalog}
MODELS = ["gpt-6-sol", "claude-sonnet-5",
"gemini-3.6-flash", "deepseek-v4-flash"]
PROMPT = ("A customer writes: 'My parcel has not "
"arrived for 6 days'. Write a polite "
"3-sentence reply.")
def run(model):
t0 = time.perf_counter()
r = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": PROMPT}],
)
u, p = r.usage, PRICES[model]
cost = (u.prompt_tokens * float(p["input_per_mtok"])
+ u.completion_tokens
* float(p["output_per_mtok"])) / 1_000_000
secs = time.perf_counter() - t0
text = r.choices[0].message.content
return (model, secs, u.prompt_tokens,
u.completion_tokens, cost, text)
with ThreadPoolExecutor(len(MODELS)) as pool:
results = list(pool.map(run, MODELS))
# cheapest answer first
for m, s, tin, tout, c, text in sorted(
results, key=lambda x: x[4]):
print(f"\n=== {m} | {s:.1f}s | "
f"{tin}+{tout} tok | ${c:.5f}")
print(text)Lessons from using this in real projects:
- A single prompt decides nothing. I run 20 to 50 real examples before choosing.
- Reasoning models count their thinking as output, so a short reply can still be token-heavy.
- Models split the same text into different numbers of tokens, so compare the cost of whole requests rather than the rates.
For a ranking based on 1,000 support replies, see the cheapest LLM API in 2026.
Node.js version
npm install openaiimport OpenAI from "openai";
const BASE = "https://kadraris.com/v1";
const client = new OpenAI({
baseURL: BASE,
apiKey: process.env.KADRARIS_API_KEY,
});
const res = await fetch(
`${BASE}/models?category=chat¤cy=USD`);
const prices = Object.fromEntries(
(await res.json()).data.map((m) => [m.id, m.pricing]));
const models = ["gpt-6-luna", "claude-haiku-4.5",
"deepseek-v4-flash"];
const prompt = "3 names for a cafe by a train station.";
const results = await Promise.allSettled(
models.map(async (model) => {
const r = await client.chat.completions.create({
model,
messages: [{ role: "user", content: prompt }],
});
const p = prices[model];
const u = r.usage;
const cost = (u.prompt_tokens * Number(p.input_per_mtok)
+ u.completion_tokens
* Number(p.output_per_mtok)) / 1e6;
return { model, cost: "$" + cost.toFixed(5),
text: r.choices[0].message.content };
}),
);
for (const r of results) {
console.log(r.status === "fulfilled"
? r.value : r.reason.message);
}With Promise.allSettled, one failing model does not kill the whole comparison. Mind the option name: baseURL in Node, base_url in Python.
Keeping Anthropic SDK code as it is
Code written for the Anthropic SDK, with tool_use blocks or prompt caching for example, can stay. /v1/messages serves Claude in its own format:
from anthropic import Anthropic
claude = Anthropic(
base_url="https://kadraris.com",
api_key=os.environ["KADRARIS_API_KEY"],
)
msg = claude.messages.create(
model="claude-opus-5.5",
max_tokens=1024,
messages=[{"role": "user",
"content": "Table schema for booking "
"meeting rooms?"}],
)
print(msg.content[-1].text)The Anthropic SDK gets the base URL without /v1. This endpoint is for Claude only. If one app mixes Claude with GPT and Gemini, sticking to the OpenAI format everywhere is easier. Claude costs and caching: Claude Opus 5.5 API pricing. Endpoint details: Anthropic Messages docs.
Differences that bite after a switch
The request format is shared; the models are not. The ones I run into most:
- Sampling parameters. Claude Sonnet 5 returns a 400 error for non-default
temperatureortop_p, and OpenAI says GPT-6 Astra does not take them either. If a request has to work on any model, drop both. - Output budget on reasoning models. Thinking eats into
max_tokens. Set it too low and the answer comes back cut off or empty. Leave it unset at first, or give plenty of room. - Limits. On Kadraris, Gemini 3.6 Flash can write at most 8,192 output tokens, and Claude Haiku 4.5 has a 200K context rather than 1M.
GET /v1/modelslistscontext_windowandmax_output_tokens. - Tone and length. One system prompt gives different styles on different models. Read a dozen answers before you ship a switch.

No-code tools work too: n8n, Make and Zapier accept the same key with base URL https://kadraris.com/v1, see the no-code docs. GPT-6 tiers and prices: GPT-6 API pricing.
Questions
Does streaming work the same way for Claude and Gemini through this endpoint?
Yes. stream=True uses the standard Chat Completions chunk format for every model, so one streaming handler covers them all.
Which errors should never trigger a fallback?
401, 402, 403 and 422. They point to the key, the balance, the key's limit or the request itself, and another model would fail the same way.
How do I find the context size of each model?
Call GET /v1/models?category=chat. Each entry has context_window and max_output_tokens, and the endpoint is public.
Do I need separate accounts with OpenAI, Anthropic and Google?
No. One Kadraris key and one balance reach every model in the catalog, including image, video and music models.


























