> ## Documentation Index
> Fetch the complete documentation index at: https://docs.marginfront.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Spend Controls

> Cap your own team's AI coding spend and read it back from code

# Spend Controls

## What is a spend cap?

A **spend cap** is a limit you set on how much your own team spends on AI coding tools. For example: "Stop AI spend at $5,000 across the whole team per month," or "$200 per week for one developer."

These caps govern your company's **internal** coding-agent spend: the usage you meter with the [Claude Code & Codex spend tool](/tools/code-cost-clarity). They have nothing to do with your customers. Events you record for customer billing never count toward a cap, and a cap never changes what any customer pays.

You can manage caps in the dashboard's Money tab. These endpoints are the same controls, callable from your own code. The [Node SDK](/sdk/spend-controls) and the [MCP tools](/mcp/tools) call these exact endpoints too, so every path sees the same caps and the same numbers.

***

## The two ideas to know first

**Scope: who the cap governs.**

* `org`: one ceiling for the whole team.
* `dev`: one developer, named by their `customerExternalId` (their email).

A developer cap can never be set higher than the whole-team ceiling. The API rejects the attempt with a plain message.

**Mode: what the cap does when spend reaches it.**

* `track`: watches and sends alert emails. Blocks nothing.
* `enforce`: a developer's machine stops the coding-agent tool call when the cap is reached. Enforcement only happens on machines where the team turned on the [Spend Control device setup](/tools/code-cost-clarity#spend-control-opt-in-caps). The API itself never blocks anything; it stores the caps and reports the spend.

Every cap covers **all AI tools**. You can't yet cap one provider (say, only Claude) on its own; if you send a different `providerScope`, the API answers with `"Caps cover all AI tools for now."`

***

## Who can change caps

Reading caps, spend, and coverage works with any secret key for your organization.

**Creating, updating, or deleting a cap needs a key that belongs to an owner or finance user.** Any other key gets a `403 Forbidden` with a message naming the required role. This is the same rule the dashboard enforces: spend caps sit with the people who own the budget.

***

## The endpoints

### List caps

```
GET /v1/control/caps
```

Returns every cap policy for your organization. Each cap includes a `sentence` field: the cap written out as one plain sentence, built by the server.

**Response (`200 OK`):**

```json theme={null}
[
  {
    "id": "b7f3c9a1-...",
    "scope": "org",
    "scopeValue": null,
    "providerScope": "all",
    "amountUsd": 5000,
    "period": "month",
    "mode": "enforce",
    "coolOffHours": null,
    "alertThresholds": [50, 80, 100],
    "createdByUserId": "user-...",
    "createdAt": "2026-07-01T00:00:00.000Z",
    "updatedAt": "2026-07-01T00:00:00.000Z",
    "sentence": "Stop AI spend at $5,000 across the whole team per month"
  }
]
```

***

### Create a cap

```
POST /v1/control/caps
```

Owner or finance key required.

**Request body:**

```json theme={null}
{
  "scope": "dev",
  "scopeValue": "alice@acme.com",
  "providerScope": "all",
  "amountUsd": 200,
  "period": "week",
  "mode": "enforce"
}
```

| Field             | Type      | Required | Notes                                                                                                             |
| ----------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `scope`           | string    | Yes      | `org` (whole-team ceiling) or `dev` (one developer).                                                              |
| `scopeValue`      | string    | For dev  | The developer's `customerExternalId` (their email). Omit for `org`.                                               |
| `providerScope`   | string    | Yes      | Must be `"all"`. Every cap covers all AI tools.                                                                   |
| `amountUsd`       | number    | Yes      | The cap amount in US dollars.                                                                                     |
| `period`          | string    | Yes      | `day`, `week`, or `month`. The window resets on UTC boundaries.                                                   |
| `mode`            | string    | Yes      | `track` (watch and alert, block nothing) or `enforce` (armed machines stop the tool call at the cap).             |
| `coolOffHours`    | number    | No       | Hours to wait after a cap trips before the device re-checks. Whole number.                                        |
| `alertThresholds` | number\[] | No       | Alert ladder as whole percents of the cap, up to 5 values, each higher than the last. Default is `[50, 80, 100]`. |

