OpenAI compatible API: what it is, why base_url matters, how to test it

What an "OpenAI-compatible API" really means, why a client only needs a new base_url and key, how the Anthropic format differs, and how to test the connection in five minutes.

6 min · · development · API keys

The phrase "openai compatible api" shows up more and more in service descriptions and app settings. It matters to anyone who wants to plug models other than OpenAI's into an app or IDE: if a service is compatible, the client only needs a new address and key. Below: what compatibility actually means, where it ends, how the Anthropic format differs, and how to test a Zukko connection with a single curl command.

What an OpenAI compatible API means

An OpenAI-compatible API is a server that accepts requests and responds in the same format as the OpenAI API. The "contract" matches: paths, JSON fields, authorization and streaming format. The client doesn't care who sits behind the address: it sends the same JSON and gets a response with the familiar structure.

The minimum set compatibility usually implies:

  • Path POST /v1/chat/completions for chat requests and GET /v1/models for the model list.
  • Authorization via the Authorization: Bearer <key> header.
  • Request with model and messages, where each message has a role (system, user, assistant) and content.
  • Response with a choices array, text in choices[0].message.content, and token counters in usage (prompt_tokens, completion_tokens).
  • Streaming with "stream": true — data: {...} lines in Server-Sent Events format and a final data: [DONE].

What compatibility does not guarantee:

  • Every endpoint. OpenAI has many (embeddings, audio, files and more). A compatible service may implement only some. In Zukko, the OpenAI format means POST /v1/chat/completions, GET /v1/models and POST /v1/responses.
  • Every parameter for every model. Fields like tools or response_format are passed through, but support depends on the specific model. Test with your own requests.
  • The same model names. The service sets the names. For the current list and prices, call GET https://api.zukko.pro/v1/models or open the Models page in the dashboard.

What base_url is and why it contains /v1

base_url is the beginning of the address to which the SDK appends the path of a specific method. For a chat request, the official OpenAI Python SDK appends /chat/completions to base_url. So for the OpenAI format, base_url must already include /v1:

base_url = https://api.zukko.pro/v1 request → https://api.zukko.pro/v1/chat/completions

Two common mistakes:

  • Forgetting /v1. The request goes to https://api.zukko.pro/chat/completions, which doesn't exist — a 404.
  • Adding /v1 twice. If you paste the full path https://api.zukko.pro/v1/chat/completions into the base URL field, the client appends another suffix, and you get a 404 again.

Anthropic clients work differently: the official Anthropic SDK and Claude Code append /v1/messages themselves, so they need the address without /v1. A detailed breakdown is in the article base URL with or without /v1.

Which clients connect via base_url

Nearly anything that works with OpenAI also works with a compatible service:

  • Official SDKs. Python: OpenAI(base_url="https://api.zukko.pro/v1", api_key="fetch_..."); the SDK also reads the OPENAI_BASE_URL and OPENAI_API_KEY variables. Node.js: new OpenAI({ baseURL: "https://api.zukko.pro/v1", apiKey: ... }).
  • Frameworks such as LangChain: you set the same base URL in the OpenAI client.
  • IDEs and agents: Cursor, Cline, Kilo Code, Codex — you enter the base URL https://api.zukko.pro/v1 and the key.
  • Chat web UIs: LibreChat, Open WebUI and similar.

The settings field goes by different names: Base URL, API Base, Endpoint, OpenAI API URL. If you're not sure which one it is, check the tool's own documentation. The meaning is the same everywhere: the address up to and including /v1, plus the key.

How the Anthropic format differs

The second widespread standard is Anthropic's Messages API, the native format of Claude models. Claude Code, the official Anthropic SDK and Roo Code use it. The main differences:

OpenAI formatAnthropic format
EndpointPOST /v1/chat/completionsPOST /v1/messages
Base URL for SDKhttps://api.zukko.pro/v1https://api.zukko.pro
KeyAuthorization: Bearerthe Anthropic SDK sends x-api-key; Zukko accepts both headers
Versionnot neededanthropic-version: 2023-06-01 header (the SDK sets it)
System prompta message with role: "system"usually a separate system field
max_tokensoptionalrequired
Response textchoices[0].message.contentcontent[0].text (array of blocks)
Tokensusage.prompt_tokens, completion_tokensusage.input_tokens, output_tokens
Streamingdata: chunks and [DONE]events message_start, content_block_delta, message_stop and others

