# Claude Code integration

Claude Code (Anthropic's CLI) emits OpenTelemetry telemetry when the `CLAUDE_CODE_ENABLE_TELEMETRY` environment variable is set. Point its OTLP exporter at ActivityLog and you get per-session activity, token usage, model breakdown, and tool calls.

This is currently **the only live Claude data path** — there is no Anthropic-side usage import for subscription accounts. Customers on the paid Anthropic API can also forward usage via the OTel hook the same way.

## What you get

| Visibility | Field on the `Message` row |
|---|---|
| Which model was used | `model` (e.g. `claude-opus-4-7`) |
| Input tokens | `inputTokens` |
| Output tokens | `outputTokens` |
| Cache read tokens | `cacheReadTokens` |
| Cache creation tokens | `cacheCreateTokens` |
| Cost (microcents = USD × 10⁶) | `costMicrocents` |
| User / session | `metadata.user.id` / `metadata.session.id` |
| Tool calls | child messages of the parent generation span |
| Prompt + completion | `body` (Enterprise; truncated on Pro/Free) |

This is the same `gen_ai.*` first-party promotion that applies to any OTLP source — see [`51-ingest-otlp.md`](./51-ingest-otlp.md#llm-telemetry-the-gen_ai-promotion-rule).

## Setup

### 1. Mint a token

Portal → Systems → **New System** named `claude-code` (or whatever) → **New Token** → 5-year retention recommended → Generate.

### 2. Set environment variables

On every machine running Claude Code:

```bash
export CLAUDE_CODE_ENABLE_TELEMETRY=1

# OTLP exporter destinations
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://api.activitylog.com/api/v1/otlp/v1/logs
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.activitylog.com/api/v1/otlp/v1/traces
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

# Auth — bearer token on every export
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer al_REPLACE_WITH_YOUR_TOKEN"

# Identify this user / service
export OTEL_RESOURCE_ATTRIBUTES="service.name=claude-code,user.name=$USER"
```

Restart any Claude Code sessions to pick up the change.

### 3. Verify

Run any Claude Code command:

```bash
claude "what files are in this directory?"
```

Within a few seconds you should see new entries on the portal **Messages** page with `serviceName: "claude-code"` and `type: "ai.generation"` (or similar).

## Cost queries

Once data is flowing, you can query cost rolled up by model / user / day:

```bash
curl "https://api.activitylog.com/api/v1/messages/aggregate?type=ai.generation&groupBy=model&metric=sum(costMicrocents)&from=2026-05-01" \
  -H "Authorization: Bearer YOUR_JWT_HERE"
```

The Claude Code report pack (planned — see [`90-reports.md`](./90-reports.md)) will turn these queries into a dashboard.

## Multi-user setups

If multiple developers run Claude Code on their workstations and you want per-user attribution:

- Set `user.name=$USER` (or `OTEL_RESOURCE_ATTRIBUTES=user.id=alice@example.com`) on each machine.
- All sessions emit under the same token — separation is at the resource-attribute layer.
- Aggregate by `metadata.user.name` in queries:
  ```
  ?type=ai.generation&groupBy=metadata.user.name&metric=sum(costMicrocents)
  ```

If you'd prefer hard isolation (each user's data goes to a separate system), mint one token per user and configure `OTEL_EXPORTER_OTLP_HEADERS` accordingly. Costs are higher in admin overhead but the boundary is real.

## Why not pull from the Anthropic Usage API?

The Anthropic Usage Report API is **paid-API-only** — it doesn't cover Claude subscription usage (Pro / Max plans). Fyin's own use of Claude Code is on subscriptions, so the OTel hook is the only signal source. For paid-API customers the Usage Report API would technically work, but the OTel path is more granular (per-tool, per-session) and avoids a polling worker.

A planned `claude-usage` import worker would have pulled from the Usage API; that work is shelved until a customer needs it.

## Status

**Live** — this is the recommended setup as of 2026-05. The Fyin team uses it themselves for tracking Claude Code spend.

## What's next

| Goal | Doc |
|---|---|
| The OTLP receiver behavior | [`51-ingest-otlp.md`](./51-ingest-otlp.md) |
| Query token usage and cost | [`60-query-api.md`](./60-query-api.md) |
| Claude Code report pack status | [`90-reports.md`](./90-reports.md) |

## Troubleshooting

**Claude Code says telemetry is enabled but nothing arrives.**
Check `OTEL_EXPORTER_OTLP_HEADERS` formatting — the SDK expects `key1=value1,key2=value2` not standard HTTP header syntax. The leading `Authorization=Bearer ` has no extra space inside.

**Per-user `costMicrocents` doesn't match my Anthropic invoice.**
Claude Code reports the per-request cost the local SDK calculated from its model price table. This drifts from the invoice by promotional credits, regional pricing, and rounding. It's directionally accurate — don't reconcile to the cent.
