# Connecting usage

The ledger needs one thing from you: token metadata. Model names, token
counts, regions, timestamps. Never prompts, never completions.

This page is the *why*: which paths exist, what each costs you in accuracy,
and why we refuse some credentials outright. **The step-by-step lives in the
console**, where each option carries its own live controls and generates
your key already filled into the commands.

## The rule about credentials

**We do not accept a credential that can change anything in your account.**

Reading organization usage from Anthropic or OpenAI requires an Admin key,
and those keys are not read-only. An Anthropic Admin API key can remove
organization members, invite people into your org, change roles, and
deactivate your production API keys. OpenAI's administers users, invites,
and projects. Neither offers a usage-only variant.

So for those providers the sync runs on your machine and only token counts
travel. A provider gets a managed connection here only when its usage can
be read by a credential scoped to read nothing else, which today means
Google Cloud, where a service account can hold `roles/monitoring.viewer`
and nothing more.

## Accuracy is the other axis

Aggregate usage APIs do not report everything. Where a provider does not
break out cached or reasoning tokens, we record them as unreported, which
is deliberately conservative and widens your estimate. Capturing usage in
your own code gives us the exact numbers.

The safest path is therefore also the most accurate one, which is a
convenient thing to be able to say honestly.

| Path | Credential we hold | Effort | Accuracy |
|---|---|---|---|
| `@r3ckon/sdk` in your code | none | one import, one wrap | exact cache and reasoning counts |
| OpenTelemetry (OTLP) | none | one exporter setting | no cache or reasoning detail |
| Sync script | none | one cron job | daily provider aggregates |
| Google Cloud connection | scoped read-only service account | paste once | no cache or reasoning detail |
| CSV upload | none | occasional, manual | as good as your export |
| Modelled from questions | none | about thirty seconds | wide, honestly stated range |

## What the sync scripts do

Worth knowing before you run something against a live account:

- [anthropic.mjs](/sync/anthropic.mjs) and [openai.mjs](/sync/openai.mjs)
  have no dependencies and need Node 18 or newer.
- Your provider key is used only to call your provider. It is never sent to
  us, never printed, and never written to a log.
- `--dry-run` prints the exact payload and sends nothing.
- How far back you can go is your provider's decision, not ours. The
  scripts page through history in 31-day buckets and accept up to 400 days,
  but provider usage APIs retain a limited window and do not document it.
  If yours stops short, the console can project the rest from what you did
  get rather than leaving the period blank.
- Re-running a window restates it rather than adding to it, so overlapping
  cron runs are safe.

Keep the admin key out of your shell history by putting it in a file rather
than exporting it inline, and create a dedicated key for the script so you
can revoke it independently.

## Capture in your code: the SDK

The tightest estimates come from the response object itself, because it
carries exact cache reads and reasoning counts that no aggregate usage API
exposes. The SDK reads them off every completed call and batches them here.

```bash
npm install @r3ckon/sdk
```

```js
import Anthropic from "@anthropic-ai/sdk";
import { instrument } from "@r3ckon/sdk";

const anthropic = instrument(new Anthropic(), {
  apiKey: process.env.R3CKON_INGEST_KEY,   // aiok_ingest_...
  connection: "checkout-service",           // groups usage in the console
  region: "us-east-1",                      // optional, improves grid accuracy
});
```

Use the client exactly as before. The wrapper never alters a response, never
blocks a call, propagates provider errors unchanged, and drops events rather
than growing memory if this service is unreachable, because instrumentation
that can break a production request is worse than no instrumentation.

It wraps Anthropic clients (`messages.create`) and OpenAI clients
(`chat.completions.create`, `responses.create`). That second shape also
covers **OpenAI-compatible gateways such as OpenRouter**: point the OpenAI
client at the gateway's base URL and instrument it the same way, and every
model the gateway routes to is captured through one wrap. Streaming calls
return a stream rather than a usage object, so nothing is recorded for them
rather than something invented.

Short-lived scripts should flush before exit:

```js
import { reporterFor } from "@r3ckon/sdk";
await reporterFor(anthropic).shutdown();
```

## Already running OpenTelemetry

If your stack exports OpenTelemetry metrics, point it at us and you are done.
No credential of any kind changes hands: your exporter pushes token counts,
the same way it already pushes everything else.

Set the endpoint to `https://r3ckon.com/api/v1/otlp` and send your ledger
ingest key as the authorization header. Exporters append `/v1/metrics`
themselves, so that base URL is the whole configuration.

```
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://r3ckon.com/api/v1/otlp
OTEL_EXPORTER_OTLP_METRICS_HEADERS=authorization=Bearer aiok_ingest_...
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta
```

Protobuf and JSON encodings are both accepted, so you do not need to change
your exporter's protocol.

The temporality setting is a requirement rather than a preference, and the
endpoint says so plainly if it is missing. A cumulative counter reports a
running total since your process started, so adding successive exports would
multiply your usage, and a process alive for a week carries a total spanning
days with no way to split it. Delta means "usage during this interval", which
is the only thing that can honestly be attributed to a day. We refuse
cumulative rather than guess.

We read the standard GenAI metric, `gen_ai.client.token.usage`, along with
`gen_ai.token.type`, `gen_ai.request.model`, and `gen_ai.provider.name`. Any
other metric in the payload is ignored and named back to you in the response,
so a misconfiguration shows up as a list rather than as silence.

**Accuracy note.** The GenAI metric has no token type for cached or reasoning
tokens; only input and output exist. We therefore record both as unreported,
which under our methodology widens your range rather than narrowing it. That
is the honest direction, but it does mean an OTLP account carries a wider
band than one capturing in code. If you want the tighter figure, capture in
your own code and post to the metering API.

**Content.** If any attribute carries prompt or completion text, the whole
payload is rejected and nothing is stored. We would rather you discover your
instrumentation is oversharing than trust us to have discarded it. Turn off
content capture on your GenAI instrumentation and send again.

## No API at all

Claude Pro and Max, ChatGPT Plus and Pro, and the Gemini app publish no
usage API at any permission level, so there is no token count for anyone to
read, including us. Rather than guess quietly, we
[model it from a few questions](/estimate) and carry the extra uncertainty
through the arithmetic. Figures produced that way are labelled
self-estimated everywhere they appear.

## Everything ends up at the metering API

Every path above ultimately posts token metadata to the same endpoint. If
you run a gateway, a self-hosted stack, or an internal pipeline, you can
post to it directly: [Metering API](/docs/metering-api).
