# Spend Caps and Usage Alerts

Let a workspace or a person cap the overage a quota may bill, get told at the percentages it picks, and cap each project inside a workspace.

A spend cap saves your customer from a surprise bill. Some plans include an allowance, such as 10,000 API calls a month, and bill for every unit past it. That extra usage is called overage. A spend cap is the customer's own limit on it: after a set number of extra units, usage is refused and nothing more is recorded, just as on a plan with no overage.

Next to the cap, the customer can pick percentages of the allowance at which your backend is told. Inside a workspace, they can also cap each project.

```bash
curl -X PATCH "https://api.console.buildbase.app/api/v1/public/workspaces/$WORKSPACE_ID/quotas/api-calls" \
  -H "x-session-id: $SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{"maxOverageUnits": 5000, "notifyAtPercent": [80, 100]}'
```

```json
{
  "quotaSlug": "api-calls",
  "spendCap": { "maxOverageUnits": 5000, "notifyAtPercent": [80, 100] }
}
```

With 10,000 calls included, that workspace can record up to 15,000 calls this period, and pays overage on 5,000 of them at most. Your webhooks get `quota.threshold_reached` at 8,000 calls and again at 10,000.

> **Note:**
  A spend cap (`maxOverageUnits`) only matters on a quota that bills overage:
  its overage switch on, with an overage price. Set both on the plan version
  under **Billing > Plans**. Usage alerts work on any quota. See
  [Quotas](/credits/quotas).

## Who sets it

Your customer sets their own cap, so your app calls one of these routes for them:

| Whose quota | Route                                                       | Who may call it                                            |
| ----------- | ----------------------------------------------------------- | ---------------------------------------------------------- |
| A workspace | `PATCH /api/v1/public/workspaces/:workspaceId/quotas/:slug` | A member with permission to manage the workspace's billing |
| A person    | `PATCH /api/v1/public/me/quotas/:slug`                      | The signed-in person, on their own quota                   |

Both take the signed-in user's session in `x-session-id`. There's no SDK hook for setting a cap yet, so call the route from your settings screen or your backend. A person's cap takes `maxOverageUnits` and `notifyAtPercent`. Only a workspace can have `partitionCaps`.

## The body

Send any of these fields:

| Field             | Type                                | What it does                                                                                                                                    |
| ----------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `maxOverageUnits` | integer, 0 or more, or `null`       | Units allowed past the allowance this period. `0` stops at the allowance. `null` removes the cap                                                |
| `notifyAtPercent` | up to 5 whole numbers from 1 to 100 | Percentages of the allowance to be told at. `[]` removes them                                                                                   |
| `partitionCaps`   | `{ [partitionKey]: units or null }` | Workspace only: a cap this period for each partition, a slice of the workspace's usage such as one project. `null` removes that partition's cap |

Send at least one field. Whatever you leave out stays as it was. Partition caps are updated one key at a time, so sending one doesn't clear the others. The response is the cap as it now stands.

## What a cap changes

- **Recording.** A unit that would go past `maxOverageUnits` is refused, and nothing is recorded, exactly as on a plan without overage. The refusal is 402 `QUOTA_EXHAUSTED`, with `quotaSlug` and `resetsAt`, and its message names the cap. See [Quotas](/credits/quotas) for handling it.
- **Nothing else on a hard allowance.** A cap only limits overage. So it does nothing on a quota whose plan doesn't bill overage, during a trial (which is always held to the allowance), or on an unlimited quota.
- **The check routes.** `POST /api/v1/public/me/entitlements/check`, and the org's `GET /api/subscriptions/users/entitlements/check`, now count the cap. A quota's `remaining` is the allowance left plus the overage the cap still allows, instead of unlimited.
- **Reading it back.** `GET .../quotas/:slug` carries `spendCap` beside `usage`, `included`, `available` and `resetsAt`. The workspace's route needs permission to view usage.

A new cap can take up to ten seconds to apply everywhere. Every recording reads the cap, and each server process keeps its copy for up to ten seconds.

## Usage alerts

Usage alerts tell you when a customer is getting close to their allowance, so you can warn them before they pay extra. Each time a recording takes usage from below one of the chosen percentages to at or above it, BuildBase sends `quota.threshold_reached` for that percentage, once a period, to your webhooks and workflows. It's one of the 239 events in the [catalog](/webhooks/overview):

```json
{
  "subjectType": "workspace",
  "subjectId": "66f0c1d2e3a4b5c6d7e8f901",
  "workspaceId": "66f0c1d2e3a4b5c6d7e8f901",
  "quotaSlug": "api-calls",
  "percent": 80,
  "used": 8012,
  "included": 10000,
  "resetsAt": "2026-11-01T00:00:00.000Z"
}
```

If one burst of usage jumps past two percentages at once, only the higher one fires. BuildBase doesn't message your customer for you: send the email or the in-app message from your webhook handler or a workflow. A person's alert names the person and no workspace.

Separately, the first unit of overage fires `quota.limit_exceeded`, and BuildBase emails that one to the owner. See [Webhooks](/webhooks/overview).

## A cap per project

Inside one workspace, you can stop a single project from using up the whole allowance. Record usage with a `partitionKey`, such as the project's id, and it counts toward that partition as well as toward the workspace. A capped partition gets whichever is smaller: its own room left, or the workspace's.

```bash
curl -X PATCH "https://api.console.buildbase.app/api/v1/public/workspaces/$WORKSPACE_ID/quotas/api-calls" \
  -H "x-session-id: $SESSION_ID" \
  -H "Content-Type: application/json" \
  -d '{"partitionCaps": {"project-42": 2000, "project-7": null}}'
```

Record usage with the same key, on `POST /api/v1/public/workspaces/:workspaceId/subscription/usage` with `partitionKey` in the body. A unit past the partition's cap is refused with 402 `QUOTA_EXHAUSTED`, naming the partition. To read a partition's usage, `GET .../quotas/api-calls?partitionKey=project-42` answers `partitionUsed`, plus `partitionCap` and `partitionAvailable` when the partition has a cap.

A key is 1 to 128 letters, digits, dots, underscores, colons or hyphens (`^[A-Za-z0-9._:-]{1,128}$`). It can't be `pool` or start with `member:`, because per-member quotas use those. One quota can cap at most 100 partitions. A change past that is refused with 400 on `partitionCaps`, so remove a cap with `null` before you add another.

## Next steps

- [Quotas](/credits/quotas) - Record usage and handle the refusal.
- [Family and group plans](/billing/family-plans#shared-and-personal-allowances) - Give each member an allowance of their own.
- [Claims](/billing/users-and-workspaces#claims) - Count keys, projects or installed apps against a limit.
