> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runledger.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runledger.co/_mcp/server.

# Auto top-up and alerts

A prepaid customer who runs out of credits in the middle of work is a support ticket. Two things prevent it: an auto top-up that buys credits for them, and alerts that tell them or you before the balance is gone.

## Before you start

* A prepaid customer on a plan that allows top-ups. [Prepaid and postpaid billing](/concepts/billing#plans) covers plans.
* A saved payment method on the customer, through a [payment provider](/concepts/payment-providers).

## Auto top-up

An auto top-up is a standing rule on one customer: when a pool's available balance falls to `threshold`, buy credits to bring it back to `target_balance`.

The plan has to allow it, and say how much a customer may commit:

**`Config document: plans`**

```json title="Config document: plans"
{
  "plans": [
    {
      "key": "scale_monthly",
      "name": "Scale",
      "billing_mode": "prepaid",
      "currency": "usd",
      "interval": "month",
      "credit_type": "ai_credits",
      "pools": [{ "key": "included" }, { "key": "purchased" }],
      "top_up": {
        "unit_amount_decimal": "1",
        "minimum_credits": "1000",
        "maximum_credits": "100000",
        "pool": "purchased",
        "low_balance_at": "2000",
        "auto": {
          "allowed": true,
          "maximum_monthly_limit": { "amount": 500000, "currency": "usd" }
        }
      },
      "rates": [{ "event_name": "agent.task", "credits_per_unit": "10" }]
    }
  ]
}
```

Then set the rule for a customer, from your backend or from the [customer view](/guides/customer-view):

**`PUT /v1/customers/{customer_id}/auto_top_up`**

```json title="PUT /v1/customers/{customer_id}/auto_top_up"
{
  "threshold": "500",
  "target_balance": "5000",
  "monthly_limit": { "amount": 20000, "currency": "usd" }
}
```

**`Response: AutoTopUp`**

```json title="Response: AutoTopUp"
{
  "id": "atu_7bN3xV9qLc",
  "object": "auto_top_up",
  "customer": "cus_9sF2kq81LmQz",
  "credit_type": "ai_credits",
  "pool": "purchased",
  "enabled": true,
  "threshold": "500",
  "target_balance": "5000",
  "monthly_limit": { "amount": 20000, "currency": "usd" },
  "purchased_this_month": { "amount": 0, "currency": "usd" },
  "status": "active",
  "consecutive_failures": 0,
  "actor": { "type": "key", "id": "key_1xB6nW3dQz", "name": "Billing backend" },
  "livemode": false,
  "created_at": "2026-11-04T13:12:09Z"
}
```

`PUT` sets the rule, `GET` reads it and `DELETE` removes it. A customer has one auto top-up, and setting it again replaces the old one. The response names the pool it keeps full. The customer needs a saved payment method, from a checkout in `payment_method` mode or an earlier top-up. Without one, the request fails with `invalid_state`.

### How a purchase works

1. After a consumption posts, if the pool's `available` balance (after holds) is at or below `threshold`, Runledger starts a purchase of `target_balance` minus `available`, kept within the plan's `minimum_credits` and `maximum_credits`.
2. It charges the customer's saved payment method at their payment provider, the same way a [top-up checkout](/concepts/billing#checkout) does. Runledger never holds the money.
3. When the payment succeeds, a paid grant with `source: auto_top_up` is created, with the plan's top-up price as its cost basis and the plan's `top_up.credits_expire_in`, or one year when the plan sets none. `auto_top_up.succeeded` is sent.
4. There is at most one purchase in flight for a customer. A second can't start until the first settles.

`monthly_limit` caps what automatic purchases may cost in a calendar month (UTC). It limits purchases, not consumption: when the next purchase would pass it, Runledger buys what fits, if that meets the minimum, and otherwise buys nothing. A customer whose limit has run out is denied when their balance does, like any customer without credits.

### When a payment fails

A failed payment sends `auto_top_up.failed` and counts as a failure. The next consumption that finds the balance low tries again. After three failures in a row the rule stops: `status` becomes `disabled`, `auto_top_up.disabled` is sent, and an `auto_top_up.disabled` [notification rule](/api-reference/notification-rules/create) emails the people you chose. A success resets the count. Set the rule again with `PUT` to turn it back on.

### Alerts without buying

`enabled: false` keeps the rule and buys nothing. `balance.low` still goes out when the balance falls to its `threshold`. That is how a customer asks for a low-balance warning without letting anything be charged.

### Sandbox

There is no card in the sandbox, so a purchase stays `pending` until you settle it with `POST /v1/test_helpers/auto_top_ups/{id}/resolve` and an `outcome` of `succeeded` or `failed`. The `auto_top_up` scenario in the MCP server's `run_scenario` tool runs a pool below its threshold and settles it both ways. Use it to see the retry and the disable after three failures.

Creating a customer's auto top-up rule, changing it and turning it off each write an [audit event](/api-reference/access-and-audit#the-audit-log) with the actor (`auto_top_up.created`, `auto_top_up.updated`, `auto_top_up.turned_off`), whether the customer, an operator or an agent did it.

## Alerts

Runledger sends alerts about a customer's balance, limits and credits as webhooks to your endpoints, and you decide how to reach the person: an email, an in-product banner or a message to an agent. Runledger never emails your end customers. [Notification rules](/api-reference/how-configuration-works#notification-rules) are for your own team and don't carry these alerts. The events are thin like every other event. The `context` field names the customer, `member_id`, `team_id`, pool, allowance, limit and threshold, so you can write the message without another call.

| Event                                                                 | Sent when                                                             | Threshold set by                                                                      |
| --------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `usage.threshold_reached`                                             | A [usage window](/guides/usage-windows) reaches a share of its amount | `alert_at` on the allowance                                                           |
| `spend_limit.threshold_reached`                                       | Spend reaches a share of a [spend limit](/guides/spend-limits)        | `alert_at` on the limit                                                               |
| `balance.low`                                                         | A pool's available balance falls to a level                           | `top_up.low_balance_at` on the plan, or the `threshold` of the customer's auto top-up |
| `balance.depleted`                                                    | A balance reaches zero                                                | Always on                                                                             |
| `credit_grant.expiring`                                               | A grant with credits left is about to expire                          | `grant_expiry_notice` on the account. See [Credit expiry](/guides/credit-expiry)      |
| `auto_top_up.succeeded`, `auto_top_up.failed`, `auto_top_up.disabled` | An automatic purchase settled, failed or stopped                      | None                                                                                  |

Each threshold fires once per window or period, and `balance.low` once each time the balance falls below its level. A customer can set their own auto top-up on the [customer view](/guides/customer-view). The limit a customer sets there blocks and has no `alert_at`, so thresholds of the customer's choosing come from a limit you set for them through the API with `set_by: customer`.

**`Webhook event`**

```json title="Webhook event"
{
  "id": "whe_3rT8nB5vQp",
  "object": "webhook_event",
  "type": "usage.threshold_reached",
  "api_version": "2026-10-18",
  "related_object": {
    "id": "cus_9sF2kq81LmQz",
    "object": "usage_summary",
    "url": "/v1/customers/cus_9sF2kq81LmQz/usage_summary"
  },
  "context": {
    "customer": "cus_9sF2kq81LmQz",
    "allowance": "weekly",
    "pool": "included",
    "threshold": "0.8",
    "window_start": "2026-11-02T00:00:00Z",
    "resets_at": "2026-11-09T00:00:00Z"
  },
  "reason": { "type": "request", "request_id": "req_7Hc2Lq0vTz8d", "idempotency_key": "conv_4821-step-7" },
  "livemode": false,
  "created_at": "2026-11-06T09:41:03Z"
}
```

Fetch `related_object.url` for the current numbers, and deduplicate on `id` like any other event. See [Webhooks](/api-reference/handle-webhooks).

## API reference

[Set](/api-reference/customers/auto-top-up/set), [retrieve](/api-reference/customers/auto-top-up/retrieve) and [delete](/api-reference/customers/auto-top-up/delete) a customer's auto top-up.

## Next steps

* [Spend limits](/guides/spend-limits) cap the money value of work in a period, a separate test from the balance.
* [Credit expiry and breakage](/guides/credit-expiry) covers how long purchased credits last: one year by default.
* [Customer view](/guides/customer-view) lets customers set their own top-up.
* [Webhooks](/api-reference/handle-webhooks) covers verifying the events these alerts send.