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 and 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-runprints 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.
npm install @r3ckon/sdk
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:
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 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.