Use the OpenAI SDK with Claude, Gemini, DeepSeek and GPT
Keep the OpenAI Python or Node SDK and call Claude, Gemini, DeepSeek, GLM and GPT by changing base_url. Streaming, routing, fallback and LangChain.
The OpenAI SDK does not care which company trained the model on the other end. It sends a Chat Completions request to whatever base_url you give it. Point it at https://kadraris.com/v1, use a sk-kd-... key, and the model field can be claude-sonnet-5, gemini-3.6-flash, deepseek-v4-flash or any other chat model in the catalog. Responses come back in the usual OpenAI shape, streaming included.
This guide has the setup for Python, Node and curl, then the two patterns that make the switch useful: picking a model per task and falling back to another model when one fails.
Setup in Python
pip install openai
export KADRARIS_API_KEY=sk-kd-...import os
from openai import OpenAI
client = OpenAI(
base_url="https://kadraris.com/v1",
api_key=os.environ["KADRARIS_API_KEY"],
)
models = ["claude-sonnet-5", "gemini-3.6-flash",
"deepseek-v4-flash", "gpt-6-luna"]
for model in models:
resp = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "Reply in one sentence."},
{"role": "user", "content": "What does a 304 status mean?"},
],
max_tokens=120,
)
print(f"{model}: {resp.choices[0].message.content}")One client object, four model families. Nothing changes between calls except the model string.
Streaming
Set stream=True and read chunks the same way you would with an OpenAI model:
stream = client.chat.completions.create(
model="claude-opus-5.5",
messages=[{"role": "user",
"content": "Explain eventual consistency simply."}],
stream=True,
)
for chunk in stream:
if not chunk.choices:
continue
text = chunk.choices[0].delta.content
if text:
print(text, end="", flush=True)Setup in Node and TypeScript
npm install openaiimport OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://kadraris.com/v1",
apiKey: process.env.KADRARIS_API_KEY,
});
const resp = await client.chat.completions.create({
model: "gpt-6-sol",
messages: [{ role: "user",
content: "Write a regex for UK postcodes." }],
});
console.log(resp.choices[0].message.content);
const stream = await client.chat.completions.create({
model: "gemini-3.6-flash",
messages: [{ role: "user",
content: "Five weekend projects for an Arduino." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}One small trap: Node spells it baseURL, Python spells it base_url.
No SDK at all: curl
Useful for debugging, and for tools that only let you enter a URL and headers:
curl https://kadraris.com/v1/chat/completions \
-H "Authorization: Bearer $KADRARIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [{"role": "user",
"content": "Three-line summary of Macbeth."}]
}'Which model ids to start with
| Model id | Maker | Good fit | Price |
|---|---|---|---|
claude-opus-5.5 | Anthropic | hard reasoning, long docs, tricky code | $3.78 / $18.90 per 1M tokens |
claude-sonnet-5 | Anthropic | daily coding and writing | $3.24 / $16.20 per 1M tokens |
gpt-6-sol | OpenAI | coding, agents, mid-range cost | $2.16 / $10.80 per 1M tokens |
gpt-6-luna | OpenAI | bulk extraction, tagging, summaries | $0.108 / $0.54 per 1M tokens |
gemini-3.6-flash | coding, short answers over long input | $0.79 / $3.95 per 1M tokens | |
deepseek-v4-flash | DeepSeek | low-cost long-context jobs | $0.135 / $0.27 per 1M tokens |
glm-5.2 | Z.ai | another option for code and chat | $1.22 / $4.05 per 1M tokens |
Details for each: Claude Opus 5.5, Claude Sonnet 5, GPT-6 Sol, GPT-6 Luna, Gemini 3.6 Flash, DeepSeek V4 Flash, GLM-5.2. The current list is always returned by GET /v1/models.
Pick the model per task
Once the model is just a string, you can choose it by job type instead of by which SDK is installed. A small lookup table is enough:
ROUTES = {
"extract": "gpt-6-luna", # many small structured jobs
"classify": "deepseek-v4-flash",
"draft": "claude-sonnet-5",
"code": "gpt-6-sol",
"review": "claude-opus-5.5", # errors here are costly
}
def ask(kind, prompt, **kw):
resp = client.chat.completions.create(
model=ROUTES[kind],
messages=[{"role": "user", "content": prompt}],
**kw,
)
return resp.choices[0].message.contentTreat this table as a starting point, not a verdict. Build your own small test set: 20 to 50 real inputs, run through two or three candidates, and weigh output quality against price per 1M tokens. Moving a task to another model is a one-line change.
Fall back when a model has a bad day
Rate limits and upstream errors happen. A short chain of models keeps your app responding:
import time
from openai import APIStatusError, APIConnectionError
CHAIN = ["claude-sonnet-5", "gpt-6-sol", "gemini-3.6-flash"]
NO_RETRY = (401, 402, 422)
def chat_with_fallback(messages, **kw):
last_err = None
for model in CHAIN:
for attempt in range(2):
try:
resp = client.chat.completions.create(
model=model, messages=messages, **kw)
return model, resp.choices[0].message.content
except APIStatusError as e:
last_err = e
if e.status_code in NO_RETRY:
raise # fix config or balance first
time.sleep(1.5 * (attempt + 1))
except APIConnectionError as e:
last_err = e
time.sleep(1.5 * (attempt + 1))
raise last_err
model, answer = chat_with_fallback(
[{"role": "user", "content": "A haiku about retries."}])
print(model, answer)Why some codes skip the retry:
| Code | What it means | Retry or move on? |
|---|---|---|
| 401 | key is wrong | stop |
| 402 | balance too low | stop, top up |
| 404 | model id unknown | stop, fix the id |
| 422 | bad parameters | stop, fix the request |
| 429 | rate limited | retry with backoff |
| 502 | provider error | retry, then next model |
Note that 404 is not in NO_RETRY in the code above. If one id in your chain is misspelled, the loop simply moves on to the next model. Add 404 to the tuple if you prefer a hard stop.
Already on the Anthropic SDK?
No need to rewrite. Kadraris also accepts the Anthropic Messages format at /v1/messages:
import os
from anthropic import Anthropic
client = Anthropic(
base_url="https://kadraris.com",
api_key=os.environ["KADRARIS_API_KEY"],
)
code = '''
def find(cur, name):
cur.execute("SELECT * FROM t WHERE n='%s'" % name)
'''
msg = client.messages.create(
model="claude-opus-5.5",
max_tokens=512,
messages=[{"role": "user",
"content": "Spot the security bug:" + code}],
)
print(msg.content[0].text)Here the base URL has no /v1, because the Anthropic SDK adds the path on its own. Claude Code connects the same way, see Claude Code with a custom base URL.
LangChain, n8n and other tools
Anything built on the OpenAI client lets you set a base URL.
LangChain:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="claude-sonnet-5",
base_url="https://kadraris.com/v1",
api_key=os.environ["KADRARIS_API_KEY"],
)
print(llm.invoke("Name three hash functions.").content)n8n, Make, Zapier and Cursor: choose the OpenAI credential or node, enter https://kadraris.com/v1 as the base URL, paste your sk-kd-... key, and type the model id by hand if the dropdown does not list it. The no-code docs cover n8n, Make and Zapier screen by screen.
Cost and balance
Chat is billed per 1M input and output tokens at the rates in the table above and on the pricing page. Everything draws from one prepaid balance with no subscription and no expiry. The minimum top-up is $10. Prompts and outputs are not used for training.
The same key and balance also run image, video and music models, explained in Chat, Images, Video and Music Behind a Single API Key.
Balance check from Python:
import requests
r = requests.get(
"https://kadraris.com/v1/balance",
headers={"Authorization":
f"Bearer {os.environ['KADRARIS_API_KEY']}"},
)
print(r.json())Endpoint reference: /docs/chat.
Questions
Will my existing OpenAI code break if I change base_url?
Usually not. The request and response follow the Chat Completions format, so message lists, max_tokens, stream and choices parsing stay the same. The parts to check are model ids and any parameter a given model does not accept, which returns 422.
Can one script mix Claude and Gemini calls?
Yes. Use a single client and pass a different model on each call. The routing table above is exactly that.
What happens when I run out of balance during a fallback chain?
The API returns 402, and the fallback code re-raises it instead of trying the next model, because every model draws from the same balance. Top up and run again.
Does Cursor or n8n need a plugin for this?
No. Both accept a custom OpenAI base URL. Enter the Kadraris URL and key, then type the model id.


























