Recipes

Spend limits, alerts and automatic top-up

Keep AI usage spend in check: hard and soft spend limits per customer, subscription or user, limit tiers, alerts with a state per threshold, credit runway, automatic top-up, early usage invoices, a rate-change preview and a usage widget for your app.

This recipe continues Prepaid AI credits. There, your app checks the credit before each model call. Here, you put a ceiling on spend, warn the customer before the ceiling, buy more credit for them before it runs out, and bill large usage before the period ends.

Every control is off until you create the first one. All amounts are in minor units of the currency (50000 = USD 500.00). Spend is rated usage before tax and before credit, so a limit means the same thing whether credit or an invoice pays for the usage.

Set a spend limit

A limit applies to one customer: all its usage, one subscription, or each value of a usage property (each user, team or API key). interval is day, week (Monday to Sunday), month (the calendar month in your business time zone) or period (the billing period).

curl
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/customers/$CUS/spend_limits \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "scope": "customer", "interval": "month", "amount_minor": 50000, "currency": "USD", "mode": "hard" }'
  • mode: "hard": the access check answers allowed: false with code: "spend_limit_reached" when the spend in the interval, plus open holds, plus your estimate, is more than the limit.
  • mode: "soft": nothing is refused. We send the spend_limit_reached event once per interval.

Usage is never refused when you send it. Usage that arrives after the limit is reached is billed.

A refused check names the limit, so your app can tell the user when it resets:

{
  "allowed": false,
  "code": "spend_limit_reached",
  "limits": [
    {
      "id": "slim_XXXXXXXXXXXX01",
      "scope": "customer",
      "interval": "month",
      "amount_minor": 50000,
      "spent_minor": 49980,
      "resets_at": "2026-12-01T00:00:00Z"
    }
  ]
}

Shorter intervals cannot be more than longer ones: a weekly limit above the monthly limit is refused with a 422 that names the field.

A limit for each user or API key

First add the property to the meter's limit_dimensions (at most 3 per meter). Then limit each value, or one value:

{ "scope": "property", "property_key": "user_id", "interval": "day", "amount_minor": 2000, "currency": "USD", "mode": "hard" }

With no property_value, each user gets USD 20.00 a day. A row with a value ("property_value": "user-17") wins over it for that user. A property limit counts usage from the time the property was added to the meter. Send the property in the access check (scope) or the limit reports status: "not_checked".

Raise limits as customers pay

A limit tier raises the customer limit when the customer's lifetime paid amount reaches a level. The higher of the customer's own limit and the tier applies. A refund never lowers a tier.

curl
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/spend_limit_tiers \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "currency": "USD", "level_minor": 10000, "amounts": { "month": 50000 } }'

Limits your customer sets

In the customer portal, the customer can add a hard limit of its own and lower or delete it. It can never be more than your limit for the same scope and interval, and the lower of the two applies. Your limits show in the portal and the customer cannot change them.

Warn before the limit

An alert watches spend, usage_quantity, credit_balance or credit_runway. Thresholds are absolute or a percent: of the spend limit, of the feature's limit, or of the credit given.

curl
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/usage_alerts \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "spend",
    "customer": "'$CUS'",
    "interval": "month",
    "currency": "USD",
    "thresholds": [{ "value": 50, "unit": "percent" }, { "value": 80, "unit": "percent" }, { "value": 100, "unit": "percent" }],
    "email": true
  }'

Send product instead of customer for a plan alert: it applies to every subscription to the plan, also to new ones.

Each threshold fires usage_alert_triggered once per period. One large jump fires every threshold that it passes. When the value goes back (a top-up, voided usage) or the period ends, the threshold sends usage_alert_resolved. To rebuild your view without webhooks, read the state of each threshold:

curl
curl "$TMBILLING_URL/v1/businesses/$BIZ/usage_alerts/$ALERT/states?customer=$CUS" \
  -H "Authorization: Bearer $TMBILLING_API_KEY"

Each threshold is armed, triggered or resolved, with triggered_at, resolved_at, resolve_reason and resets_at.

Show the credit runway

curl
curl "$TMBILLING_URL/v1/businesses/$BIZ/customers/$CUS/credit_runway" \
  -H "Authorization: Bearer $TMBILLING_API_KEY"
{
  "object": "credit_runway",
  "currency": "USD",
  "estimate": true,
  "runway_days": 12,
  "runs_out_at": "2026-11-14T09:00:00Z",
  "balance_minor": 6000,
  "burn_minor_per_day": 500,
  "auto_topup_minor": 0,
  "message": "Runs out in about 12 days"
}

