Metered entitlements are in beta. The API and configuration options may change.
Metered entitlements give customers a credit grant when they subscribe. As they consume resources—API calls, compute minutes, storage operations—usage events deplete the grant. At any point, you can query the balance to see how many credits remain and whether the customer still has access.
Unlike static entitlements (which grant a fixed configuration value), metered entitlements track real-time consumption against a replenishing credit pool.
How It Works
- Create a metered feature with a billable metric that measures consumption
- Attach the feature to a plan price with an entitlement template defining the grant amount, reset period, and rollover rules
- Customer subscribes → a grant is provisioned automatically for the current period
- Your app sends usage events as the customer consumes
- Call the balance endpoint to check remaining credits and gate access in real time
Setup
Creating a Metered Feature
curl -X POST "https://api.paygentic.io/v0/features" \
-H "Authorization: Bearer sk_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"productId": "prod_abc123",
"merchantId": "mer_abc123",
"name": "API Calls",
"key": "api-calls",
"type": "metered"
}'
Linking to a Price (Entitlement Template)
When creating or updating a price, include an entitlementTemplate to configure how grants are provisioned for subscribers:
curl -X POST "https://api.paygentic.io/v0/prices" \
-H "Authorization: Bearer sk_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"planId": "plan_def456",
"unitPrice": "0",
"billableMetricId": "metric_xyz789",
"feature": {
"id": "feat_abc123",
"entitlementTemplate": {
"usagePeriod": {
"interval": "P1M"
},
"issueAfterReset": 1000,
"isSoftLimit": false,
"resetMaxRollover": 0,
"resetMinRollover": 0,
"preserveOverageAtReset": false
}
}
}'
Setting unitPrice: "0" grants customers access without charging for individual events. This is the typical setup when you want to use metered entitlements for quota enforcement rather than per-event billing.
Linking the Price to a Plan
curl -X PATCH "https://api.paygentic.io/v0/plans/plan_def456" \
-H "Authorization: Bearer sk_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prices": ["price_ghi789"]
}'
Entitlement Template Reference
| Field | Type | Default | Description |
|---|
usagePeriod.interval | ISO 8601 duration | required | How often the grant resets. "P1M" monthly, "P1D" daily, "P1W" weekly |
usagePeriod.anchor | datetime | activeFrom | Reference point for computing period boundaries |
issueAfterReset | number | — | Credits granted at the start of each period. If omitted, no grant is provisioned |
issueAfterResetPriority | integer | 0 | Burn order when multiple grants exist (lower = burned first) |
isSoftLimit | boolean | false | If true, customer retains access even when balance reaches 0 |
resetMaxRollover | number | 0 | Max unused credits carried into the next period (0 = use-it-or-lose-it) |
resetMinRollover | number | 0 | Minimum credits guaranteed to roll over |
preserveOverageAtReset | boolean | false | If true, overage from the previous period is deducted from the new period’s grant |
Checking the Balance
Use GET /v1/entitlements/{entitlementId} to get a real-time snapshot. For metered entitlements, the response includes live balance and usage data inline.
curl -X GET "https://api.paygentic.io/v1/entitlements/ent_abc123" \
-H "Authorization: Bearer sk_test_YOUR_API_KEY"
Response example:
{
"object": "entitlement",
"id": "ent_abc123",
"customerId": "cus_xyz789",
"featureId": "feat_789",
"featureKey": "api-calls",
"featureType": "metered",
"subscriptionId": "sub_abc",
"status": "active",
"activeFrom": "2026-01-01T00:00:00Z",
"activeTo": null,
"hasAccess": true,
"metadata": {},
"balance": 750,
"usageInPeriod": 250,
"overage": 0,
"currentPeriodStart": "2026-02-01T00:00:00Z",
"currentPeriodEnd": "2026-03-01T00:00:00Z"
}
Response fields:
| Field | Description |
|---|
id | The entitlement ID |
featureKey | The unique identifier for the feature |
featureType | Always "metered" for metered features |
status | Entitlement status: active, expired, or canceled |
hasAccess | true if balance > 0, or if isSoftLimit is true |
balance | Remaining credits across all active grants |
usageInPeriod | Total usage consumed in the current period |
overage | Usage beyond available credits |
currentPeriodStart / currentPeriodEnd | Current usage period boundaries |
Gating Access
Check the balance before allowing an operation, then send the usage event regardless:
async function canMakeApiCall(entitlementId) {
const response = await fetch(
`https://api.paygentic.io/v1/entitlements/${entitlementId}`,
{ headers: { 'Authorization': 'Bearer sk_test_YOUR_API_KEY' } }
);
const { hasAccess } = await response.json();
return hasAccess;
}
// In your request handler
if (!(await canMakeApiCall(customer.entitlementId))) {
return res.status(429).json({ error: 'API call quota exceeded' });
}
// Proceed with the request, then send a meter event
await fetch('https://api.paygentic.io/v0/events', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'api.call',
subject: customerId,
data: { quantity: 1 },
}),
});
The grant engine does not block usage events in real time. Always check hasAccess before allowing an operation—do not rely on the event pipeline to enforce limits.
Soft vs Hard Limits
| Hard Limit (isSoftLimit: false) | Soft Limit (isSoftLimit: true) |
|---|
| When balance = 0 | hasAccess returns false | hasAccess stays true |
| Best for | Strict quotas, free-tier caps | Overage billing, best-effort enforcement |
| Overage | Not accrued (access denied) | Tracked in the overage field |
Grant Lifecycle Examples
These examples show how grants behave across billing periods under different configurations.
Example 1 — Monthly Grant, Use-It-or-Lose-It
issueAfterReset: 1000, resetMaxRollover: 0, isSoftLimit: false
| Date | Event | Balance |
|---|
| Jan 1 | Grant issued | 1000 |
| Jan (mid) | 600 credits consumed | 400 |
| Feb 1 | Period reset — unused 400 forfeited, new 1000 issued | 1000 |
| Feb (late) | 1000 credits consumed | 0 |
| Feb (late) | Another request | hasAccess: false |
Example 2 — Monthly Grant with Rollover Cap
issueAfterReset: 1000, resetMaxRollover: 500, isSoftLimit: false
| Date | Event | Balance |
|---|
| Jan 1 | Grant issued | 1000 |
| Jan | Only 200 consumed — 800 remaining | 800 |
| Feb 1 | Reset: 800 > 500 cap, rolled over as 500; new 1000 issued | 1500 |
| Feb | Heavy usage month | — |
The rollover is applied before the new grant, so the customer starts the period with rollover + issueAfterReset.
Example 3 — Daily Grants on a Monthly Subscription
usagePeriod.interval: "P1D", issueAfterReset: 100, resetMaxRollover: 50
A customer’s subscription renews monthly, but their grant resets daily. Each day they receive a fresh allocation.
| Day | Event | Balance |
|---|
| Day 1 | Daily grant issued | 100 |
| Day 1 | 80 consumed | 20 |
| Day 2 | Reset: 20 ≤ 50 cap, rolls over; new 100 issued | 120 |
| Day 2 | 110 consumed | 10 |
| Day 3 | Reset: 10 rolls over; new 100 issued | 110 |
Example 4 — Soft Limit with Overage Carry-Forward
issueAfterReset: 1000, isSoftLimit: true, preserveOverageAtReset: true
| Date | Event | Balance | Overage |
|---|
| Jan 1 | Grant issued | 1000 | 0 |
| Jan | 1200 consumed | 0 | 200 |
| Jan 31 | Access still allowed (soft limit) | — | 200 |
| Feb 1 | Reset: new 1000 issued, 200 overage deducted | 800 | 0 |
The preserveOverageAtReset flag ensures customers who overconsume one period have a reduced allowance the next period.
Example 5 — Minimum Rollover Floor
issueAfterReset: 1000, resetMaxRollover: 100, resetMinRollover: 100
When both min and max rollover are set to the same value, customers always carry forward exactly that amount regardless of actual usage:
| Jan usage | Remaining | Rolls over as |
|---|
| 950 | 50 | 100 (raised to min) |
| 900 | 100 | 100 (exact match) |
| 200 | 800 | 100 (capped at max) |
Rollover and Overage Reference
At each period reset, the carried-over balance is computed as:
rolledOver = min(resetMaxRollover, max(resetMinRollover, currentBalance))
| Ending balance | resetMaxRollover | resetMinRollover | Rolls over as |
|---|
| 800 | 1000 | 0 | 800 (within range) |
| 1200 | 1000 | 0 | 1000 (capped at max) |
| 50 | 1000 | 100 | 100 (raised to min) |
| 0 | 0 | 0 | 0 (use-it-or-lose-it) |
When preserveOverageAtReset is true, any overage accumulated in the previous period is subtracted from the new period’s starting balance after rollover and the new grant are applied.
Next Steps