Read-only by design: how TokenLedger handles your keys

14 July 2026 · 4 min read

TokenLedger asks for API keys, and asking for API keys deserves a precise answer to "what exactly can you do with them". This post is that answer. The short version: the keys we accept cannot spend money, cannot run inference, and cannot read your prompts — not because we promise to behave, but because the key types themselves do not have those permissions.

Keys that cannot spend

We do not accept standard inference keys. Each provider offers an administrative or restricted key type whose scope is reading usage and billing data, and those are the only types TokenLedger connects.

| Provider | Key type | Can read | Cannot do | | --- | --- | --- | --- | | Anthropic | Admin key (sk-ant-admin...) | Usage and cost reports via the Admin API | Call the Messages API — no inference, no spend | | OpenAI | Admin key | Organisation usage and costs endpoints | Call completions, responses or any inference endpoint | | OpenRouter | Management key | Key list, usage and credit data | Spend credits on completions | | Stripe | Restricted key | Customers, Subscriptions, Invoices — Read only | Create charges, issue refunds, modify anything |

The distinction matters. An Anthropic admin key sent to the Messages API is rejected by Anthropic, not by a policy of ours. An OpenAI admin key cannot produce a single token of inference. An OpenRouter management key can enumerate usage but cannot place a request that costs credits. The Stripe key is a restricted key you create yourself, with Read granted on exactly three resources; Stripe enforces the rest.

If TokenLedger were compromised entirely, the keys in our database could be used to read usage reports. That is the blast radius. Nobody can run up your bill, impersonate your product or move money with them.

Provisioning guides for each provider are in the docs, including which scopes to leave off. If you paste a standard inference key by mistake, we reject it at validation and tell you which key type to create instead.

How keys are stored

Keys are encrypted at rest with AES-256-GCM. Each record gets its own randomly generated IV — no IV reuse across records — and GCM's authentication tag is verified on every decrypt, so a tampered ciphertext fails closed rather than decrypting to garbage. The encryption key lives in the application environment, separate from the database; a database dump alone yields ciphertext.

Decryption happens in exactly one place: server-side, at sync time, when the scheduler wakes to pull your usage data. The plaintext key exists in memory for the duration of the API calls and is not written anywhere. Keys are never sent to the browser, never embedded in client bundles, never included in API responses to the frontend.

What you see in the app

After you save a key, the full value is gone from the interface permanently. The dashboard shows a masked form — sk-ant-...4f2a — enough to tell your keys apart, useless for anything else. There is no "reveal" button. If you lose a key, you rotate it at the provider and paste a new one; we deliberately cannot show it back to you.

What we never see

The usage endpoints we read return aggregates: token counts, model names, request counts, costs, timestamps, key and workspace identifiers. They do not return prompts, completions, embeddings inputs or any message content — the providers do not expose content through these APIs at all. There is no configuration of TokenLedger that reads what your users typed. The most sensitive fact we hold about your traffic is how much of it there is and what it cost.

Logs

Application logs are structured, and key material is redacted before writing — a scrubber runs over log payloads and error objects, since exception messages are the classic place secrets leak. Provider API errors are logged with the key's internal ID and masked suffix, never the credential. The same applies to Stripe data: log lines reference internal record IDs, not customer details.

Leaving

Your data is yours, including on the way out. You can export everything — synced usage rows, computed costs, findings — as CSV or JSON at any time, no support ticket required. If you delete your account, we hard-delete your key records and synced data within 24 hours: encrypted key material, usage rows, revenue joins, the lot. Not a soft-delete flag — the rows are gone. We keep invoices we are legally required to keep, and nothing else.

One caveat worth stating plainly: deleting keys from TokenLedger does not revoke them at the provider. Rotate keys at the source when offboarding any vendor, including us. A read-only usage key that outlives its purpose is a small risk, but small risks are still risks, and this post is not in the business of rounding them to zero.

That is the whole design. Keys that cannot spend, encrypted with AES-256-GCM under per-record IVs, decrypted only server-side at sync time, masked everywhere, redacted from logs, exportable and hard-deleted within 24 hours. If your security team has questions this post does not answer, send them over — the honest version of this document is the only one worth publishing.