Beta

API reference

OpenTelemetry

POST /v1/logs — token and cost usage from Claude Code and Codex telemetry (OTLP/HTTP with JSON).

Agentower accepts OpenTelemetry logs over OTLP/HTTP with JSON encoding. Telemetry adds token counts (and, for Claude Code, a cost estimate) to agents; it never changes an agent's state — hooks do that. Scope: telemetry or ingest.

Set your exporter's endpoint to Agentower's address (the exporter adds /v1/logs itself when you use OTEL_EXPORTER_OTLP_ENDPOINT), the protocol to http/json, and the key as a header:

OTEL_EXPORTER_OTLP_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.agentower.com
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer at_live_…

Protobuf isn't supported: a protobuf request gets 415 with a hint to switch to http/json.

#Which key

These three routes — POST /v1/logs, /v1/metrics and /v1/traces — accept a telemetry key or an ingest key. A telemetry key works only here — every other route refuses it — so it's the safer one to put in a tool's settings file, which the agent's own commands can read. If a telemetry key leaks, it can add usage numbers to your board and nothing else. See Keys and scopes.

Create a telemetry key on Keys & connections (step 4, Create a telemetry key) — the settings block there then carries it, along with OTEL_LOGS_EXPORT_INTERVAL and OTEL_METRIC_EXPORT_INTERVAL set to 60000 (send once a minute), and the ~/.agentower/claude-code.env snippet gains an AGENTOWER_TELEMETRY_TOKEN line. The Claude Code setup command (node scripts/claude-code.mjs connect) puts AGENTOWER_TELEMETRY_TOKEN from that file into Claude Code's settings, with the same two intervals; without that line it uses your ingest key and warns you. The Agentower plugin doesn't set up telemetry. See Connect Claude Code.

#POST /v1/logs

Agentower reads these log records and ignores all others:

Toolservice.nameLog recordAttributes read
Claude Codeclaude-codeclaude_code.api_requestsession.id, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, cost_usd, vcs.repository.name
Codexstarts with codexcodex.sse_event with token countsconversation.id, input_token_count, output_token_count, reasoning_token_count

For Claude Code, tokens in = input + cache read + cache creation tokens. model is the model that served the request (e.g. claude-haiku-4-5-20251001 for a subagent), so tokens are counted per model — see Usage. Without it, the tokens count under the session's model. Everything else in a record — including any prompt text — is dropped, and never stored.

One Claude Code API request
curl -X POST "$AGENTOWER_URL/v1/logs" \
  -H "authorization: Bearer $AGENTOWER_KEY" \
  -H "content-type: application/json" \
  -d '{"resourceLogs":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"claude-code"}}]},
       "scopeLogs":[{"logRecords":[{"timeUnixNano":"1790000000000000000","attributes":[
         {"key":"event.name","value":{"stringValue":"api_request"}},
         {"key":"session.id","value":{"stringValue":"3f1c9a52-docs-example"}},
         {"key":"input_tokens","value":{"intValue":"1200"}},
         {"key":"output_tokens","value":{"intValue":"350"}},
         {"key":"cost_usd","value":{"doubleValue":0.021}}]}]}]}]}'
Response · 200
{}

An empty object is OTLP's "everything accepted". Usage is added to the session with the same session.id as its hook events.

#Metrics and traces

POST /v1/metrics and POST /v1/traces are accepted (so exporters don't keep retrying) and ignored. They answer {}.