> ## 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.

# Customer Alerts

> Set a per-customer cost or revenue tripwire and get an email when it's crossed

# Customer Alerts

## What is a customer alert?

A **customer alert** is a watch on one customer: "email me when this customer's cost passes $500 this month," or "email me when this customer's revenue passes $10,000." When the number crosses the line, MarginFront emails a person you name and drops an in-app notification that links straight to that customer's page.

The two numbers you can watch:

* `cost` — what this customer costs you to serve (their usage cost).
* `revenue` — what this customer is billed.

Both are read through the same canonical metrics MarginFront uses everywhere else, so a watch sees the exact numbers the dashboard shows.

You can manage these watches on a customer's page in the dashboard, or from your own code with the endpoints below. The [MCP tools](/mcp/tools) call these exact endpoints too.

***

## Who can manage watches

Every endpoint here — reads included — needs a **secret** key that belongs to an **owner, admin, or finance** user. Any other key gets a `403 Forbidden` naming the role. A publishable key (`mf_pk_...`) is refused. A watch exposes one customer's money, so it sits with the people who own the numbers.

***

## The window: month, rolling, or custom

Every watch measures its number over a window you pick:

* `month` — the current UTC calendar month. Re-arms on its own each month, so a monthly watch keeps working month after month.
* `rolling` — the last 30 days. Fires once when the number crosses, so you get one heads-up per crossing.
* `custom` — a fixed date range you set with `customStartDate` and `customEndDate`. Fires at most once and expires at the end of the range.

***

## The endpoints

### List watches

```
GET /v1/customer-alerts
```

Returns every customer watch for your organization, each with the watched customer's name, the metric, the threshold, and the window.

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

```json theme={null}
[
  {
    "id": "a1b2c3d4-...",
    "customerId": "550e8400-e29b-41d4-a716-446655440000",
    "customerName": "Acme Corp",
    "customerExternalId": "acme-001",
    "metric": "cost",
    "thresholdUsd": 500,
    "windowMode": "month",
    "customStartDate": null,
    "customEndDate": null,
    "recipientEmail": "finance@yourco.com",
    "createdByUserId": "user-...",
    "createdAt": "2026-08-01T00:00:00.000Z",
    "updatedAt": "2026-08-01T00:00:00.000Z"
  }
]
```

***

### Create a watch

```
POST /v1/customer-alerts
```

Owner, admin, or finance key required.

**Request body:**

```json theme={null}
{
  "customerId": "550e8400-e29b-41d4-a716-446655440000",
  "metric": "cost",
  "thresholdUsd": 500,
  "windowMode": "month",
  "notifyEmail": "finance@yourco.com"
}
```

| Field             | Type   | Required                | Notes                                                                                                                                                                                 |
| ----------------- | ------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerId`      | string | Yes                     | The customer to watch, by MarginFront's internal UUID (not your `externalId`).                                                                                                        |
| `metric`          | string | Yes                     | `cost` (what the customer costs you) or `revenue` (what they're billed).                                                                                                              |
| `thresholdUsd`    | number | Yes                     | The tripwire in US dollars. Must be positive.                                                                                                                                         |
| `windowMode`      | string | Yes                     | `month`, `rolling`, or `custom`. See the window section above.                                                                                                                        |
| `customStartDate` | string | For `custom`            | ISO 8601 date. The range start. Only allowed when `windowMode` is `custom`.                                                                                                           |
| `customEndDate`   | string | For `custom`            | ISO 8601 date. The range end. Only allowed when `windowMode` is `custom`.                                                                                                             |
| `notifyEmail`     | string | Yes (for API-key calls) | The email that gets the alerts. Must be a current org member with an owner, admin, or finance role. An API key has no person behind it, so this is required when you call with a key. |

The recipient gets a confirmation email when the watch is created, then an alert email plus an in-app notification whenever it trips.

**Response (`201 Created`):** the full watch, same shape as the list response.

**curl example:**

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/customer-alerts \
  -H "x-api-key: mf_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "550e8400-e29b-41d4-a716-446655440000",
    "metric": "cost",
    "thresholdUsd": 500,
    "windowMode": "month",
    "notifyEmail": "finance@yourco.com"
  }'
```

**Common errors:**

* `400 Bad Request`: an internal coding-spend customer as the target (use [Spend Controls](./spend-controls) for your own AI coding spend), a deleted customer, custom-mode dates that don't make sense, a recipient who isn't an org member with a money role, or your organization's watch limit already reached.
* `403 Forbidden`: your key doesn't belong to an owner, admin, or finance user.
* `404 Not Found`: no customer in your organization matches that ID.
* `409 Conflict`: an identical watch already exists. Edit that one instead of creating a second.

***

### Update a watch

```
PATCH /v1/customer-alerts/{id}
```

Owner, admin, or finance key required. You can change `metric`, `thresholdUsd`, and `windowMode` (with the custom dates when you switch to `custom`). The watched customer and the recipient are fixed at create — to change either, delete the watch and create a new one.

**Response (`200 OK`):** the updated watch, same shape as the list response.

**Common errors:**

* `400 Bad Request`: custom-mode dates that don't make sense for the resulting watch.
* `403 Forbidden`: your key doesn't belong to an owner, admin, or finance user.
* `404 Not Found`: no watch with that ID in your organization.
* `409 Conflict`: the edit would collide with a watch that already exists.

***

### Delete a watch

```
DELETE /v1/customer-alerts/{id}
```

Owner, admin, or finance key required. Deleting a watch also clears its in-app notifications.

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

```json theme={null}
{
  "deleted": true,
  "id": "a1b2c3d4-..."
}
```

**Common errors:**

* `403 Forbidden`: your key doesn't belong to an owner, admin, or finance user.
* `404 Not Found`: no watch with that ID in your organization.

***

## Good to know

* **A watch fires once per window.** A `rolling` or `custom` watch that has already tripped for its window won't email you again for that same window, even if you edit its threshold afterward. A `month` watch re-arms each calendar month.
* **Editing never re-fires a window that already alerted.** The record of what already fired is permanent by design, so changing a threshold can't spam a fresh alert for a window you've already been warned about.
* **Internal coding-spend accounts can't be watched here.** For your own team's Claude Code and Codex spend, use [Spend Controls](./spend-controls) instead.
