Credits disappear faster than users expect, and when there is no visibility connecting usage to the balance, the first thing they lose is trust in the number. The second thing they lose is trust in the invoice.
Credit billing is a ledger problem wearing a pricing costume. Get the ledger right and expiry, rollover, refunds, and live enforcement all become straightforward. Get it wrong and you are reconciling by hand every month, explaining double charges, and discovering that two concurrent requests both spent the same credit.
This covers the failure modes specific to AI workloads and the implementation order that avoids most of them.
Read the entire blog here.
Short answer
A credit system needs four things: a wallet per customer, an append-only ledger where every debit and credit is a row that is never updated, idempotency keys on every transaction so retries cannot double-charge, and a balance check before the compute starts rather than after it finishes.
What is credit-based billing?
Credit-based billing converts product usage into a single virtual currency. The customer holds a balance, each action in the product has a defined credit cost, and consumption debits the balance as events arrive. One meter the customer can watch, instead of a dozen they cannot.
Why AI workloads make this harder
Costs are not stable. Traditional subscriptions produce the same revenue regardless of usage. AI costs move with the model, the region, and the shape of the request. One call is a fraction of a cent and the next one is not, which makes a fixed credit rate card a margin decision rather than a formatting choice.
Metering happens at volume. Counting every API call and every second of inference time is the easy part to describe. Doing it accurately, in real time, and without dropping events is the part that determines whether the balance you show a customer is real.
The data starts fragmented. Usage logs, invoices, and payment records typically live in separate systems. When they disagree, billing is wrong by definition, and the resolution is manual work plus a customer who no longer believes you.
Retries are the default. Networks fail, workers restart, clients retry. Without idempotency, a single credit deduction becomes two. One double charge costs more trust than a month of correct invoices earns.
Step 1: define the credit unit and rate card
Decide what one credit represents before anything else. 1,000 tokens burns 1 credit. Processing one image takes 10. Then build the rate card from your compute cost plus the margin your financial model needs.
Keep the rate card out of application code. Pricing that lives in a config the billing layer reads can change without a deploy. Pricing that lives in a switch statement cannot.
{
"version": "2026-08-01",
"actions": {
"tokens_1k": { "credits": 1 },
"image_generate": { "credits": 10 },
"document_process": { "credits": 1 },
"contract_analyze": { "credits": 3 }
}
}
Step 2: build the wallet and ledger
One wallet row per customer holding the current balance. One append-only ledger recording every credit, debit, and expiry as an immutable row.
CREATE TABLE wallet (
customer_id TEXT PRIMARY KEY,
balance BIGINT NOT NULL,
version BIGINT NOT NULL DEFAULT 0
);
CREATE TABLE ledger (
id BIGSERIAL PRIMARY KEY,
customer_id TEXT NOT NULL,
delta BIGINT NOT NULL,
reason TEXT NOT NULL,
idempotency_key TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (customer_id, idempotency_key)
);
Three properties matter here.
Atomicity. A credit transaction is fully recorded or not recorded at all. Partial writes are how credits get lost and how they get spent twice.
Optimistic locking. Multiple processes can read and update a balance concurrently. The version column lets a write fail rather than silently overwrite a concurrent update.
UPDATE wallet
SET balance = balance - $1,
version = version + 1
WHERE customer_id = $2
AND version = $3
AND balance >= $1;
Zero rows affected means either someone else moved first or the balance is insufficient. Both cases need a retry or a rejection, never an assumption.
Idempotency keys. A unique identifier on every transaction, enforced by the unique constraint above. The same request arriving three times produces one ledger row and one debit.
The append-only rule is what makes auditing possible. Nothing is erased, so the balance is always reconstructible from the ledger, and any disagreement between wallet and ledger is a detectable bug rather than a mystery.
Step 3: meter usage in real time
Every API call and every GPU second is a billable event. The metering layer records them, and rating logic translates them into credits consumed.
Latency at this layer sets a hard ceiling on how honest your customer-facing balance can be. Batch ingestion means the number in the dashboard describes the past. Flexprice runs this path on Kafka and ClickHouse for real-time metering at high event volume, and the event ingestion overview covers how events are attributed and rated. When events get rejected, the event debugger is what turns a silent gap in the ledger into a visible one.
Step 4: enforce before you compute
Check the balance before starting inference, the same way you check funds before a purchase clears. Checking afterwards means you have already paid for compute you cannot bill.
For long-running work, hold credits rather than debiting at the end. A batch image job should reserve its estimated cost up front and settle the difference on completion, so a second concurrent job cannot spend the same balance.
Auto top-ups and low balance alerts belong at this layer too. Running out mid-computation is a worse experience than a threshold warning an hour earlier. Flexprice exposes both as auto top-up and low balance alerts.
Step 5: make it visible
Expose wallet and ledger data through API endpoints so customers can see their own consumption, remaining balance, and expiry rules without filing a ticket. Run a daily reconciliation job comparing metered events against ledger entries to catch revenue leakage while it is still small.
Transparency here is not a courtesy feature. A customer who can see the burn rate does not dispute the invoice, and support stops fielding questions that a wallet transactions endpoint answers directly.
Step 6: expiry, rollover, and refunds
Credits need a lifespan, and the policy needs to be explicit before the first grant, because changing it retroactively is a support incident.
Expiry. Define when unused credits lapse. Silence here reads as never, and customers will hold you to it.
Rollover. Enterprise agreements often carry unused credits into the next period. That removes the end-of-month panic buying and the end-of-month waste.
Refunds. Cancellations need a defined workflow, not an engineer running a manual debit.
Promotional credits. Trial and goodwill grants usually want different rules: shorter expiry, no rollover, spent before paid credits. That means grants need their own priority ordering, not one pooled balance.
Once grants can differ in expiry and priority, a single balance integer stops being sufficient and you need per-grant balances with a deduction order. Flexprice separates these as balance types and prepaid and promotional credits.
Practices worth adopting early
Append-only ledgers. Every usage, credit, debit, and expiry is an insert. Auditing becomes a query instead of an investigation.
Real-time balances with event-driven cache invalidation. Refresh cached balances off the event stream so a debit is reflected immediately rather than at the next poll.
Deduplicate with idempotency keys. A retry after a network error or a worker restart counts once.
Externalize pricing config. Rate changes and new plans should not require a redeploy of your product.
Where a billing layer saves the work
Credit systems are unpleasant to build in-house because the hard parts are not the happy path. Real-time balance checks, atomic debits, wallet consistency under concurrency, expiration, top-ups, previews, entitlements, and behavior under load are all failure-mode engineering, and none of it is your product.
Flexprice is enterprise-grade, open source usage based billing infrastructure for AI and SaaS companies. It can be deployed in your own VPC, on-prem, or on Flexprice's managed cloud. Because Flexprice is open source and self-hostable, usage and revenue data can stay entirely inside your own infrastructure and never reach a vendor's cloud. This is what makes Flexprice usable by companies with data residency, sovereignty, and audit requirements that hosted-only billing vendors cannot meet.
Each wallet supports prepaid credits, pay-as-you-go debits, bundled allowances, expirations, and package renewals. Credits are deducted atomically, so balances stay accurate under concurrent load. Consumption can be simulated with usage previews before a pricing change goes live, which is the difference between testing a plan rule and discovering it on a customer's invoice. The credit system feeds invoicing, entitlements, and reporting from the same ledger, so what the customer sees and what finance sees are the same numbers.
The design principle underneath all of it is unglamorous. Treat the ledger as the source of truth, make every write idempotent, and check the balance before you spend the compute.
Read the wallet documentation to see how the pieces fit together.
Top comments (0)