PL Log inOpen Studio
Preview version. Model catalog, prices, calculator and docs are live. Generation goes live at launch.

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

bash
pip install openai
export KADRARIS_API_KEY=sk-kd-...
python
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:

python
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

bash
npm install openai
javascript
import 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:

bash
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 idMakerGood fitPrice
claude-opus-5.5Anthropichard reasoning, long docs, tricky code$3.78 / $18.90 per 1M tokens
claude-sonnet-5Anthropicdaily coding and writing$3.24 / $16.20 per 1M tokens
gpt-6-solOpenAIcoding, agents, mid-range cost$2.16 / $10.80 per 1M tokens
gpt-6-lunaOpenAIbulk extraction, tagging, summaries$0.108 / $0.54 per 1M tokens
gemini-3.6-flashGooglecoding, short answers over long input$0.79 / $3.95 per 1M tokens
deepseek-v4-flashDeepSeeklow-cost long-context jobs$0.135 / $0.27 per 1M tokens
glm-5.2Z.aianother 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:

python
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.content

Treat 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:

python
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:

CodeWhat it meansRetry or move on?
401key is wrongstop
402balance too lowstop, top up
404model id unknownstop, fix the id
422bad parametersstop, fix the request
429rate limitedretry with backoff
502provider errorretry, 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:

python
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:

python
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:

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.