Credit Pool Fields
| Field | Type | Description |
|---|---|---|
pool_key | string | Unique key within an org (e.g., ai_tokens, sms_credits) |
display_name | string | Human-readable label shown in the portal |
limit_per_period | integer | Credits granted to the tenant each billing period |
refill_behavior | enum | reset — unused credits are dropped; rollover — they carry forward |
rollover_cap | integer | null | Max credits that can carry over (null = no cap) |
limit_behavior | enum | hard — requests blocked once limit is hit; soft — requests allowed but flagged |
Credit Sources
A tenant’s balance in a pool comes from two sources:| Source | When granted | Expiry |
|---|---|---|
| Base | Automatically on subscription activation or renewal | End of billing period |
| Add-on | After successful add-on purchase (payment confirmed) | Per add-on expiry_type |
The Ledger
Credits are tracked in a double-entry ledger table (tenant_credit_ledger). Each entry records:
qty_granted— credits issued at the time of grantqty_remaining— credits not yet consumed (decremented on usage)source_type—baseoraddonexpires_at— when these credits expire
Checking Balance
Recording Consumption
Callcredits/consume from your backend whenever a tenant consumes a credit. Every call requires an idempotencyKey — passing the same key twice returns the original result without double-deducting.
Hard vs Soft Limits
| Behavior | What happens at the limit |
|---|---|
hard | Crovver returns an error; your app should block the action |
soft | Usage is still recorded; your app receives a flag to warn the user |
check-usage-limit to gate actions before consuming:
Rollover Example
A plan withai_tokens: 1000/month, refill_behavior: rollover, rollover_cap: 500:
| Month | Granted | Used | End Balance | Rolled into next month |
|---|---|---|---|---|
| Jan | 1,000 | 700 | 300 | 300 (< cap) |
| Feb | 1,000 + 300 | 400 | 900 | 500 (capped) |
| Mar | 1,000 + 500 | 1,100 | 400 | 400 |