# Points Quotas (Rolling Windows) (/en/docs/points-quota)



Configure rolling-window quotas for subscription and free-periodic credits. A single entitlement can carry multiple windows, e.g. `{5h: 500, week: 5000, month: 20000}`. At any moment the available quota is the minimum of "limit − used-in-window" across all windows. If the 5-hour window is exhausted, the current available quota is 0 even if the weekly/monthly windows still have room.

This applies to `subscription_credit` (credits granted with a subscription) and `free_periodic_credit` (free credits granted per period). Top-up, registration, and granted credits are pool balances — they are not affected by window quotas.

## Who this is for [#who-this-is-for]

Realm Admins who configure subscription quotas and the free-period quota for new users. The configuration happens in the admin console or via API.

## Core idea [#core-idea]

A quota window has two fields: a length in seconds and a limit. The window is rolling — it looks back `windowSeconds` from now and sums what was consumed. At least one window, at most eight. The user's available quota is the minimum remaining across all configured windows, so the tightest window is the one that bites.

## Configure subscription quotas [#configure-subscription-quotas]

Subscription quotas hang off the Entitlement Mapping. Configure them on the **Entitlement Mappings** page or via `PUT /api/bill/{realmId}/entitlement-mappings/batch`.

### Admin console [#admin-console]

1. Log in as Realm Admin and go to `/{realmId}/manage/billing/entitlement-mappings`
2. Pick the target product from the product list
3. Find the multi-window quota editor in the advanced config of the price row
4. Add window rows and save

### API request example [#api-request-example]

```json
PUT /api/bill/{realmId}/entitlement-mappings/batch
{
  "paymentProvider": "stripe",
  "externalProductId": "prod_xxx",
  "updates": [
    {
      "mappingId": "550e8400-e29b-41d4-a716-446655440000",
      "entitlementKey": "pro-plan",
      "billingType": "recurring",
      "billingPeriod": "month",
      "pointsPerPeriod": 1000,
      "grantPeriodType": "monthly",
      "validityDays": 30,
      "grantOnSubscribe": true,
      "maxPeriods": 10,
      "enabled": true,
      "quotaWindows": [
        { "windowSeconds": 18000, "limit": 500 },
        { "windowSeconds": 604800, "limit": 5000 },
        { "windowSeconds": 2592000, "limit": 20000 }
      ]
    }
  ]
}
```

Field constraints:

| Field           | Constraint                 | Description                         |
| --------------- | -------------------------- | ----------------------------------- |
| `windowSeconds` | Greater than 0             | Sliding window length, in seconds   |
| `limit`         | Greater than or equal to 0 | Quota cap for this window           |
| `quotaWindows`  | At most 8 rows             | Exceeding this is rejected with 400 |

The configuration only affects subsequently granted quota entitlements; already-active entitlements keep the `quota_windows` snapshot they were granted with.

### Active-subscription protection [#active-subscription-protection]

If a batch update would flip a mapping that still has active subscriptions from `enabled=true` to `enabled=false`, the backend rolls back the whole transaction and returns 409; the response body includes the count of affected active subscriptions. The frontend surfaces this with a confirm dialog.

## Configure the Realm default free-period quota [#configure-the-realm-default-free-period-quota]

The periodic quota a free user gets at registration is controlled by the Realm default config, at `/{realmId}/manage/points/default-config`, backed by `GET/PUT /api/points/{realmId}/default-config`.

### Admin console [#admin-console-1]

1. Log in as Realm Admin and go to `/{realmId}/manage/points/default-config`
2. Find the free-period multi-window quota editor
3. Add window rows and save

### API request example [#api-request-example-1]

```json
PUT /api/points/{realmId}/default-config
{
  "registrationBonusPoints": 1000,
  "freePeriodicPointsAmount": 50,
  "freePeriodicGrantPeriodType": "daily",
  "freePeriodicValidityDays": 1,
  "freePeriodicQuotaWindows": [
    { "windowSeconds": 86400, "limit": 50 },
    { "windowSeconds": 604800, "limit": 200 }
  ]
}
```

`freePeriodicQuotaWindows` semantics:

* `null`: leave the stored value untouched (PUT is a partial update)
* `[]`: clear the window configuration
* `[{windowSeconds, limit}]`: replace with the new window definitions

Validation matches the Entitlement Mapping rules: `windowSeconds` greater than 0, `limit` greater than or equal to 0, at most 8 rows.

### Registration bonus credits [#registration-bonus-credits]

`registrationBonusPoints` controls the one-time `registration_credit` a new user gets at registration, which is permanent. This field sits in its own area of the same config page as the free-period quota.

## Verify the configuration took effect [#verify-the-configuration-took-effect]

1. After a new user registers or a new subscription is paid, visit `/{realmId}/user/points`
2. The usage dashboard on the page shows, per window row, the remaining / limit / used / recovery time
3. If no window rows appear, check whether the corresponding mapping or Realm default has `quotaWindows` configured, and whether the user has actually been granted the quota entitlement

## How consumption mixes with pool balance [#how-consumption-mixes-with-pool-balance]

On consume, the window quota is deducted first within the same transaction; any overflow is taken from the top-up / registration / granted pools. If the combined total is insufficient, the whole consume rolls back and is rejected — no partial deduction. See [Billing Architecture](/docs/billing-overview) for the credit-account and pool model.
