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-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.

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.

Machine-readable: this page as markdown, the whole product as llms-full.txt.

Next: what it costs

See pricing