Nearly every "unified API" onboarding fails the same way: the base URL is almost right. The value differs by SDK — not by model, not by provider — because each official SDK appends a different path of its own. Get it wrong and you get a 404 with no hint about which half was wrong.
The short version: the OpenAI SDK takes https://api.apimodels.app/v1 because it appends /chat/completions. The Anthropic SDK takes the bare root domain https://api.apimodels.app because it appends /v1/messages itself — adding /v1 yourself produces /v1/v1/messages and a 404. Native Gemini calls go to /v1beta/models/{model}:generateContent. All three were verified against production on 2026-09-06, along with the two failure modes.
Why the difference exists at all: these are not our conventions, they are the official SDKs' own. The OpenAI client treats the base URL as "everything up to and including the API version"; the Anthropic client treats it as "the host". A gateway that speaks both protocols natively — rather than translating one into the other — inherits both conventions, and that is the price of Claude Code, the Anthropic SDK and Cursor working without a shim.
One way to tell a native Anthropic endpoint from a translated one, on any provider: send a request and look at the response body. Native returns {"type":"message","content":[...]}; a translation layer returns {"choices":[...]}. The difference matters if you use thinking blocks, native tool_use shapes, or the full set of streaming event types — a translation drops or flattens them.
Base URL by SDK — verified against production 2026-09-06
SDK / 调用方式 base_url SDK 自己拼的路径
──────────────────────────────────────────────────────────────────────────────
OpenAI (python / node) https://api.apimodels.app/v1 /chat/completions
Anthropic (python/node) https://api.apimodels.app /v1/messages
Gemini 原生 (REST) https://api.apimodels.app /v1beta/models/{m}:generateContent
Claude Code ANTHROPIC_BASE_URL=https://api.apimodels.app
Cursor Anthropic base URL = https://api.apimodels.app
最常撞的两个 404
──────────────────────────────────────────────────────────────────────────────
❌ OpenAI SDK 漏了 /v1 → POST /chat/completions 404
❌ Anthropic SDK 多写了 /v1 → POST /v1/v1/messages 404
鉴权对所有路径一致:Authorization: Bearer <APIMODELS_API_KEY>
Anthropic 风格的端点也接受 x-api-key。cURL
# OpenAI 兼容:base_url 带 /v1
curl https://api.apimodels.app/v1/chat/completions \
-H "Authorization: Bearer $APIMODELS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.6-luna","max_tokens":32,"messages":[{"role":"user","content":"hi"}]}'
# 原生 Anthropic:注意路径里的 /v1 是【端点】的一部分,不是 base_url 的
curl https://api.apimodels.app/v1/messages \
-H "Authorization: Bearer $APIMODELS_API_KEY" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-sonnet-5","max_tokens":32,"messages":[{"role":"user","content":"hi"}]}'
# Gemini 原生
curl "https://api.apimodels.app/v1beta/models/gemini-3.1-flash-lite:generateContent" \
-H "Authorization: Bearer $APIMODELS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}'Python
# 同一把 key,两个 SDK,两种 base_url —— 差别只在 SDK 自己拼什么路径
from openai import OpenAI
import anthropic
KEY = "YOUR_APIMODELS_KEY"
# OpenAI SDK:带 /v1(它会拼 /chat/completions)
oai = OpenAI(api_key=KEY, base_url="https://api.apimodels.app/v1")
print(oai.chat.completions.create(
model="gpt-5.6-luna", max_tokens=32,
messages=[{"role": "user", "content": "hi"}]).choices[0].message.content)
# Anthropic SDK:不带 /v1(它会拼 /v1/messages)
ant = anthropic.Anthropic(api_key=KEY, base_url="https://api.apimodels.app")
print(ant.messages.create(
model="claude-sonnet-5", max_tokens=32,
messages=[{"role": "user", "content": "hi"}]).content[0].text)It depends on the SDK, not the model. The OpenAI SDK needs https://api.apimodels.app/v1 because it appends /chat/completions to whatever you give it. The Anthropic SDK needs https://api.apimodels.app with no /v1, because it appends /v1/messages itself. Adding /v1 for the Anthropic client produces /v1/v1/messages and a 404 — verified 2026-09-06.
Bypass the SDK and send one curl to the full URL you think it is calling. If POST https://api.apimodels.app/v1/chat/completions returns 200, your base URL was missing /v1. If POST https://api.apimodels.app/v1/messages returns 200 but your Anthropic client still 404s, your base URL has an extra /v1. Most SDKs will also print the resolved URL if you enable debug logging.
Claude Code reads two environment variables, no config file needed:\n\nexport ANTHROPIC_BASE_URL="https://api.apimodels.app"\nexport ANTHROPIC_AUTH_TOKEN="YOUR_KEY"\n\nCursor: replace the Anthropic base URL and key in settings with the same values. Because these run on the native Anthropic Messages API rather than an OpenAI-shaped translation, tool use, thinking blocks and streaming event shapes are unchanged, so no business code changes.
Send a request and read the response body. Native Anthropic returns {"type":"message","content":[...]}; a translation layer returns {"choices":[...]}. It matters if you rely on thinking blocks, native tool_use structures, or the full range of streaming event types — a translation drops or flattens them, and you usually find out only when something silently stops working.
Yes: Authorization: Bearer <YOUR_KEY> works on every path. Anthropic-style endpoints additionally accept x-api-key, so an existing Anthropic client that sets that header instead works unchanged.
Yes. One key covers the OpenAI-compatible path, the native Anthropic Messages path and native Gemini, and draws on the same USD balance — along with image, video and audio models. You do not hold a separate key or balance per protocol.