Skip to main content
Every credit balance is denominated in one of two ways. The denomination decides what a “credit” means, how you display it, and — critically — which balances you’re allowed to add together. Branch on credit_currencies.denomination. Never assume.

The two denominations

Unit credits

Raw counts. “AI Credits”, “Lovable Credits”, “API calls” — an abstract quantity you define. denomination: unit.

Fiat credits

A prepaid currency balance. “$10 of credit”, denominated in an ISO-4217 currency (usd, eur, …). denomination: <currency code>.

Unit credits

A unit credit is just a count. You decide what one credit is worth in your product — one message, one image, one API call, or a share of several features via a credit system. There is no exchange rate to a currency; a “credit” only means something inside your app. Render unit credits with formatCredits(n):
formatCredits(1250); // "1,250 credits"
Example
A Pro plan grants 200 credits/month. A basic message costs 1 credit, a premium message costs 10. The customer spends them in any mix they like. The balance is a count — there’s no dollar figure attached to it.

Fiat credits

A fiat credit is prepaid money. The balance is denominated in a real currency and stored in that currency’s minor units (cents for usd). “$10 of credit” is a balance of 1000 in usd. Usage is resolved to a currency cost and drawn down against it. Render fiat credits as money with <Money> / formatMoney(cents, code) — never as a raw count:
formatMoney(1000, "usd"); // "$10.00"
formatMoney(750, "eur");  // "€7.50"
Example
A customer buys **10ofAPIcredit.Eachrequestburnsafractionofacentbasedontokensused.Theirbalancefallsfrom10 of API credit**. Each request burns a fraction of a cent based on tokens used. Their balance falls from `10.00toward$0.00` — it’s a wallet of prepaid dollars, not a count.

The hard rule: never mix denominations

Unit credits and fiat credits — and two different fiat currencies — are different kinds of thing. Adding, ranking, or averaging across denominations produces a meaningless number.
Never sum, rank, or compare balances across denominations. 250 credits + $10 is not $260, and it is not 260 of anything. Keep each denomination — and each fiat currency — in its own bucket.
Concretely:
  • Don’t total a customer’s balances into one figure when they hold unit credits and fiat credits.
  • Don’t sum usd and eur balances — they’re separate wallets.
  • Don’t cross the formatters: never formatMoney a unit count, never formatCredits a fiat balance.
  • Don’t store a fiat amount as unit credits, or a count as a fiat balance.
Everywhere you touch a balance, branch first:
if (currency.denomination === "unit") {
  render(formatCredits(balance));
} else {
  render(formatMoney(balance, currency.denomination)); // ISO-4217 code, e.g. "usd"
}
Both denominations are first-class in the UI. Credit tabs, columns, and badges render on every relevant surface — a customer with several wallets sees each one on its own terms, single-currency, side by side. See Wallets & the ledger.

Modelling a fiat-credit system

To make a credit system represent money, map each credit to a minor unit of the currency (1 credit = 1 cent) and price top-ups at 1 credit each. See Top-ups for how a fiat balance is refilled.

See also

Credits overview

How the credit path fits together.

Wallets & the ledger

Where balances live, per currency.

Credit currencies (React)

Read the denominations configured on the account.

Top-ups

Refill a balance — following the customer’s collection method.