Migrating from OpenRouter to Zukko: what to change in code and config

Moving from OpenRouter to Zukko's OpenAI-compatible API comes down to the address, key and model names. Every change, OpenRouter-specific parameters and a rollout plan.

5 min · · development · сравнение

If your code runs through OpenRouter and you are looking for an alternative, moving to Zukko usually comes down to three changes: the API address, the key and the model names. Both services support the OpenAI-compatible format, so your SDK and request structure stay the same. Below: exactly what to change, what to do with OpenRouter-specific headers and parameters, and how to roll out safely.

Why move

The decision depends on your needs, so here are just the facts about Zukko:

  • one fetch_... key for Claude, GPT and GLM models, a single USD balance, pay per token with no subscription;
  • payment from Russia is available: SBP, Russian cards, crypto; top-ups from $1;
  • two API formats: OpenAI-compatible and Anthropic-compatible;
  • keys have daily and monthly limits in $ and an allowed-models list.

Compare cost at your own volume: take monthly input and output tokens from your OpenRouter stats and price them at Zukko rates, for example for Claude Sonnet 5: input_millions × $0.30 + output_millions × $0.30. For agents with a large repeated context, note that Zukko bills cached tokens at the regular input price.

Three migration points

1. Base URL. OpenRouter uses https://openrouter.ai/api/v1; Zukko uses https://api.zukko.pro/v1 for OpenAI-compatible clients.

2. Key. Create a key under API keys. It starts with fetch_ and is shown in full only once, so save it right away.

3. Model IDs. OpenRouter uses vendor-prefixed IDs (anthropic/claude-sonnet-5, openai/gpt-5.6-sol). Zukko has no prefix: claude-sonnet-5, gpt-5.6-sol.

Example: Python and the OpenAI SDK

Before (OpenRouter):

bash
from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key="sk-or-v1-...",
    default_headers={
        "HTTP-Referer": "https://example.com",
        "X-OpenRouter-Title": "My App",
    },
)

response = client.chat.completions.create(
    model="anthropic/claude-sonnet-5",
    messages=[{"role": "user", "content": "Hello"}],
)

After (Zukko):

bash
from openai import OpenAI

client = OpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key="fetch_your_key",
)

response = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)

According to the OpenRouter docs, the HTTP-Referer and X-OpenRouter-Title headers (older code may use X-Title) are optional and used for app attribution in OpenRouter rankings. Zukko does not need them; remove them to keep the code clean.

The same in JavaScript/TypeScript:

bash
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.zukko.pro/v1",
  apiKey: process.env.ZUKKO_API_KEY,
});

const response = await client.chat.completions.create({
  model: "claude-sonnet-5",
  messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content);

Mapping table

Find (OpenRouter)Replace (Zukko)
https://openrouter.ai/api/v1https://api.zukko.pro/v1
sk-or-v1-...fetch_...
anthropic/claude-sonnet-5claude-sonnet-5
Opus 5.5 (OpenRouter writes the version with a dot)claude-opus-5-5 (with a hyphen)
openai/gpt-5.6-solgpt-5.6-sol
openai/gpt-5.6-lunagpt-5.6-luna
z-ai/glm-5.3glm-5.3
z-ai/glm-5.3-flashglm-5.3-flash
aliases like ~anthropic/claude-sonnet-latesta concrete id from /v1/models
HTTP-Referer, X-OpenRouter-Title, X-Title headersremove

The OpenRouter IDs were checked against its public model list at https://openrouter.ai/api/v1/models. The Zukko model set changes: the current list is at GET https://api.zukko.pro/v1/models or on the "Models" page in the dashboard.

OpenRouter-specific parameters

OpenRouter accepts its own request-body fields that are not part of the standard OpenAI API. The most common are models (an array of fallback models) and provider (routing preferences across providers); both are described in OpenRouter's provider routing docs. Zukko's docs do not include these fields, so:

  • remove provider and models from requests;
  • if you need a fallback to another model, implement it in your code: catch the error and retry with a different model;
  • search for places where these fields are passed via extra_body; they are easy to miss.

Standard OpenAI API parameters (messages, max_tokens, stream, tools and others) carry over unchanged. Reference: Chat Completions.

Streaming

Streaming code does not need changes, but check one detail: the last chunk may carry token usage data with an empty choices list. Add a check before accessing chunk.choices[0]:

bash
stream = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[{"role": "user", "content": "Tell me about HTTP/2"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

LangChain, Open WebUI, Dify and other tools

LangChain: ChatOpenAI(model="claude-sonnet-5", base_url="https://api.zukko.pro/v1", api_key="fetch_...").

Open WebUI, Dify, LibreChat and similar: add an OpenAI-compatible provider in settings, set Base URL https://api.zukko.pro/v1 and the fetch_... key, then pick models. Field names differ between versions, so check the tool's own documentation.

Code on the Anthropic SDK: set base_url="https://api.zukko.pro" without /v1; the SDK adds the path itself. More in Anthropic or OpenAI format.

Production rollout plan

  1. Create a separate test key with a small daily limit and an allowed-models list.
  2. Move the base URL, key and model into environment variables if they are still hardcoded.
  3. On staging, swap the values and run your tests, including streaming and tool calls.
  4. Check spending in the dashboard and compare it with your formula estimate.
  5. Switch production. Rollback means restoring the old variable values and restarting the app.

Zukko error codes worth handling: 401 invalid key, 402 insufficient balance, 403 model not allowed for the key, 413 request too large, 429 too many requests, 503 provider unavailable.

Checklist

  • fetch_... key created and saved
  • Base URL is https://api.zukko.pro/v1 (for the Anthropic SDK, https://api.zukko.pro)
  • Vendor prefixes removed from model IDs, Opus 5.5 written as claude-opus-5-5
  • HTTP-Referer, X-OpenRouter-Title, X-Title headers removed
  • provider and models fields removed
  • Streaming has the if chunk.choices check
  • A test request returns an answer and spending shows up in the dashboard

FAQ

Do I need to rewrite prompts?

No: the models are the same, only the gateway changes. Still run your test set after the move to make sure model IDs and parameters were replaced correctly.

What about OpenRouter fallback models?

Move the logic into your code: on an error, retry with a different model. If a model is disabled, Zukko responds with 503 and the model_disabled code, which you can use to switch to a fallback.

How do I quickly check that everything works?

Send a short request to an inexpensive model:

bash
r = client.chat.completions.create(
    model="glm-5.3-flash",
    messages=[{"role": "user", "content": "ping"}],
)
assert r.choices[0].message.content
print("OK")

Where can I read more?

Quick start and authentication in the Zukko docs.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

OpenRouter alternative: move to api.zukko.pro · Zukko