API documentation

Compatible with the OpenAI client libraries, and with the Claude CLI.

Quickstart

The shortest path needs no code at all. If you use the Claude CLI, point two environment variables at us and run it — there is nothing to install, no wrapper to adopt, and no model to configure.

# Claude Code appends /v1/messages itself — no /v1 on this line.
export ANTHROPIC_BASE_URL="https://api.claudcli.com"
export ANTHROPIC_AUTH_TOKEN="sk-your-key-here"

claude

On Windows the same two lines are set rather than export, and they belong in the window you run the CLI from:

set ANTHROPIC_BASE_URL=https://api.claudcli.com
set ANTHROPIC_AUTH_TOKEN=sk-your-key-here

claude

A new window inherits whatever the machine already has set. If ANTHROPIC_BASE_URL or ANTHROPIC_API_KEY is defined for the whole machine — some tools set it for you — then a window where you did not type the lines above will use those values instead, and nothing about the CLI's output says so. Type them in every window, and run set ANTHROPIC_ when a run seems to be answering from somewhere else.

That is the whole setup. Left to itself the CLI asks for the model it ships with; we serve the equivalent tier, and go on serving it when a new version of that model is released. Choosing a model yourself is optional:

# Optional — pick the tier instead of letting the CLI pick it.
export ANTHROPIC_MODEL="Haiku 4.5"
export ANTHROPIC_SMALL_FAST_MODEL="Haiku 4.5"

The mistake that is easy to make here is documented under Claude Code: the base URL takes no /v1.

Or, with the OpenAI SDK

Any OpenAI-compatible client works too, and there is no separate library to learn. The only differences from the OpenAI defaults are base_url and api_key.

pip install openai
from openai import OpenAI

client = OpenAI(
    base_url="https://api.claudcli.com/v1",
    api_key="sk-your-key-here",
)

response = client.chat.completions.create(
    model="Haiku 4.5",
    messages=[{"role": "user", "content": "Explain vector databases briefly."}],
)

print(response.choices[0].message.content)

Authentication

Every request carries your API key as a bearer token. Keys are created in the dashboard and shown once, at the moment they are created — we store only a hash, so a lost key is replaced rather than recovered.

Authorization: Bearer sk-your-key-here

A key that has been revoked, an account that has been suspended, and a key that never existed all return the same response. That is deliberate: it means a key you have cancelled tells an attacker nothing about whether it was ever real.

Chat completions

POST /v1/chat/completions

The request body follows the OpenAI schema. These fields are supported:

FieldTypeNotes
modelstringRequired. One of the model ids below.
messagesarrayRequired. The conversation so far.
max_tokensintegerClamped to the model's ceiling and to your remaining balance.
temperaturenumber
top_pnumber
stopstring or array
streambooleanSee Streaming.
toolsarrayTool calling is supported.
tool_choicestring or object
response_formatobjectJSON mode where the model supports it.
seedinteger

Unknown fields are dropped rather than forwarded. If a parameter you rely on is not listed above, send us the request shape and we will add it — but it will not silently reach the model in the meantime.

Streaming

Set stream: true and consume the response as server-sent events, exactly as with the OpenAI SDK. The final frame is data: [DONE].