The burn is the spend of the last 7 days ÷ 7. With less than 24 hours of usage, runway_days is null ("Not enough usage to estimate"). What an automatic top-up can still buy this month is included.

Top up credit automatically

One configuration per customer and currency. It buys credit when the balance falls to a threshold, or on a schedule, and adds the credit only after the payment succeeds:

curl
curl -X PUT $TMBILLING_URL/v1/businesses/$BIZ/customers/$CUS/auto_topup \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "USD",
    "trigger": "threshold",
    "threshold_minor": 1000,
    "method": "fixed",
    "amount_minor": 5000,
    "monthly_cap_minor": 50000,
    "max_runs_per_day": 3,
    "grant": { "tax_treatment": "taxed_at_purchase" }
  }'
  • method: "fixed" adds amount_minor (a multiple of it when one is not enough to go above the threshold). method: "target" fills up to target_minor.
  • Each purchase stays inside the business's top-up limits (credit_config.top_up_limits), the monthly_cap_minor and max_runs_per_day.
  • "trigger": "schedule" with "schedule": { "every": "month", "day": 1 } or { "every": "week", "weekday": 1 } buys on fixed dates. Day 31 buys on the last day of a shorter month.
  • With no payment_method, we charge the customer's payment methods in their order.

If the payment is declined, we void its invoice, turn automatic top-up off and send credit_auto_topup_failed. We do not try again. Turn it on again with POST …/auto_topup/enable after the customer updates the payment method. GET …/auto_topup/runs lists each purchase.

Mandates in India

On a mandate that needs a notice before each debit, the notice goes out first and the charge happens after it, 26 to 49 hours later on Indian gateways (10 days for US bank accounts). So a threshold must cover that time at the current usage. A threshold that covers less is refused at save, with the amount that is enough. A card or UPI mandate can pay at most ₹15,000 without the payer, so each purchase is at most ₹15,000. If usage grows past what the threshold covers, we send credit_auto_topup_threshold_low once a week. Until the credit arrives, the access check can answer insufficient_credit.

Invoice usage early

Bill usage during the period each time the unbilled usage of a subscription reaches an amount:

curl
curl -X PUT $TMBILLING_URL/v1/businesses/$BIZ/products/$PLAN/usage_invoice_thresholds/USD \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "amount_minor": 50000 }'

PUT …/subscriptions/$SUB/usage_invoice_threshold sets an amount for one subscription, or { "amount_minor": null } turns it off for that subscription. We check about one minute after the usage arrives. Each early invoice has purpose: "usage_interim", and each usage line shows cumulative_minor (usage to date) and billed_before_minor. Discounts and the minimum spend apply only on the invoice at the end of the period, and all the invoices together bill exactly what one invoice would. Volume pricing and the last, min and avg aggregations bill only at the end of the period.

Preview a rate change

Before you change a rate, see what each customer's last closed period would cost at the new rate. It writes nothing:

curl
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/prices/$PRICE/rate_change_preview \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "unit_amount_decimal": "0.00025" }'

The answer lists old_minor, new_minor, diff_minor and diff_bps per customer, largest increase first, and the totals.

Show usage in your app

The usage widget shows the balance, the runway, the usage this period, the estimated bill and the limits, inside your own page. Your backend creates a token for the signed-in customer:

curl
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/customers/$CUS/usage_widget_tokens \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

The uwt_… token reads only that customer's data, accepts no writes and stops working after 15 minutes. Add your app's origin in Settings → Usage controls (or PUT /v1/businesses/$BIZ/settings/usage_widget with { "origins": ["https://app.example.com"] }), then copy the snippet from that page:

<script src="https://api.example.com/widget/v1/usage.js" integrity="sha384-…" crossorigin="anonymous"></script>
<div data-usage-widget></div>
<script>
  UsageWidget.mount(document.querySelector("[data-usage-widget]"), {
    getToken: () => fetch("/api/usage-widget-token").then((r) => r.text()),
  });
</script>

The widget calls getToken again when the token expires. Theme it with CSS variables on the element: --uw-font, --uw-fg, --uw-muted, --uw-bg, --uw-border, --uw-radius and --uw-accent.

In the dashboard, open a customer and select View usage controls for limits, alerts, runway and automatic top-up. Plan alerts, limit tiers and the widget are in Settings → Usage controls.
Copyright © 2026