Build
How billing periods work
UsageGate does not run a monthly cron that tops everyone up. The period comes from your Stripe subscription. Rules decide how many credits; Stripe decides until when they are valid.
The two clocks
| What | Who owns it | Example |
|---|---|---|
| How many credits | Your rules / Stripe metadata grants | Pro → 50,000 ai_credits |
| Until when (resetAt) | Stripe current_period_end | Valid until Mar 26 23:59 UTC |
One period, step by step
- Maya upgrades to Pro. Stripe starts a billing period (e.g. monthly).
invoice.paid(orcustomer.subscription.updated) hits UsageGate.- We set Maya's balances from
feature_grants— absolute amounts, not increments. - We set
resetAtto that subscription'scurrent_period_end. - While
now < resetAt,canAccess/consumeburn down the remaining balance. - When
now ≥ resetAt, effective balance becomes 0 — even if unused credits were left. Nothing auto-refills yet. - Next Stripe invoice for the new period fires → we grant a fresh balance and a new
resetAt. Cycle repeats.
- 1invoice.paid
Stripe period 1
- Balance set to 50,000
- resetAt = Mar 26 (period end)
- Maya burns credits with consume
- 2resetAt passed
Period ends
- Stored balance may still say 38,000
- Effective balance reads as 0
- Checks deny until the next invoice
- 3invoice.paid
Stripe period 2
- Balance overwritten to 50,000
- New resetAt = Apr 26
- Access works again
What “1,000 / month” on a rule means
In the rules editor, a limit label like 1,000 / month is a human description of your commercial intent. UsageGate does not schedule a calendar month from that label. The real period boundary is whatever Stripe puts on the subscription (monthly, yearly, custom).
What happens to leftover credits?
They expire with the period. Grants are absolute sets: the next invoice overwrites the balance to the plan allotment (e.g. back to 50,000), it does not add leftover + new. That keeps out-of-order webhooks safe.
Manual grant() and periods
grant(user, feature, { balance: 100 })with noresetAt→ balance never expires until something overwrites it (another grant or Stripe).grant(..., { balance: 50, resetAt: trialEnd })→ same expiry rules as Stripe; after that date, effective balance is 0.- Use manual grants for free signup seeds, trials, and admin overrides. Paid renewals should always come from Stripe.
Canceled / unpaid subscriptions
On customer.subscription.updated, if status is canceled, unpaid, or incomplete_expired, we apply the same grant keys with balance 0 so access stops.
