Prepaid credit
A key with a total budget is a prepaid key: what it holds is credit, bought in advance and used up by its requests. This page is the rule that credit follows. It is the same rule whoever paid and however: a top-up through the admin API, a payment through a payment provider, a bonus.
With nothing configured the rule changes nothing: a prepaid key is billed what the providers cost, as before, and earns no bonus.
- The rule
- Which keys
- The burn multiplier
- Two kinds of credit
- The weekly bonus
- Settings
- Seeing it
- What it does not do
The rule
1 credit = 1 US dollar, at the moment a top-up is applied
balance = top-ups + bonuses − burns
burn = what the provider cost × the burn multiplier
weekly bonus = average bonus-eligible balance over the week
× the annual bonus rate ÷ 52
A key at or below zero is blocked: its requests are answered
402 budget_exceeded until it is credited again. The request that crosses
zero is completed and paid for; the balance then stands a little below
zero, and the next credit pays that off first.
Credit cannot be withdrawn or refunded. There is no call that pays any
out, to anyone. It can only be spent on requests through this gateway. An
operator can lower a key's budget by hand (PATCH /admin/keys/{name}), which
takes credit away; nothing sends it anywhere.
Amounts are IEEE-754 doubles in US dollars, like every cost in the ledger. Nothing is rounded when it is stored or compared; listings round to six decimals. A bonus of less than a millionth of a dollar is not credited.
Which keys
A key is prepaid when it has budget_usd and its budget_period is
total (the default period). Everything on this page applies to such keys
and to no others:
| Key | Burn multiplier | Bonus | Top-ups |
|---|---|---|---|
budget_usd set, period total |
applies | applies, to eligible credit | managed keys only |
budget_usd set, period monthly or daily |
no: billed the provider cost | no | refused (409 not_prepaid) |
no budget_usd |
no | no | refused (409 not_prepaid) |
A key in the config file can be prepaid (its budget is then fixed in the file, and the burn multiplier applies to it), but it cannot be topped up or earn a bonus: both change a key's budget, and the gateway does not write the config file.
The burn multiplier
credits.burn_multiplier says how much credit one dollar of provider cost
uses up. At 1.2, a request that cost the provider $0.50 burns $0.60 of the
key's credit; a key holding $1.20 is blocked once the providers have cost
$1.00.
- The setting is global; a key may carry its own
burn_multiplier, which wins ("burn_multiplier": 1.5in akeys[]entry, or in a create orPATCHcall for a managed key;nullgoes back to the setting). - The multiplier that applies is the one in force when the usage event reaches the control plane. What was burned stays burned: changing the multiplier does not re-price the past.
- The provider cost is never changed by it. Every usage record carries both
figures:
cost_usd(what the provider cost) andcredits_usd(what was burned), so the margin iscredits_usd − cost_usd, per request and per key.
Worked example at 1.2:
| Provider cost | Burned | Balance | |
|---|---|---|---|
| Top-up of $10 | 10.00 | ||
| A request costing $0.25 | 0.25 | 0.30 | 9.70 |
| A media job costing $2.00 | 2.00 | 2.40 | 7.30 |
| Requests costing $6.10 in all | 6.10 | 7.32 | −0.02 (blocked) |
Two kinds of credit
Every top-up says whether it is bonus-eligible. A prepaid key therefore holds two balances: eligible credit, and the rest ("plain" credit).
- Spending draws on plain credit first. Eligible credit is touched only once the plain credit is gone, so it keeps earning for as long as it can.
- A bonus is eligible credit, so it compounds.
- Which top-ups are eligible:
- A payment through a payment provider: per
payments.bonus_eligible(default true). - A top-up through the admin API: what the call says
(
"bonus_eligible": true), elsecredits.admin_topups_eligible(default false). - A budget set or changed by hand (
PATCHofbudget_usd): plain. Raising it adds plain credit; lowering it takes plain credit first, then eligible credit.
- A payment through a payment provider: per
- Plain credit added later does not give back eligible credit that was already spent.
Example: a key holds $10 plain and $10 eligible. Requests burn $15: the plain $10 goes first, then $5 of the eligible credit. The key now holds $5, all of it eligible.
The weekly bonus
With credits.bonus_annual_rate above zero, every prepaid managed key earns
a bonus on its eligible credit once a week:
bonus = average eligible balance over the week × rate ÷ 52
At 5% a year, $100 of eligible credit held all week earns
100 × 0.05 ÷ 52 = $0.0962.
- Weeks are ISO weeks in UTC: Monday 00:00 to the next Monday 00:00.
- The average is over the whole week. A key that held $100 for half the week earns half of that; a key credited on Tuesday earns six sevenths. Topping up just before the week ends earns almost nothing for that week.
- The rate is the one in force when the week is closed. It is a setting an operator can change at any time, not a promise; say so to your customers.
- How the average is measured. The eligible balance is sampled at every
top-up and every
credits.sample_interval_s(15 minutes unless set). Between two samples it is taken to have moved in a straight line. Samples are kept with the key, so the measure survives restarts. - It is credited as a top-up of its own: source
bonus, referencebonus:<key>:<ISO week>(for examplebonus:acme:2026-W46), in the audit log askey.bonus, in the top-up history and in the usage feed. A reference is only ever credited once, so a week cannot be paid twice: not after a restart, not if the clock is set back, not if the reference is sent by hand (that is a409). - Weeks that ended while the control plane was not running are closed when it starts again, one by one, each credited once and each on top of the one before. What the balance did during the gap is not known sample by sample: it is taken to have moved in a straight line from the last sample to the first one after the start.
- A key created during a week earns for the part of the week it held eligible credit. A deleted key earns nothing more; its state goes with it. A balance at or below zero earns nothing.
- A key that holds no eligible credit is not sampled, and with a rate of 0 nothing is sampled at all: the key store is not written for it.
Worked example at 5% (0.0962% a week), multiplier 1.2:
| When | What | Eligible | Plain |
|---|---|---|---|
| Monday | $100 arrives through a payment provider | 100.00 | 0 |
| Wednesday | Requests costing the providers $20 burn $24 | 76.00 | 0 |
| Next Monday 00:00 | Average was (100×2 + 76×5) ÷ 7 = 82.86; bonus 82.86 × 0.05 ÷ 52 = 0.0797 |
76.0797 | 0 |
Settings
In the config file, all optional:
"credits": {
"burn_multiplier": 1.2,
"bonus_annual_rate": 0.05,
"sample_interval_s": 900,
"admin_topups_eligible": false
}
| Field | Default | Meaning |
|---|---|---|
burn_multiplier |
1 |
Credit burned per dollar of provider cost, for prepaid keys. 0.01 to 100. |
bonus_annual_rate |
0 |
The weekly bonus's rate per year, as a fraction: 0.05 is 5%. 0 to 10. 0 switches the bonus off. |
sample_interval_s |
900 |
How often eligible balances are sampled while a bonus rate is set. 1 to 86,400. |
admin_topups_eligible |
false |
Whether a top-up through the admin API is bonus-eligible when the call does not say. |
An unknown field in credits is refused when the config is loaded. Per key:
burn_multiplier (see above).
The config file is re-read when it changes, so a new multiplier or rate takes effect without a restart: the multiplier for the usage that arrives from then on, the rate for the weeks closed from then on.
Seeing it
GET /admin/keys: every key hascost_usd(provider cost) andcredits_burned_usd; a prepaid key also has"credits": {"balance_usd", "eligible_usd", "plain_usd", "burned_usd", "burn_multiplier", "bonus_annual_rate", "last_bonus"}(null for any other key).period_spend_usdof a prepaid key is its credit burned.GET /admin/events(the usage feed): every usage event hascost_usdandcredits_usd; credit added to a key is an event of its own,"kind": "credit", withamount_usd,source,reference,bonus_eligibleand thebudget_usdafter it. Read in order, the feed is a complete statement of a key.GET /admin/topups?key=<name>&limit=<n>: applied top-ups and bonuses, newest first, each with itssourceand whether it waseligible.GET /metrics:omnigate_key_cost_usd_total(provider cost) andomnigate_key_credits_burned_usd_total;omnigate_key_period_cost_usdof a prepaid key is its credit burned.- The audit log:
key.topup(withsourceandeligible) andkey.bonus(written by the gateway itself,"auth": "system").
What it does not do
- It does not hold or move money. Credit is a number in the key store. What an operator does with what customers paid is outside the gateway.
- No cash-out, no refunds, no transfers between keys.
- No bonus for keys in the config file, and none for monthly or daily budgets.
- The margin is not reported as revenue anywhere; it is the difference of two figures in each record.
- Enforcement is after the fact, as for every budget: the request that crosses zero completes, and the data plane learns of the block with its next config poll.