**Response (`201 Created`):** the full cap, same shape as the list response, including its `sentence`.

**curl example:**

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/control/caps \
  -H "x-api-key: mf_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "org",
    "providerScope": "all",
    "amountUsd": 5000,
    "period": "month",
    "mode": "track"
  }'
```

**Common errors:**

* `400 Bad Request`: a developer cap higher than the whole-team ceiling, a `scopeValue` that doesn't match the scope, or a `providerScope` other than `"all"`.
* `403 Forbidden`: your key doesn't belong to an owner or finance user.
* `409 Conflict`: a cap with this scope and target already exists. Update it instead of creating a second one.

***

### Update a cap

```
PATCH /v1/control/caps/{id}
```

Owner or finance key required. You can change `amountUsd`, `period`, `mode`, `coolOffHours`, and `alertThresholds`. A cap's identity (its scope and target) can't be changed: to point a cap at someone else, delete it and create a new one.

**Response (`200 OK`):** the updated cap, including its refreshed `sentence`.

**Common errors:**

* `400 Bad Request`: the new amount would put a developer cap above the whole-team ceiling.
* `403 Forbidden`: your key doesn't belong to an owner or finance user.
* `404 Not Found`: no cap with that ID in your organization.

***

### Delete a cap

```
DELETE /v1/control/caps/{id}
```

Owner or finance key required. The response echoes the removed cap's sentence so you can confirm what was deleted.

**Response (`200 OK`):**

```json theme={null}
{
  "deleted": true,
  "sentence": "Stop AI spend at $200 for alice@acme.com per week"
}
```

**Common errors:**

* `400 Bad Request`: deleting this whole-team ceiling would leave a developer cap with nothing above it.
* `403 Forbidden`: your key doesn't belong to an owner or finance user.
* `404 Not Found`: no cap with that ID in your organization.

***

### Read spend against the caps

```
GET /v1/control/spend?period=week
```

Returns your team's internal coding-agent spend for the current period window (the server computes the window in UTC). Add `customerExternalId` to also get one developer's number:

```
GET /v1/control/spend?period=week&customerExternalId=alice@acme.com
```

**Response (`200 OK`):**

```json theme={null}
{
  "period": "week",
  "window": {
    "start": "2026-07-20T00:00:00.000Z",
    "end": "2026-07-26T23:59:59.999Z"
  },
  "asOf": "2026-07-23T14:05:00.000Z",
  "org": {
    "spentUsd": 42.5,
    "eventCount": 310,
    "unpricedEventCount": 0
  },
  "dev": {
    "customerExternalId": "alice@acme.com",
    "spentUsd": 12.75,
    "eventCount": 88,
    "unpricedEventCount": 0
  }
}
```

Three honesty rules to know:

* `spentUsd` is `null` when there's no priced usage in the window. A `null` means "nothing to total yet," never a fake `$0`.
* `unpricedEventCount` tells you how many events exist that couldn't be priced yet (their model isn't in the pricing table). If it's above zero, the true spend is higher than `spentUsd` shows.
* Only internal coding-agent usage counts. Events you record for customer billing are never in these totals.

***

### Read coverage

```
GET /v1/control/coverage
```

Answers "how many of my developers are actually protected?" over the trailing 7 days. Works with any secret key.

**Response (`200 OK`):**

```json theme={null}
{
  "armed": 3,
  "total": 5,
  "asOf": "2026-07-23T14:05:00.000Z"
}
```

* `armed`: developers whose machines have enforcement turned on.
* `total`: developers seen at all in the window.
* `asOf` is `null` when there's been no recent activity. The server reports honest absence rather than making up a timestamp.

These are the same numbers the dashboard's Money tab shows, computed by the same math.

***

## How enforcement actually works

The API stores caps and reports spend. The stopping happens on each developer's machine, through the [Claude Code & Codex spend tool](/tools/code-cost-clarity) with its Spend Control option turned on. Two consequences worth knowing:

* **Enforcement fails open.** If a machine can't read fresh cap data (network down, stale file), it doesn't block. A broken cap should never brick a developer's day.
* **A cap in `track` mode never blocks anywhere.** It watches and sends the alert-ladder emails, nothing more.
