Spend guard
Check the budget before calling your model.
Set caps
In site settings, open Spend caps. Use your own units: credits, cents or tokens. Blank means no cap; zero blocks positive spend.
PATCH /api/sites/:siteKey
{"spend":{"account":100,"device":200,"network":1000,"key":500}}| Budget | Window | Identity |
|---|---|---|
| account | UTC day | accountId |
| device | UTC day | Visitor and fingerprint hashes from the account's last verification |
| network | UTC hour | IP prefix, hashed per site |
| key | UTC hour | Your API-key identifier, hashed per site |
Send the same accountId used in verification. Device spend is shared across accounts matching either device hash; a reservation matching both hashes counts once. A budget applies only when its identity is available. Always send accountId, ip and key when relying on their caps; an account without a prior verification has no device budget.
Reserve before inference
POST https://nolgard.com/v1/spend
{"secret":"sk_…","op":"reserve","accountId":"user_42","cost":20,"ip":"198.51.100.8","key":"api_key_7"}
{"allowed":true,"reservationId":"…","remaining":{"account":80,"device":180,"network":980,"key":480}}Only start inference when allowed is true. The per-site Durable Object checks all applicable caps atomically. A reservation holds budget for five minutes. Reserve an upper bound and commit before expiry.
{"allowed":false,"remaining":{"account":0},"reason":"account_budget"}Reasons: account_budget, device_budget, network_budget, key_budget. Exactly reaching a cap is allowed; exceeding it is denied. Remaining balances include pending reservations.
Two more reasons come from the payment guard rather than from your caps: hold_budget means the account is over the hold cap on credits bought by a risky payment, and the rest is released when the hold expires; payment_hold means an early fraud warning or a fraudulent dispute froze the account, so every reservation is denied until an owner releases the hold.
Commit or release
POST /v1/spend
{"secret":"sk_…","op":"commit","reservationId":"…","cost":12}
{"ok":true}
POST /v1/spend
{"secret":"sk_…","op":"release","reservationId":"…"}Commit without cost keeps the estimate. A lower actual cost frees the difference; higher actual cost is recorded even if it exceeds the cap, so later requests are blocked. Release cancels unused reservations. Repeating the same terminal operation does not charge twice. A conflicting terminal operation, unknown id or expired reservation returns {"ok":false}. Terminal ids are retained through the next UTC day for retries. Spend belongs to the reservation's original window, including commits after a window boundary.
Errors and testing
Costs must be finite and positive. Optional identifiers must be nonempty strings (256 characters maximum; ip: 128). Invalid input returns 400, invalid secret 401, demo writes 403, and requests above 120 per minute per site return 429. Keep secrets server-side and fail closed on transport errors.
All four test secrets allow costs up to 1000 and deny larger costs with account_budget. Allowed calls return test-spend-reservation; commit/release accept only that id. Test mode is stateless: no real reservations, counters or billing.
The dashboard shows Spend denied when reserve calls are denied. Daily totals count reserve calls rather than inferences; estimated cost totals do not track commit adjustments. Only per-site keyed hashes are stored for identities, never raw IPs or API-key ids.