返回指南
Troubleshooting·2026年7月17日·6 min read

OpenAI-compatible API returning 401/403 — base_url and header pitfalls

The whole point of OpenAI-compatible APIs is that the SDK just works — so when it 401s, the bug is almost always in the two lines you changed: base_url and api_key. Here are the failure modes in the order they actually occur.

The whole point of OpenAI-compatible APIs is that the SDK just works — so when it 401s, the bug is almost always in the two lines you changed: base_url and api_key. Here are the failure modes in the order they actually occur.

The error

response (HTTP 401)
{
  "error": {
    "type": "invalid_api_key",
    "message": "Invalid or missing API key.",
    "code": "invalid_api_key"
  }
}

Causes and fixes at a glance

CauseFix
base_url missing the /v1 suffix (or doubling it)Most gateways want https://host/v1 exactly — the SDK appends /chat/completions itself.
Key from a different hostsk-… keys only authenticate against the service that minted them; check prefix ↔ host.
Corporate proxy / WAF stripping the Authorization headerTest from a clean network; configure the proxy to pass Authorization through.
OPENAI_API_KEY env var overriding your explicit keyThe SDK reads env by default — a stale env var silently wins in some setups; pass api_key explicitly.

Verify the exact URL the SDK is hitting

Print client.base_url and call GET /v1/models — the cheapest authenticated endpoint. If /models works, auth is fine and your error is elsewhere:

check.py
from openai import OpenAI

client = OpenAI(
    base_url="https://api.kunavo.com/v1",   # exactly one /v1
    api_key="sk-kunavo-...",                 # explicit beats env vars
)
print(client.base_url)
print([m.id for m in client.models.list().data][:5])

Curl the same host to rule the SDK out

If curl with Authorization: Bearer works and the SDK doesn't, compare the SDK's actual request (set OPENAI_LOG=debug) — nine times out of ten a proxy or env var rewrote something.

If you're calling through Kunavo

Kunavo's endpoint is strict-OpenAI-shaped at https://api.kunavo.com/v1 with Bearer auth, and GET /v1/models works as the auth smoke test. If your code runs against api.openai.com, pointing base_url at Kunavo is the only change — same SDK, same wire format, one key for Claude, Gemini, GPT and media models.

FAQ

401 vs 403 on a gateway — what's the difference?

401 = the credential itself wasn't accepted (missing/invalid key). 403 = the key is valid but not allowed to do that (disabled key, suspended account, model not permitted). Read the error body — compatible APIs put the reason in error.message.

Why does my code work locally but 401 in CI?

CI is a different environment: the secret isn't set, is set for a different service, or a proxy strips the header. Log repr(key[:12]) and the base_url from inside CI to see what's actually being sent.

More error semantics live in the error reference; getting a key takes a minute via sign up and the authentication docs.