Usage Gate

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

WhatWho owns itExample
How many creditsYour rules / Stripe metadata grantsPro → 50,000 ai_credits
Until when (resetAt)Stripe current_period_endValid until Mar 26 23:59 UTC

One period, step by step

  1. Maya upgrades to Pro. Stripe starts a billing period (e.g. monthly).
  2. invoice.paid (or customer.subscription.updated) hits UsageGate.
  3. We set Maya's balances from feature_grants — absolute amounts, not increments.
  4. We set resetAt to that subscription's current_period_end.
  5. While now < resetAt, canAccess / consume burn down the remaining balance.
  6. When now ≥ resetAt, effective balance becomes 0 — even if unused credits were left. Nothing auto-refills yet.
  7. Next Stripe invoice for the new period fires → we grant a fresh balance and a new resetAt. Cycle repeats.
  1. 1invoice.paid

    Stripe period 1

    • Balance set to 50,000
    • resetAt = Mar 26 (period end)
    • Maya burns credits with consume
  2. 2resetAt passed

    Period ends

    • Stored balance may still say 38,000
    • Effective balance reads as 0
    • Checks deny until the next invoice
  3. 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 no resetAt → 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.