stream = client.chat.completions.create(
    model="Haiku 4.5",
    messages=[{"role": "user", "content": "Count to ten."}],
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta.content if chunk.choices else None
    if delta:
        print(delta, end="", flush=True)
Note on the final chunk. The last frame before [DONE] carries the token counts and has an empty choices array. If your loop indexes choices[0] without checking, that frame will raise — this is standard OpenAI behaviour and the snippet above guards for it.

Long silences are kept alive with SSE comment frames. Every client library ignores them, and they exist so an idle connection is not closed by a proxy while the model is still thinking.

Claude Code

POST /v1/messages

The Claude CLI does not speak the OpenAI format, and it has no setting to make it. So the same models are also served over the Anthropic Messages API at the path above, and the CLI works against this service unmodified. Requests are translated on the way out and back, so everything else on this page — token accounting, streaming, tool calling, the balance — behaves identically on either endpoint.

The setup is in the Quickstart; what follows is what each variable does, and which of them you can leave out.

VariableNotes
ANTHROPIC_BASE_URL The API host, with no /v1 — the CLI adds the path itself, so a version prefix here produces /v1/v1/messages. This is the one people get wrong, because every other snippet on this page does take the prefix. A doubled prefix is absorbed rather than refused, so it will still work — set it correctly anyway, because it is the request the CLI means to send.
ANTHROPIC_AUTH_TOKEN Your sk- key. ANTHROPIC_API_KEY works too and is sent as a different header; both carry the same credential.
ANTHROPIC_MODEL Optional. Pins the model the CLI reasons with to one of the ids under Models. Left unset, the CLI asks for the model it ships with and we serve the tier that belongs to.
ANTHROPIC_SMALL_FAST_MODEL Optional. The model for background work — summaries, titles and similar. Left unset, the CLI asks for its own small model, which is served here too. Setting it is a choice, not a requirement.
CLAUDE_CODE_MAX_CONTEXT_TOKENS Optional, and worth setting on a large codebase. The CLI decides how much context it has from its own model catalogue and not from ours, so it can assume far less room than the model you are actually talking to. Set it to the context window published under Models to use all of it.

Leaving the model unset

With neither model variable set, the CLI sends its own default ids. Those are served here, and each is matched by family — every Opus id is the Opus tier, every Haiku id the Haiku tier — so a version released after this page was written resolves without anyone having to add it first.

Those ids are not the ones under Models. The catalogue publishes our own names, deliberately; both resolve, and a family id is answered at whichever tier that family names. Set ANTHROPIC_MODEL to a catalogue id when you would rather be explicit about which tier you are buying.

What the CLI sends is translated into the same request as /v1/chat/completions, so a session is billed, limited and metered exactly as any other request. Errors come back in the Anthropic envelope — {"type": "error", "error": {...}} — while the status codes and the codes listed under Errors are the same on both.

Models

GET /v1/models
GET /v1/models/{model_id}
Model id Name Context tokens Max output tokens
Haiku 4.5 Haiku 4.5 200,000 64,000
Opus 5 Opus 5 1,000,000 128,000
Sonnet 5 Sonnet 5 1,000,000 128,000

Tokens and billing

Your balance is a number of tokens. It moves in three steps, and all three are visible in the dashboard:

  1. Reserve. Before your request is sent, we hold an upper bound on what it could cost, based on the prompt length and max_tokens. If the hold does not fit in your balance, the request does not start and nothing is sent.
  2. Serve. The request runs. You are not charged for the hold — it is a hold, not a charge.
  3. Settle. When the response finishes, the hold is replaced by the token count the model reported. The difference is returned to your balance immediately.

This is why a request can stop on balance while you still have tokens: the hold is an upper bound on the whole request, so a prompt larger than your remaining balance does not start, rather than failing part-way through. Where your balance covers the prompt but not the full output you asked for, the output is trimmed to what it can afford and you get a shorter answer instead.

Charging is based on the counts the model reports, not on our own estimate. If a stream ends before those counts arrive — because the connection dropped — you are charged for the prompt plus what was actually delivered, never for the full hold. Each ledger row says which of the two it was.

Errors

Errors use the OpenAI envelope, so existing error handling keeps working.

{
  "error": {
    "message": "Incorrect API key provided.",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_api_key"
  }
}

On /v1/messages the same errors come back in the Anthropic envelope instead, which is what the CLI expects. The status codes below and the codes in the table are identical on both; only the wrapper differs.

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "Incorrect API key provided."
  }
}
StatusMeaningWhat to do
400 The request body was malformed. Check the field named in param.
401 The API key was not accepted. Check the key. Do not retry — this will not fix itself.
402 Your token balance cannot cover this request. Add tokens, then send the request again — a retry loop will not help.
429 Rate limit or concurrency limit reached. Wait for the interval in Retry-After.
502 The model returned an error. Retry with backoff.
503 The service is temporarily unavailable. Retry with backoff. We are already looking at it.
504 The request timed out. Retry, or lower max_tokens.

A failed request is not billed. If the model rejects the call, or the service is unavailable, your balance is untouched.

Limits

LimitDefaultNotes
Requests per minute Unlimited Per API key. We will set one for you on request.
Concurrent requests Unlimited Per API key. Streaming requests hold their slot until they finish.
Maximum request body 8 MiB Enforced while reading, not after.

There is no request rate limit and no concurrency limit by default. A request is stopped by your balance and by nothing else. Your own limits are shown on the dashboard, which is the authoritative number if it differs from the defaults here.