In Zukko, any model can be called in either format: the gateway converts the request. The difference shows up in price when caching is involved. For Claude, the cache-read discount applies only in the Anthropic format: there the gateway sees how much input came from the cache. In the OpenAI format, all Claude input is billed at the input price. An example with prices as of October 7, 2026: for claude-sonnet-5, input costs $0.4 per 1M tokens and a cache read costs $0.05. For GPT models, caching is visible in both formats via prompt_tokens_details.cached_tokens. How to pick a format for your task is covered in the docs page Anthropic or OpenAI format.

Zukko addresses

  • OpenAI format: base URL https://api.zukko.pro/v1 → POST /v1/chat/completions, GET /v1/models, POST /v1/responses.
  • Anthropic format: POST https://api.zukko.pro/v1/messages and POST /v1/messages/count_tokens.
  • Clients that add /v1 themselves (Claude Code, Anthropic SDK, Roo Code): https://api.zukko.pro.
  • OpenCode (anthropic provider via the AI SDK): https://api.zukko.pro/v1.
  • Claude Code: ANTHROPIC_BASE_URL=https://api.zukko.pro and ANTHROPIC_AUTH_TOKEN=fetch_..., plus catalog models in ANTHROPIC_MODEL and ANTHROPIC_DEFAULT_HAIKU_MODEL: background requests go to the Haiku slot (by default claude-haiku-*), which is not in the catalog, and without this variable they get 404 model_not_found.

How to test the connection with curl

Three commands show that the address, key and balance are fine.

1. Model list. The endpoint is public, no key needed:

bash
curl https://api.zukko.pro/v1/models

2. A request in the OpenAI format:

bash
curl https://api.zukko.pro/v1/chat/completions \
  -H "Authorization: Bearer fetch_your_key" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-6.1-sol", "messages": [{"role": "user", "content": "Answer in one word: working?"}]}'

3. The same question in the Anthropic format:

bash
curl https://api.zukko.pro/v1/messages \
  -H "Authorization: Bearer fetch_your_key" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model": "claude-sonnet-5", "max_tokens": 64, "messages": [{"role": "user", "content": "Answer in one word: working?"}]}'

What the responses mean:

  • 200 with text — everything is connected.
  • 401 — the key is wrong, mistyped or revoked.
  • 402 — insufficient balance.
  • 403 model_not_allowed_for_key — the model isn't on this key's allowed list.
  • 404 model_not_found — unknown model name; check against GET /v1/models.
  • 404 with the not_found code — the path doesn't exist: check /v1 in the address.

If curl works but your program doesn't, the problem is almost always the base URL field or the model name in the client settings.

Minimal Python example

bash
from openai import OpenAI

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

resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[{"role": "user", "content": "Hi! Answer in one sentence."}],
)
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)

To switch models, you change only model, for example to gpt-6.1-sol or deepseek-v4.1-flash. The full field reference is in the chat/completions docs, and a step-by-step guide for your own service is in the article how to connect an AI model to your app.

If you don't have a key yet, create one on the API keys page and use it in any of the examples above.

FAQ

Is an OpenAI compatible API only for OpenAI models?

No. Compatibility is about the request format, not the models. Through Zukko's OpenAI format you can call Claude, GPT, Gemini, DeepSeek, Qwen and GLM — only the model value changes.

Why does the client return 404 when the key is correct?

Most often because of /v1: OpenAI clients need it in the base URL, Claude Code and the Anthropic SDK don't. The other option is an unknown model name, in which case the response carries the model_not_found code; in Claude Code this is how a missing ANTHROPIC_DEFAULT_HAIKU_MODEL shows up.

Which format should I use for Claude?

If caching matters (agents, long conversations, a large system prompt), use the Anthropic format: only there does the gateway bill Claude cache reads at the reduced price. For simple one-off requests, either works.

Are all OpenAI parameters supported?

The core ones (model, messages, max_tokens, stream) are. Support for tools, response_format and others depends on the model; test with your own request before building logic on them. temperature is a special case: according to the Zukko docs, newer Claude models may reject non-default values, so it's better not to pass it for them.

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_*

OpenAI compatible API: what it is and how to connect · Zukko