Anthropic
Claude directly, over the Messages API — not a chat-completions shim. The system turn is a top-level field and the reply arrives as content blocks, so this provider carries its own HTTP shape while sharing every prompt with the rest.
Good for
- Teams that want Claude and already have an Anthropic account.
- Instruction-following on the classifier's strict JSON contract, which is the part that decides whether the AI layer contributes anything at all — a reply that is not the demanded shape is discarded, not salvaged.
- A cheap default: the built-in model is a Haiku-class one, chosen because classification is high-volume and low-difficulty.
Configuration
[llm]
provider = "anthropic"
[llm.providers.anthropic]
api_key_env = "ANTHROPIC_API_KEY" # required
model = "claude-haiku-4-5-20251001" # set this explicitly
max_tokens = 512
# api_base = "https://api.anthropic.com"
export ANTHROPIC_API_KEY=sk-ant-...
| Key | Required | Default | Notes |
|---|---|---|---|
api_key_env | ✅ | — | Env var holding the key |
model | — | claude-haiku-4-5-20251001 | Any Claude model the account can reach |
api_base | — | https://api.anthropic.com | Trailing slash trimmed |
max_tokens | — | 512 | Cap on the reply |
The pinned API version (anthropic-version: 2023-06-01) is sent on every
request and is not configurable — pinning it is what keeps a server-side API
change from silently altering verdicts.
api_base has no /v1 hereUnlike the OpenAI-shaped providers, the Anthropic default base is
https://api.anthropic.com — the version segment is part of the request path
the provider builds, not of api_base.
Choosing a model
| Want | Try |
|---|---|
| Cheapest sensible default | a Haiku-class model (the built-in default) |
| Better judgement on borderline messages | a Sonnet-class model |
| Deeper reasoning than this job needs | an Opus-class model — almost certainly overkill |
Anthropic model ids are dated (claude-haiku-4-5-20251001). That is a
feature: pinning the date means today's verdicts are still today's verdicts
next month. Set model explicitly and update it on your own schedule.
Both jobs are small. Classification is a one-line JSON verdict; a rewrite is one short message, not an essay. There is no reason to reach for a large model unless you have measured the borderline cases and found the small one wanting.
list_models returns an empty list for this provider — the dashboard's model
picker has no catalog to browse here, so type the id. If you want a live
picker, put OpenRouter first in the chain.
The strict-JSON contract
The classifier is given the exact reply shape verbatim in the system prompt, so the model has no format latitude:
{"verdict":"clear"|"low_context","confidence":0.0-1.0,
"missing":["details"|"link"|"recipients"],
"reasons":["short human sentence for the author"]}
Anything else is an error, and an error is read as "no opinion" — the heuristic verdict stands. The parser tolerates fenced or prose-wrapped JSON, but it will not invent a verdict out of a reply that does not contain one.
Cost and privacy
- Only ambiguous messages are sent. Clear passes and textbook fails never
leave the process. Under
sensitivity = "conservative"nothing is sent. - What is sent: the message text and the team charter; for a rewrite, resolved context items too.
- Replies are capped at
max_tokens(512 by default). - Requests time out after 30 seconds and fall through to the next provider.
- If message content must not reach a vendor at all, use Ollama.
Errors and secrets
Non-2xx bodies are quoted as a capped excerpt (300 characters) so an HTML
error page cannot flood the logs, and the API key never appears in an error —
it exists only in the x-api-key header.
A 401 is the key; a 404 on a valid key is usually a model id the account cannot reach, often a stale date suffix; a 429 is what the fallback chain exists for.