Call Claude, Gemini, DeepSeek and GPT with the OpenAI SDK
Use the OpenAI Python and Node SDKs to call Claude, Gemini, DeepSeek, GLM and GPT by changing base_url. Streaming, fallbacks, cost routing and tools.
You can call Claude, Gemini, DeepSeek, GLM and GPT models with the official OpenAI SDK by pointing it at an OpenAI-compatible endpoint: set base_url="https://jantaris.com/v1", use your sk-jt-... key, and pass a model id like claude-sonnet-5 or gemini-3.8-flash. The request and response format stays the standard Chat Completions shape, including streaming, so existing code keeps working.
I moved several of my own projects to this setup so I could swap models without rewriting clients. Here is the code for Python and Node, plus the patterns that made it worth it: per-task model routing and automatic fallback.
Why one SDK for every chat model
Every major vendor ships its own SDK. They are all good, and all different: message formats, streaming events, error classes, retry logic. Supporting three vendors means three code paths.
The OpenAI Chat Completions format became the lingua franca. Most tools (Cursor, n8n, Make, LangChain, LlamaIndex, countless internal scripts) can talk to it. If one endpoint serves every model in that format, the model becomes a string you change in config.
With Jantaris that endpoint is https://jantaris.com/v1/chat/completions, and the same key and prepaid balance also cover image, video and music models. More on that in One API for image, video, music and chat models.
Models you can call through the OpenAI SDK
| Model id | Vendor | What I use it for | Price |
|---|---|---|---|
claude-opus-5.5 | Anthropic | Hard reasoning, long documents, complex code | $5.40 / $27.00 per 1M tokens |
claude-sonnet-5 | Anthropic | Everyday coding and writing | $3.24 / $16.20 per 1M tokens |
gpt-6-sol | OpenAI | Coding and agent workflows, mid-tier cost | $2.16 / $10.80 per 1M tokens |
gpt-6-luna | OpenAI | High-volume extraction, classification, summaries | $0.108 / $0.54 per 1M tokens |
gemini-3.8-flash | Fast general work, coding, multi-step tasks | ||
deepseek-v4.1-flash | DeepSeek | Cheap long-context work | $0.54 / $1.75 per 1M tokens |
glm-5.3 | Z.ai | Alternative for coding and general chat |
Model pages with full details: Claude Opus 5.5, Claude Sonnet 5, GPT-6 Sol, GPT-6 Luna, gemini-3.8-flash, DeepSeek V4.1 Flash, glm-5.3. The live catalog is always at GET /v1/models.
Python: the OpenAI SDK with a custom base_url
pip install openai
export AI_API_KEY=sk-jt-...import os
from openai import OpenAI
client = OpenAI(
base_url="https://jantaris.com/v1",
api_key=os.environ["AI_API_KEY"],
)
for model in ["claude-sonnet-5", "gemini-3.8-flash", "deepseek-v4.1-flash", "gpt-6-luna"]:
resp = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "Answer in one sentence."},
{"role": "user", "content": "What is idempotency in HTTP APIs?"},
],
max_tokens=120,
)
print(f"{model}: {resp.choices[0].message.content}")Same client, four vendors. The only thing that changes is model.
Streaming responses
stream=True works as it does with OpenAI:
stream = client.chat.completions.create(
model="claude-opus-5.5",
messages=[{"role": "user", "content": "Explain CAP theorem with a real example."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content if chunk.choices else None
if delta:
print(delta, end="", flush=True)Node.js and TypeScript
npm install openaiimport OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://jantaris.com/v1",
apiKey: process.env.AI_API_KEY,
});
const resp = await client.chat.completions.create({
model: "gpt-6-sol",
messages: [{ role: "user", content: "Write a regex that matches ISO 8601 dates." }],
});
console.log(resp.choices[0].message.content);
const stream = await client.chat.completions.create({
model: "gemini-3.8-flash",
messages: [{ role: "user", content: "List five uses for a Raspberry Pi." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}Note the Node SDK uses baseURL, the Python SDK uses base_url.
Plain HTTP with curl
No SDK needed. This is handy for debugging and for tools that only let you set a URL and headers:
curl https://jantaris.com/v1/chat/completions \
-H "Authorization: Bearer sk-jt-..." \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"messages": [{"role": "user", "content": "Summarize the plot of Hamlet in 3 bullet points."}]
}'Route each task to the right model
This is where one endpoint pays off. I keep a small routing table and pick the model by task type, not by which SDK happens to be installed:
ROUTES = {
"extract": "gpt-6-luna", # structured fields from text, high volume
"classify": "deepseek-v4.1-flash",
"draft": "claude-sonnet-5",
"code": "gpt-6-sol",
"review": "claude-opus-5.5", # when mistakes are expensive
}
def ask(task_type, prompt, **kw):
return client.chat.completions.create(
model=ROUTES[task_type],
messages=[{"role": "user", "content": prompt}],
**kw,
).choices[0].message.contentThe table above is my current setup, not a law. Run your own evaluations: take 20 to 50 real examples, send them through two or three candidate models, and compare quality against cost per 1M tokens. Changing the route is a one-line edit.
Automatic fallback between models
Providers have bad days. A fallback chain keeps your app answering when one model returns a server error or you hit a rate limit:
import time
from openai import APIStatusError, APIConnectionError
FALLBACK = ["claude-sonnet-5", "gpt-6-sol", "gemini-3.8-flash"]
def chat_with_fallback(messages, **kw):
last_err = None
for model in FALLBACK:
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 (401, 402, 422):
raise # config or balance problem, switching models will not help
time.sleep(1.5 * (attempt + 1)) # 429 and 5xx: retry, then next model
except APIConnectionError as e:
last_err = e
time.sleep(1.5 * (attempt + 1))
raise last_err
model, answer = chat_with_fallback([{"role": "user", "content": "Give me a haiku about queues."}])
print(model, answer)Jantaris already fails over between infrastructure providers for part of the catalog, so the same model can keep answering when one source has trouble. The chain above adds a second layer: switching to a different model altogether.
What each status code means in this context:
| Code | Meaning | Retry? |
|---|---|---|
| 401 | Invalid key | No |
| 402 | Balance too low | No, top up first |
| 404 | Unknown model id | No, fix the id |
| 422 | Invalid parameters | No, fix the request |
| 429 | Rate limited | Yes, with backoff |
| 502 | Upstream provider error | Yes, or fall back |
Using the Anthropic SDK instead
If your code already uses the Anthropic SDK, you do not need to switch. Jantaris also serves the Anthropic Messages format at /v1/messages:
import os
from anthropic import Anthropic
client = Anthropic(base_url="https://jantaris.com", api_key=os.environ["AI_API_KEY"])
snippet = """
def get_user(cur, user_id):
cur.execute("SELECT * FROM users WHERE id = '%s'" % user_id)
"""
msg = client.messages.create(
model="claude-opus-5.5",
max_tokens=512,
messages=[{"role": "user", "content": "Review this code for SQL injection risks:" + snippet}],
)
print(msg.content[0].text)Note the base URL without /v1: the Anthropic SDK appends the path itself. This is also how Claude Code connects, covered in Use Claude Code with a custom base URL and prepaid balance.
Frameworks and no-code tools
Anything built on the OpenAI client accepts a base URL. Two common ones:
LangChain
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="claude-sonnet-5",
base_url="https://jantaris.com/v1",
api_key=os.environ["AI_API_KEY"],
)
print(llm.invoke("Name three sorting algorithms.").content)n8n, Make, Zapier, Cursor
Use the OpenAI credential or node, set the base URL to https://jantaris.com/v1, paste the sk-jt-... key, and type the model id by hand if it is not in the dropdown. The no-code docs walk through n8n, Make and Zapier step by step.
Pricing and billing
Chat models are billed per 1M input and output tokens at the rates in the table above and on /pricing. All usage comes from one prepaid balance that does not expire, with no subscription. Top-ups start at $10, with ready-made packs of $10, $25, $50, $100 and $250. Prompts and outputs are not used to train models.
Check your balance from code:
import requests
print(requests.get("https://jantaris.com/v1/balance",
headers={"Authorization": f"Bearer {os.environ['AI_API_KEY']}"}).json())The endpoint reference is in /docs/chat.
FAQ
Can I use the OpenAI SDK to call Claude?
Yes. Point the OpenAI SDK at an OpenAI-compatible endpoint that serves Claude, such as https://jantaris.com/v1, and set model="claude-sonnet-5" or model="claude-opus-5.5". The request and response use the standard Chat Completions format.
Does streaming work with non-OpenAI models?
Yes. Pass stream=True (Python) or stream: true (Node) and iterate over the chunks exactly as with OpenAI models.
How do I switch between Gemini, DeepSeek and GPT?
Change the model string, for example gemini-3.8-flash, deepseek-v4.1-flash or gpt-6-sol. The client, key and code stay the same.
Do I need separate accounts with each AI vendor?
No. One Jantaris key and one prepaid balance cover every model in the catalog, including chat, image, video and music models.
Does it work with LangChain and n8n?
Yes. Both accept a custom OpenAI base URL. Set it to https://jantaris.com/v1 and use your Jantaris key.












