If you have ever configured an AI coding assistant, local agent, or LLM gateway (like OpenCode, Cline, Cursor, or LiteLLM), you have probably stared at a settings screen that looks like this:
Most developers pick Chat completions by muscle memory, cross their fingers, and hope it works. When it throws a 404 Not Found or 401 Unauthorized, debugging feels like guesswork.
Once you understand how Base URL and API Format work together—and why three incompatible JSON dialects exist—you will never misconfigure an AI client again.
1. The Core Formula: Base URL + API Format
Think of your AI client settings as a two-part URL builder combined with a JSON translator:
Full Request URL = Base URL + API Format Path
For example, if you set:
-
Base URL:
https://opencode.ai/zen/go/v1 -
API Format:
Chat completions (/chat/completions)
Your client fires an HTTP POST request to:
https://opencode.ai/zen/go/v1/chat/completions
This looks trivial until you switch formats. Notice how the second option in many clients includes /v1 in its path (/v1/messages). If your Base URL already ends with /v1, naive clients concatenate them into a broken path:
❌ https://opencode.ai/zen/go/v1/v1/messages (404 Not Found)
✅ https://opencode.ai/zen/go/v1/messages (or remove /v1 from Base URL)
More importantly, API Format does not just change the URL path. It completely changes the authentication headers, JSON request body, JSON response shape, and Server-Sent Events (SSE) streaming structure.
2. Dialect #1: Chat completions (/chat/completions)
The "USB-C" of LLM APIs
Originally introduced by OpenAI, /chat/completions became the de facto universal standard of the AI ecosystem. Almost every model provider and local inference engine exposes an OpenAI-compatible endpoint:
- Cloud Gateways & Inference: OpenRouter, Groq, DeepSeek, Together AI, Mistral
-
Google Gemini (Compatibility Mode):
https://generativelanguage.googleapis.com/v1beta/openai/ -
Local Engines: Ollama (
http://localhost:11434/v1), vLLM, LM Studio, llama.cpp
How It Looks Under the Hood
-
Auth Header:
Authorization: Bearer <API_KEY> -
State Model: Stateless. Your client must resend the entire conversation history on every turn inside the
messagesarray. -
System Prompt: Placed inside
messageswith"role": "system".
POST /chat/completions
Authorization: Bearer sk-...
{
"model": "gpt-4o",
"messages": [
{ "role": "system", "content": "You are a concise code reviewer." },
{ "role": "user", "content": "Review this SQL query for injection." }
],
"temperature": 0.2
}
When to Pick It
Choose Chat completions as your default for 90% of proxies, open-weight models (Llama, Qwen, DeepSeek), and local Ollama setups.
3. Dialect #2: Anthropic messages (/v1/messages)
Why Anthropic Refused to Copy OpenAI
Anthropic designed its /v1/messages protocol with stricter structural guarantees and native support for Claude-specific features like Prompt Caching and Extended Thinking.
If you send an OpenAI-formatted JSON body to a native Anthropic endpoint, the server rejects it immediately.
How It Differs
-
Auth Headers: Uses
x-api-key: <API_KEY>and requires an explicit version header (anthropic-version: 2023-06-01) instead ofBearer. -
System Prompt: Moved out of
messagesinto a dedicated top-level"system"field. -
max_tokensis Mandatory: Unlike OpenAI (where it is optional), Anthropic returns a400 Bad Requestifmax_tokensis omitted. -
Tool Calling Shape: Uses structured
tool_useandtool_resultcontent blocks rather than OpenAI'stool_callsarray.
POST /v1/messages
x-api-key: sk-ant-...
anthropic-version: 2023-06-01
{
"model": "claude-3-7-sonnet-latest",
"max_tokens": 2048,
"system": "You are a concise code reviewer.",
"messages": [
{ "role": "user", "content": "Review this SQL query for injection." }
]
}
When to Pick It
Pick Anthropic messages when:
- Connecting directly to
https://api.anthropic.com(make sure your Base URL doesn't duplicate/v1). - Using a multi-provider gateway (like OpenCode Zen or LiteLLM) with Claude models and you want native prompt caching or thinking blocks preserved without lossy translation.
4. Dialect #3: Responses (/responses)
OpenAI's Next-Gen Agentic Protocol
As models shifted from simple chatbots to autonomous reasoning agents (like OpenAI's o-series, GPT-5, and Codex models), /chat/completions started showing its age. Resending massive JSON arrays on every tool-call loop wastes bandwidth, and stateless turns make server-side reasoning token caching awkward.
Enter the Responses API (/responses)—OpenAI's unified interface designed for multi-step, agentic workflows.
How It Differs
-
Auth Header: Still uses
Authorization: Bearer <API_KEY>. -
inputInstead ofmessages: Accepts either a raw string for simple prompts or a flexible array of input items. -
instructionsfor System Guidance: Top-level"instructions"replaces the"role": "system"message. -
Server-Side State (
previous_response_id): Every response returns a unique ID (e.g.,resp_abc123). Sendingprevious_response_idon your next call chains conversation state natively on the server—no need to resend full chat history. - Built-in Tools & Reasoning Items: First-class support for server-executed tools (web search, file search, code interpreter) and structured reasoning traces.
POST /responses
Authorization: Bearer sk-...
{
"model": "gpt-5-codex",
"instructions": "You are a concise code reviewer.",
"input": "Review this SQL query for injection.",
"previous_response_id": "resp_01abc..."
}
⚠️ Hidden Gotcha with
previous_response_id: Whileprevious_response_idcarries forward your conversation history, top-levelinstructionsdo not automatically persist across chained turns. Always resendinstructionson every request if your agent relies on strict system rules or JSON schemas.
When to Pick It
Pick Responses when running modern OpenAI reasoning/Codex models directly or through a gateway that explicitly supports the /responses specification. Do not pick it for Ollama or standard open-weight proxies unless they explicitly advertise /responses support.
5. Quick Reference Cheat Sheet
Bookmark this table the next time you configure an AI client:
| Feature | Chat completions |
Anthropic messages |
Responses |
|---|---|---|---|
| Endpoint Path | /chat/completions |
/v1/messages |
/responses |
| Auth Header | Authorization: Bearer |
x-api-key + anthropic-version
|
Authorization: Bearer |
| System Prompt | Inside messages (role: system) |
Top-level "system" field |
Top-level "instructions" field |
| Prompt Field |
messages (array) |
messages (array) |
input (string or item array) |
max_tokens |
Optional | Required | Optional (max_output_tokens) |
| State Model | Stateless (resend history) | Stateless (resend history) | Stateful option (previous_response_id) |
| Best Used For | Universal default (Ollama, Groq, OpenRouter, Gemini) | Native Claude (Prompt Caching, Thinking) | Modern OpenAI / Codex agentic & reasoning models |
3 Common Mistakes That Break Your Setup
-
The Double
/v1Trap: Pastinghttps://proxy.example.com/v1into Base URL while selecting/v1/messagesoften triggers a silent/v1/v1/messages404 error. Check your client's URL normalization behavior—or strip/v1from the Base URL if it fails. -
Thinking "Error 400" Means a Bad API Key: If you get a
400 Bad Requestmentioningmessages,input, ormax_tokens is required, your API key is fine—your API Format dropdown simply doesn't match what the server expects. -
Broken Streaming in IDE: Even if a "Test Connection" ping succeeds, picking the wrong format causes IDE chat panes to freeze or print raw JSON because SSE chunk schemas (
choices[0].deltavs.content_block_deltavs.response.output_text.delta) are completely incompatible across the three dialects.
Rule of Thumb
- Using Ollama, DeepSeek, Groq, Gemini, or OpenRouter? Use
Chat completions. - Using Claude directly (or via a Claude-native gateway)? Use
Anthropic messages. - Using modern OpenAI / Codex models with stateful reasoning? Use
Responses.
Which API format has given you the biggest debugging headache—or which gateway trick do you use in your setup? Drop a comment below!

Top comments (0)