HTTP API reference
The daemon binds to http://127.0.0.1:4141 by default and accepts loopback connections only. By default any Authorization value works (e.g. Authorization: Bearer copillm-local) — the daemon is local-only. If you set requireCallerSecret: true in ~/.copillm/config.yaml, the daemon generates a random secret at startup (printed as Caller secret: … and baked into the env blocks copillm emits), and every route except /healthz and /livez then requires Authorization: Bearer <that-secret>.
Selecting an account
A single daemon serves every account copillm holds (see copillm auth). A request targets a specific account with an optional leading /<account> path segment on the base URL:
http://127.0.0.1:4141/anthropic/v1/messages # the default account
http://127.0.0.1:4141/work/anthropic/v1/messages # the "work" account
http://127.0.0.1:4141/work/v1/chat/completions # likewise, OpenAI route
- A base URL with no
/<account>prefix resolves to the default account (the common case — the onecopillm auth switchsets). - The agent launchers add this prefix for you when you pass
--account; you only need it when wiring requests by hand. - Daemon-global routes are never prefixable:
/livez,/healthz,/models, and/_debugalways refer to the daemon itself, not an account. - An account id must match
^[A-Za-z0-9][A-Za-z0-9._-]*$; anything else is treated as a normal (unprefixed) path, so existing routes like/codex/...and/v1/...are never mistaken for an account.
Discovery
| Method | Path | Description |
|---|---|---|
GET | /models | Enumerate eligible models + copillm discovery metadata. |
GET | /v1/models | OpenAI-style alias for model discovery. |
GET | /codex/v1/models | Codex-flavored model list. |
GET | /anthropic/v1/models | Anthropic spec; consumed by Claude Code gateway discovery. |
Health
| Method | Path | Description |
|---|---|---|
GET | /healthz | Readiness probe. |
GET | /livez | Liveness probe. |
Inference
| Method | Path | Description |
|---|---|---|
POST | /v1/chat/completions | OpenAI Chat Completions, streaming or not. |
POST | /v1/messages | Anthropic Messages. |
POST | /anthropic/v1/messages | Anthropic Messages (alias). |
POST | /codex/v1/responses | Codex Responses API. |
Example: OpenAI SDK
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:4141/v1",
api_key="copillm-local",
)
resp = client.chat.completions.create(
model="gpt-5",
messages=[{"role": "user", "content": "hello"}],
)
print(resp.choices[0].message.content)
Example: Anthropic SDK
from anthropic import Anthropic
client = Anthropic(
base_url="http://127.0.0.1:4141/anthropic",
api_key="copillm-local",
)
msg = client.messages.create(
model="claude-sonnet-4.6",
max_tokens=1024,
messages=[{"role": "user", "content": "hello"}],
)
print(msg.content[0].text)
Error handling
When an upstream Copilot API call fails, copillm forwards the upstream HTTP status code through to the caller and returns a sanitized error payload — agents see a real error code instead of a generic 502. Auth tokens, headers, and request bodies are never echoed back in the response.
OpenAI-shaped routes (/v1/chat/completions, /codex/v1/responses) return:
{
"error": {
"type": "upstream_rate_limited",
"code": "rate_limit_exceeded",
"message": "rate_limit_exceeded: ...",
"upstream_status_code": 429,
"request_id": "..."
}
}
Anthropic-shaped routes (/v1/messages, /anthropic/v1/messages) return the same fields under {"type": "error", "error": {...}}. Streaming Anthropic responses surface the failure via an SSE error event.
type and code come from the upstream body when available; otherwise they fall back to the copillm error category. Categories the daemon emits today:
| Category | Conditions |
|---|---|
upstream_auth_error | Upstream returned 401 or 403. |
upstream_rate_limited | Upstream returned 429. |
upstream_server_error | Upstream returned 5xx. |
upstream_request_error | Upstream returned 4xx (other than the above). |
upstream_error | Any other non-2xx response. |
Failures inside the daemon (rather than upstream) surface as a 5xx with a { "error": "<kind>", "detail": "..." } shape:
- A malformed or missing upstream stream returns
502with"error": "invalid_upstream_response". - An unexpected daemon-side error returns
500with"error": "internal_error".
If you see either of these in a coding-agent error message, the daemon itself failed — not Copilot upstream. Re-run with copillm --debug start to capture the interaction in ~/.copillm/debug.log.
Daemon-side request errors
Two errors come from the daemon validating your request, before any upstream call. Both use the { "error": "<kind>", "detail": "..." } shape:
| Status | error | Cause |
|---|---|---|
404 | account_not_found | The request carried an /<account> prefix for an account that has no stored credential. Run copillm auth status to see which accounts exist. |
413 | payload_too_large | The request body exceeded the size cap. The daemon stops reading and rejects the request before buffering the whole payload. |
The body-size cap defaults to 32 MiB — far above any real chat/completions payload — and exists so a runaway agent or a pathological context can’t exhaust the single process every agent depends on. Override it with the COPILLM_MAX_REQUEST_BYTES environment variable (a positive integer count of bytes).
Translation caveats
copillm translates between OpenAI and Anthropic wire formats and Copilot’s upstream. Current behavior:
- Anthropic-to-OpenAI image input: Anthropic
imagecontent blocks in user messages — bothbase64andurlsources — are forwarded to Copilot as standard image parts, so vision-capable models can read them. Sending an image to a text-only model surfaces as a normal upstream error. - Sampling parameters:
temperature,top_p, andstop_sequenceson an Anthropic request are forwarded upstream (stop_sequencesmaps to OpenAIstop).top_kandmetadatahave no upstream equivalent and are dropped. - OpenAI-to-Anthropic content parts: model responses are text and tool-use only (responses never carry image parts).
tool_resulterrors: Anthropictool_resultblocks withis_error: trueare translated into the OpenAItoolrole (which has nois_errorfield) with the content prefixed by[tool_error]so the assistant still sees that the tool failed. This lets coding agents recover from tool failures (e.g. a failedBashinvocation or MCP tool error) instead of the whole conversation 400ing.[1m]model id suffix: Ids advertised on/anthropic/v1/modelsmay carry a[1m]suffix when the model id containsopusand the upstream model reportsmax_context_window_tokens >= 1_000_000. The suffix is stripped back off before any request is forwarded upstream, so canonical model ids are always what Copilot sees. See the Claude Code guide for why.