The Anthropic adapter (daemon/halton_meter/adapters/anthropic.py) is
the most mature surface in Halton Meter. It owns the host
api.anthropic.com and meters every call to the /v1/messages family
of endpoints — request and streaming-response bodies parsed,
input/output/thinking/cache tokens captured, latency recorded, cost
computed against the active rate card.
What’s metered
| Path | Modes |
|---|---|
/v1/messages | Standard, streaming, extended-thinking, batch |
/v1/messages?... (query variants) | Same |
Non-/v1/messages* paths (model list, account metadata, OAuth
control-plane) are observed but not metered. They pass through
unchanged.
Captured fields
For every metered request, the adapter writes one row into requests:
provider = "anthropic"model— from the response (not the request) so model-mapping redirects are honouredmode—standard,streaming,thinking,batchinput_tokens—usage.input_tokensoutput_tokens—usage.output_tokensthinking_tokens—usage.thinking_tokens, when extended thinking is oncache_read_tokens—usage.cache_read_input_tokenscache_write_tokens—usage.cache_creation_input_tokenscost_usd_minor_units— computed against the active row inpricing_rates
The cache columns are intentionally separate. Renamed from
cached_tokens on 2026-04-30 once cache-write became billable on a
different schedule from cache-read.
Streaming
Streaming responses (text/event-stream) are buffered through the
adapter, parsed event by event, and the final message_stop event’s
usage block becomes the row. If the stream is truncated mid-flight,
the row is still written with tokens_complete = false so partial
captures don’t silently inflate totals.
Tools that route through this adapter
| Tool | Path |
|---|---|
| Claude Code (Node) | NODE_EXTRA_CA_CERTS + HTTPS_PROXY |
| Anthropic Python SDK | certifi bundle (patched by init) |
curl against api.anthropic.com | system keychain (curl) or CURL_CA_BUNDLE |
| Cursor / Windsurf with Claude back-end | Node, same as Claude Code |
For the per-tool TLS-trust matrix, see How clients see your CA.
Verifying capture
$ halton-meter run -- curl -sS https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-haiku-4-5","max_tokens":8,...}'
$ halton-meter report --since 5m --by model Expect one row in the report against claude-haiku-4-5. If nothing
appears, walk
No captures in halton-meter report.
Error classification
When an Anthropic call returns an error, the adapter classifies it into one of the seven canonical buckets — see Error classification. Shipped in v0.3.0.
| HTTP | Provider error.type | error_class | retryable |
|---|---|---|---|
| 400 | invalid_request_error | bad_request | false |
| 401 | authentication_error | auth | false |
| 403 | permission_error | auth | false |
| 404 | not_found_error | bad_request | false |
| 408 | request timeout | timeout | true |
| 413 | request_too_large | bad_request | false |
| 429 | rate_limit_error | rate_limit | true |
| 500 | api_error | server_error | true |
| 529 | overloaded_error | server_error | true |
| — | network / connect failure | network | true |
HTTP 529 is a provider-overload signal, not a per-key throttle, so it
buckets as server_error (not rate_limit) and stays
retryable=true. See the judgement-call note on the
concept page.
Host matching
The adapter declares its host as api.anthropic.com exactly. Host
matching is exact-equality (with optional :port suffix) — never
prefix or suffix matching — to defend against api.anthropic.com.evil.com
and evil.api.anthropic.com.