Hmbown/CodeWhale · error
only POST /v1/chat/completions is proxied
Error message
only POST /v1/chat/completions is proxied
What it means
The chat2responses proxy only forwards POST requests whose URL contains /chat/completions (plus GET /health). Anything else gets a 404 JSON error with this message. The proxy intentionally exposes a minimal surface.
Solutions
- Use POST with a URL containing /v1/chat/completions
- If the client speaks the Responses API, switch it to chat-completions mode or use a different proxy
- Use GET /health for health checks
- Check the client's base_url so the full path is /v1/chat/completions
Example fix
// before POST /v1/responses // after POST /v1/chat/completions
Defensive patterns
Strategy: try-catch
Validate before calling
function assertProxyRequest(method, url) {
if (method === "GET" && url === "/health") return;
if (method !== "POST" || !url.includes("/chat/completions")) {
throw new Error("proxy only accepts POST /v1/chat/completions or GET /health");
}
} Try / catch
const res = await fetch(proxyUrl, opts);
if (res.status === 404) {
const body = await res.json();
if (body.error === "only POST /v1/chat/completions is proxied") {
throw new Error("Configure the client for chat-completions, not responses API");
}
} Prevention
- Set the client's base_url so requests hit /v1/chat/completions
- Do not use the Responses API against this proxy
- Use GET /health for health checks only
- Verify the HTTP verb is POST
When it happens
Trigger: Requesting /v1/responses or /models, using GET/PUT/DELETE on a chat-completions path, or hitting any other path with any verb other than the two allowed combos.
Common situations: Pointing an OpenAI SDK configured for the Responses API (POST /v1/responses) at this proxy, a client probing /health with POST, or a misconfigured base URL causing requests to /v1/chat/completions/foo with the wrong verb.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- account_agent_model_unconfigured
- Anthropic API error (HTTP )
- Auto has no concrete route yet; send a turn before warming…
- build platform HTTP client
- building bundle fetch client failed
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/a492ccdbe867113d.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/opencode-chat2responses-proxy.mjs:135
out += `data: ${JSON.stringify({ id: evt.response?.id ?? "chatcmpl-proxy", object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model, choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: item.call_id, type: "function", function: { name: item.name, arguments: "" } }] }, finish_reason: null }] })}\n\n`;
} else if (type === "response.function_call_arguments.delta") {
out += `data: ${JSON.stringify({ id: evt.response?.id ?? "chatcmpl-proxy", object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model, choices: [{ index: 0, delta: { tool_calls: [{ index: 0, function: { arguments: evt.delta ?? "" } }] }, finish_reason: null }] })}\n\n`;
} else if (type === "response.completed" || type === "response.incomplete") {
out += `data: ${JSON.stringify({ id: evt.response?.id ?? "chatcmpl-proxy", object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model, choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}\n\n`;
}
} catch {}
}
return out;
}
const server = http.createServer(async (req, res) => {
if (req.method === "GET" && req.url === "/health") {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: true, upstream: UPSTREAM_BASE }));
return;
}
if (req.method !== "POST" || !req.url?.includes("/chat/completions")) {
res.writeHead(404, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "only POST /v1/chat/completions is proxied" }));
return;
}
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", async () => {
try {
const chatBody = JSON.parse(body || "{}");
const model = chatBody.model ?? "muse-spark-1.2-contributor-free";
const isStream = chatBody.stream === true;
const apiKey = req.headers.authorization?.replace(/^Bearer\s+/i, "") ?? process.env.OPENCODE_ZEN_API_KEY ?? "";
const responsesBody = chatToResponses(chatBody);
const upstreamUrl = `${UPSTREAM_BASE}${UPSTREAM_PATH}`;
const headers = {
"content-type": "application/json",
accept: isStream ? "text/event-stream" : "application/json",
};
if (apiKey) headers.authorization = `Bearer ${apiKey}`;View on GitHub (pinned to 433685b202)