Skip to content
Ledger by RODMENA
Menu

Documentation

Authentication

How credentials work in the Ledger API. One key shape, four roles, expiry and revocation enforced by the ledger, and what to do when a key leaks.

This page was last updated on 2026-10-15.

The short version

Ledger takes one kind of credential, an API key issued by auth.rodmena.co.uk and presented as Authorization: Bearer rak_…. The ledger holds no key capable of minting a credential it will accept.

Why that sentence matters

Until 2026-08-22 the ledger verified HS256 tokens signed with a secret it also held. That meant anyone who could read LEDGER_JWT_SECRET, whether from the service environment, a systemd unit or a backup, could mint a token for any tenant with any roles and move money. Verification and signing were the same key.

That arrangement no longer exists. The ledger now holds only its own namespace key at the authorization service, which cannot forge a tenant’s credential. The probe probe_no_symmetric_token.py asserts it continuously. The service must start with no LEDGER_JWT_SECRET present, it must refuse a forged token signed with any historical secret, and no token issuer may ship in the distribution.

Roles

RolePermissionsUse
ledger_readerledger_readreporting, dashboards
ledger_posterledger_read, ledger_postthe normal application key
ledger_admin+ ledger_adminalso creates asset types and accounts

ledger_poster deliberately includes read, because a poster that cannot read balances is unusable and posting responses return them.

The credential carries its own tenant

The authorization service knows the key by a user name of the form lg.<tenant>.<principal>, base64url-encoded. The ledger parses the tenant out of the validated name, scopes the request to it, and then confirms the principal under that tenant’s own row-level security. A forged pairing names a row that is invisible under the wrong tenant. The parsed name is checked before it is used.

For you this means a key belongs to exactly one tenant and cannot be pointed at another. Another tenant’s identifiers return 404.

Expiry and revocation are enforced by the ledger

The authorization service does not expire keys, and returns expires_at: null and has no expiry parameter. The deadline on your key is the ledger’s own, and the ledger enforces it in its data plane against the key you presented. Revocation is likewise checked on every request. Decisions are cached for about 5 seconds, and a revoked key stops working within that window.

Two consequences worth stating plainly.

Expiry does not bound a leaked key. Because the deadline is ours alone, a key past it is still a valid credential upstream, and it is refused here and nowhere else. If a credential is compromised, the control is revocation, which is immediate. Waiting for an expiry is not a containment plan.

Nothing warns you before a key expires. No job scans expiry, because it is read by the request that refuses on it and by the listings, and that is all. Keys now default to a hundred-year life. You will not inherit this cliff by accident. If you deliberately choose a short one, put the date in your own calendar. A 401 CREDENTIAL_EXPIRED in production is otherwise the first notice you get.

When the authorization service is unreachable

You get 503 AUTHZ_UNAVAILABLE with Retry-After. Never 401 or 403.

This distinction is deliberate and is the most important behaviour in this document. “You are denied” and “we could not ask” are different answers. Returning a denial for an outage gives a valid caller a final-looking error their client will not retry, and caching it turns a one-second blip into a minutes-long lockout. If you see 503, back off and retry. Your own credential is fine.

There is a second, rarer case: 503 AUTHZ_CREDENTIAL_REJECTED means the authorization service is up and refusing the ledger’s own credential. This one is also not yours to fix. It is retryable, and it pages us.

If a key leaks

Tell the operator straight away. Revocation is one command and takes effect within seconds. You will get a new key, because the old secret cannot be recovered. Keys are capped at 25 per principal.

What is not here yet

You cannot mint or rotate your own keys, and an operator does it for you (issuedb #65/#66). There is no OAuth flow, no session, and no user login for the API. Those belong to the tenant console, which does not exist yet.