Coming from OpenRouter

Switch from OpenRouter in two lines.

Keep your OpenAI-compatible client. Change the base URL and API key.

openrouter.ai/api/v1www.ninjachat.ai/api/v1
Drop the vendor prefix

Same models. Flat slugs. Native image and video.

anthropic/claude-opus-4.6claude-opus-4.6
openai/gpt-5gpt-5
google/gemini-3.1-progemini-3.1-pro
x-ai/grok-4grok-4
before· openrouter
from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key="sk-or-...",
)

resp = client.chat.completions.create(
    model="anthropic/claude-opus-4.6",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)
after· ninjachat
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="claude-opus-4.6",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)
Create API key API docs ↗
You keep

Your OpenRouter models still have names.

Same key, new catalog

What joining NinjaChat actually adds.

Kling 2.6
The rest

Then these.

  1. Create a key and add credits

    Get an nj_sk_ key at /developers and add a credit pack. Balances are prepaid, and failed generations are refunded.

    verify your key works
    # 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"}]}'
  2. Drop the provider prefixes

    OpenRouter namespaces models as vendor/model. NinjaChat uses canonical catalog IDs. Routing preferences live in the routing object and ordered fallbacks live in the models array.

    routing + fallbacks
    # NinjaChat routing + ordered fallbacks
    client.chat.completions.create(
        models=["claude-opus-4.6", "gpt-5", "gemini-3.1-pro"],
        routing={"strategy": "cost", "allow_fallbacks": True},
        messages=[{"role": "user", "content": "Hello"}],
    )
  3. Verify streaming and tool calling

    Same SDK code. Streaming arrives as SSE deltas, and tool_calls round-trip with role: "tool" messages.

    streaming + tools, unchanged sdk
    # 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"},
        ],
    )

Model mapping — OpenRouter ids to NinjaChat slugs

Metered per token — prices shown are typical-request estimates at the live rates on GET /v1/models. Actual requests settle from measured token usage.

OpenRouter idNinjaChat slugPrice / request
openai/gpt-5gpt-5$0.023
anthropic/claude-opus-4.6claude-opus-4.6$0.050
anthropic/claude-sonnet-4.6claude-sonnet-4.6$0.030
google/gemini-3.1-progemini-3.1-pro$0.022
google/gemini-3-flashgemini-3-flash$0.0055
deepseek/deepseek-v3.2.2deepseek-v3.2.2$0.004
x-ai/grok-4grok-4$0.009
moonshotai/kimi-k2.5 (any K2)kimi-k2.6$0.009
openrouter/autoninja/autobilled at resolved model's rate
The honest part

What doesn't come along.

No /v1/embeddings and no moderations endpoint

NinjaChat'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.

60 requests/min default rate limit

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.

Prepaid credits, not postpaid billing

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.

Metered $/MTok chat pricing

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.05 on Claude Opus (gpt-5.4-pro ≈$0.33). No length guard — long context just needs balance.

A curated catalog, not the longest tail

OpenRouter has the broader long-tail marketplace. NinjaChat currently serves 169 text, 39 image, and 12 video models. Check GET /v1/models before migrating a niche model.

BYOK works the same way

Save your own provider keys (Anthropic, OpenAI, Google, xAI, and more) in the developer console and matching requests serve on your key automatically — the provider bills your account and NinjaChat charges a 5% platform fee, the same shape as OpenRouter's BYOK. Provider pinning beyond that is handled for you: pick a model slug (or auto), not an upstream.

FAQ

How is this migration different from switching to OpenRouter originally?

Identical mechanics: it's an OpenAI-compatible endpoint, so you change base_url and api_key. The extra step is dropping OpenRouter's vendor/ prefixes from model ids (anthropic/claude-opus-4.6 → claude-opus-4.6).

Does NinjaChat support fallback models like OpenRouter's models array?

Yes — pass an ordered models array and set routing.allow_fallbacks. Provider constraints and strategy remain inside routing.

What about image and video generation?

That's the main reason to switch: POST /v1/images/generations and POST /v1/videos share the same key and balance. Current models and unit prices come from GET /v1/models.

How does NinjaChat pricing compare to OpenRouter's pass-through?

Both bill per token. NinjaChat publishes input, cached-input, and output rates in GET /v1/models and settles each request to actual token usage.

Ready for every lab?

Start building All guides →