check before an action asks whether the customer can afford it, and track after it succeeds spends the credits.
The examples continue from Setup: a chat_message feature that costs 1 credit, and a customer on the Free plan.
Check, run, track
checknever spends. It returns{ "allowed": true, "availableCredits": 50, "creditCost": 1 }, andallowed: falsewith a200when the balance can’t cover the cost. That’s the moment to offer an upgrade or a credit pack.- Track only what succeeded. If the action failed, don’t track it and the customer isn’t charged.
- The request ID makes
trackidempotent. Use an ID you already store with the action: a message ID, a job ID, or a UUID you save with it. Up to 255 characters, and one ID belongs to one feature. - Retry with the same ID. If a
trackresponse is lost, send it again with the samerequestId. Ledger returns the original result with"idempotent": trueand charges nothing more. A new ID records a second use.
track returns what it spent:
Response
Handle a refused track
check doesn’t reserve credits, so another request can spend the last credit between your check and your track. track decides again in its own transaction and can refuse with a 409. Nothing is charged, and the same request ID can succeed later, for example after the customer buys a pack.
A
404 customer_not_found or feature_not_found means the customer or feature doesn’t exist with this key’s environment. Errors lists every code.
When Ledger is unreachable
The SDKs fail open by default:check is allowed and track is replayed later with the same request ID, both marked failedOpen: true, so an outage never blocks your users. Pass failOpen: false (Python fail_open=False) to handle outages yourself.
Call Ledger from your backend only, never from a browser or mobile app. Usage in the dashboard lists every tracked request, charged or blocked, with the reason.
Other ways. Without an SDK, call
POST /v1/check and POST /v1/track; over HTTP the request ID field is request_id.