The ANTHROPIC_BASE_URL 404, and the one path segment behind it
One key, one host, two different base URL strings. The wrong one answers 404, and most clients report that as a connection problem rather than a wrong path.
If Claude Code, an SDK or a curl call is returning 404 page not found against a custom endpoint, the cause is almost always one path segment. Anthropic-shaped clients want the bare host with no `/v1`; OpenAI-shaped clients want the host with `/v1`. One key, one host, two different strings — and the wrong one answers 404, which most clients report as a connection problem rather than a wrong path.
| Client | Base URL to set | Because |
|---|---|---|
| Claude Code | https://your-endpoint.com | It appends /v1/messages itself |
| Anthropic SDK | https://your-endpoint.com | Same — it adds the version |
| OpenAI SDK | https://your-endpoint.com/v1 | It appends /chat/completions |
| Codex CLI, aider, Continue, Cline, Cursor | https://your-endpoint.com/v1 | All OpenAI-shaped |
Raw curl | the full path, /v1/messages or /v1/chat/completions | Nothing is appended for you |
What the measurements show
Run against this station’s endpoint on 2026-08-30, four requests that differ only in the path and the credential header:
| Request | Result |
|---|---|
POST /v1/messages with Authorization: Bearer | 200 |
POST /v1/messages with x-api-key | 200 |
POST /v1/v1/messages | 404 |
GET /v1/models | 200, 27 models listed |
The third row is the whole article. /v1/v1/messages is what you get when you put /v1 in the base URL of a client that adds /v1 itself. Both halves are individually correct, which is why it is easy to do and hard to see.
The first two rows are worth knowing separately: on an Anthropic-shaped route, both credential styles work. If you are debugging a 401 rather than a 404, the header is not your problem.
Why it presents as a connection error
A 404 from a path that does not exist is usually returned by the gateway, not the model, so the body is plain text or HTML instead of the JSON error shape the client expects. The client fails to parse it, and what surfaces to you is “could not connect”, “API Error”, or a stack trace from the JSON parser.
So the symptom points at the network and the cause is a string in your config. If a request reaches the endpoint at all — even to be refused — the network is fine.
A quick way to tell them apart: curl -sv the base URL on its own. A TLS handshake that completes rules out DNS, firewall and certificate problems in one step, and leaves you with a routing question.
The second cause: a subscription and a base URL at the same time
There is a distinct failure that produces the same 404 and is not about paths at all. If Claude Code holds OAuth credentials from a Pro or Max plan and ANTHROPIC_BASE_URL is set in your environment or settings.json, the custom URL wins silently — so requests carrying subscription credentials arrive at an endpoint that has never heard of them.
The result is a 404 or a “model not found” that makes no sense, because the model does exist on the endpoint you thought you were calling.
- Want the subscription? Remove
ANTHROPIC_BASE_URLfrom your environment and from~/.claude/settings.json, then/logoutand sign in again. - Want the custom endpoint? Keep the variable and set
ANTHROPIC_AUTH_TOKENto that endpoint’s key, so the request carries the credential the endpoint expects. - Not sure which is active?
env | grep ANTHROPICand read~/.claude/settings.json. Two sources, and the file is the one people forget.
The third cause: the right path, the wrong API
Some parameters exist on one endpoint and not the other, and asking for them in the wrong place produces a 400 rather than a 404 — but it lands in the same debugging session, so it is worth naming here.
Measured on the same endpoint: tools: [{"type":"web_search"}] sent to /v1/chat/completions returns Unknown parameter: tools[0].function on some models and is silently ignored on others — HTTP 200, no tool call, no citations. The same request to /v1/responses works. Strict JSON schema behaves the same way: response_format on chat completions fails with an error naming a Responses-API field.
If a feature “does not work” but returns 200, check the endpoint before checking the model.
The fourth cause: a 200 that is not what you asked for
Once the path is right you can still get a response that breaks your client, and it is worth knowing the shape of it before you go looking for a 404 that is not there.
Some models answer a plain, non-streaming request with server-sent events anyway. Measured on 2026-08-30, one model returned data: {"object":"chat.completion.chunk"} five times out of five with no stream: true in the request. An SDK calling create() without streaming gets a JSON parse error rather than a completion — the body is not JSON, so the failure surfaces from the parser and looks like a malformed response from a broken endpoint.
The check is a header, not a retry: read the content type before parsing. text/event-stream means read it as a stream regardless of what you asked for.
A checklist that ends the problem
- Count the `/v1`s. Add up what is in your base URL and what your client appends. Exactly one, total.
- Curl the full path directly. It removes the client from the question in one command.
- Check for a competing credential. OAuth plus a custom base URL is the silent case.
- Confirm the endpoint serves the model.
GET /v1/modelslists what your key can actually reach; compare that with the id you are sending. - Check the API family. Web search and strict JSON live on
/v1/responses, not on chat completions. - Set a generous timeout. Once the path is right, some models take far longer without streaming than with it — a short timeout looks like a new failure.
Six checks, and the first two settle it most of the time.
Where to go next
- What “OpenAI-compatible” actually means — the other places a compatible endpoint quietly is not.
- AI API relays, explained — what sits behind a custom base URL, and how to audit it.
- What uptime means when your client retries — once the path is right, this is the next thing that looks like a bug and is not.
Every command above runs against our endpoint as written; the setup page has the same two base URLs per tool with a verification command beside each, and the status page shows which models are answering right now.
Figures in this guide were read on the dates shown beside them. Prices change; where a claim depends on a provider’s published price, the link goes to that provider’s own page so you can check it rather than take ours. This guide is reviewed by 2026-11-30.
Check the numbers yourself
Every model on this station, its per-token price and the provider’s published list price are on the pricing page, with no account required to read them.