Keep the OpenAI SDK, keep GPT — and get Claude, Gemini, Grok, image, and video on the same key. The migration is a base URL and an API key.
api.openai.comninjachat.ai/api/v1api.openai.com/v1→ninjachat.ai/api/v1OPENAI_API_KEY→nj_sk_…gpt-5→gpt-5from openai import OpenAI
client = OpenAI() # key from OPENAI_API_KEY
resp = client.chat.completions.create(
model="gpt-5",
messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)from openai import OpenAI
client = OpenAI(
base_url="https://www.ninjachat.ai/api/v1", # ← changed
api_key="nj_sk_YOUR_KEY", # ← changed
)
resp = client.chat.completions.create(
model="gpt-5",
messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)Get an nj_sk_ key at /developers, add a credit pack. Prepaid; failed gens refund.
# Sanity check: list every model your key can call (no auth needed)
curl https://www.ninjachat.ai/api/v1/models
# First authenticated request
curl https://www.ninjachat.ai/api/v1/chat/completions \
-H "Authorization: Bearer nj_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-5", "messages": [{"role": "user", "content": "Say hi"}]}'Same SDK code. SSE deltas; tool_calls round-trip with role:"tool".
# Streaming
stream = client.chat.completions.create(
model="claude-sonnet-4.6",
messages=[{"role": "user", "content": "Write a haiku about ninjas"}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
# Tool calling
resp = client.chat.completions.create(
model="gpt-5",
messages=[{"role": "user", "content": "Weather in Tokyo?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
)
call = resp.choices[0].message.tool_calls[0]
# ...run your function, then send the result back:
followup = client.chat.completions.create(
model="gpt-5",
messages=[
{"role": "user", "content": "Weather in Tokyo?"},
resp.choices[0].message,
{"role": "tool", "tool_call_id": call.id, "content": "22°C, clear"},
],
)If you use OpenAI's Images API, the equivalent here is POST /v1/images/generations — synchronous JSON with hosted URLs instead of base64 by default. gpt-image-2 is available directly, alongside FLUX, Imagen 4, and Nano Banana on the same key.
# Before — OpenAI Images API
img = client.images.generate(
model="gpt-image-1",
prompt="a ninja cat, studio lighting",
)import requests
r = requests.post(
"https://www.ninjachat.ai/api/v1/images/generations",
headers={"Authorization": "Bearer nj_sk_YOUR_KEY"},
json={"model": "gpt-image-2", "prompt": "a ninja cat, studio lighting", "n": 1},
)
print(r.json()["data"][0]["url"]) # hosted URL, ready to useThis endpoint is OpenAI-shaped: POST /images/generations returns { data: [{url}], model, usage, cost_usd, request_id }.
GPT models keep their names. Prices shown are typical per-request (5K-in/1K-out) at the published $/MTok rates.
claude-opus-4.6, gemini-3.1-pro, grok-4…the reason to switch: every frontier lab on one key$0.01–$0.056NinjaChat's v1 API covers chat, images, video, and search. If your pipeline embeds documents or calls a moderation endpoint, keep those calls on your current provider — only the completion traffic needs to move.
Every API key gets 60 requests per minute (video submissions are throttled harder because each one is a long-running job). 429 responses include Retry-After. Need more? Contact us from the console.
You buy a credit pack up front instead of getting a surprise invoice. Failed generations are automatically refunded, and you can set monthly spend limits per account, key, or project.
Chat bills per token — published $/MTok input and output rates for every model on GET /v1/models (cache-read and long-context tiers included). Requests preauthorize an estimated maximum and settle to actual usage; a typical request runs from ≈$0.002 on open models to ≈$0.056 on Claude Opus (gpt-5.4-pro ≈$0.37). No length guard — long context just needs balance.
NinjaChat implements the Chat Completions surface (plus native images/video/search). Code built on the Assistants API, the Responses API, fine-tuning, or audio/realtime endpoints won't port — chat.completions code ports unchanged.
No. Change base_url to https://www.ninjachat.ai/api/v1 and api_key to an nj_sk_ key. chat.completions.create, streaming loops, tool-calling round-trips, and models.list all keep working with the official openai package in Python and Node.
Yes — gpt-5.4, gpt-5.4-pro, gpt-5, gpt-5-mini, and o3-mini are all served under their own names, alongside Claude, Gemini, Grok, Llama, and DeepSeek on the same key.
response_format {type: "json_object"} and {type: "json_schema"} are both enforced server-side. One caveat: json_schema combined with stream: true returns a 400 — use non-streaming for strict-schema outputs.
No. There is no /v1/embeddings — keep embedding calls on OpenAI (or any embeddings provider) and move only completion traffic. Mixing providers is exactly what the OpenAI SDK's base_url parameter is for.
OpenAI meters tokens and invoices you. NinjaChat is prepaid credits with the same per-token billing model — published $/MTok rates for every lab's models on one balance, holds that settle to actual usage, and automatic refunds on failed requests.