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

# Per-API-key spend budgets

> Set a persisted monthly spend ceiling per API key in minor currency units, with per-percentage alerts delivered once per threshold and a stable per-key usage view — so a leaked or buggy key never runs past its budget without the team knowing.

# Per-API-key spend budgets

A spend budget is a monthly ceiling, per API key, expressed as a positive integer count of **minor currency units** (for example cents) plus a 3-letter **ISO-4217 currency** such as `USD`. Set it on any active key through the developer API and Devotel Orbit will report that key's live usage against it and fire an alert the first time spend crosses each threshold you configure — so one runaway integration, or a leaked key, cannot blow its budget silently.

This surface is distinct from the existing budget controls:

* It is a **spend** ceiling (money), not a request-count budget. The per-key request quota and per-minute rate limit in [Per-API-key usage limits and spend alerts](/guides/api-key-usage-limits) block by request volume; a spend budget caps the billable amount a key may accumulate in a UTC month.
* It is **persisted on the API and shared with your team**, not held in your browser. The dashboard badge described in [Per-API-key usage budgets and threshold alerts](/guides/api-key-usage-budget-alerts) is a per-browser tripwire; the budgets you set here are stored in your organization and read back identically for every operator.

## How budgets are stored

Budgets live in your organization's `settings.api_key_budgets` — a free-form JSONB slot, so the feature requires no schema migration and applies to every existing organization immediately. It is deliberately kept separate from the `settings.api_governance` slot that holds the request-rate/quota config from [Per-API-key usage limits and spend alerts](/guides/api-key-usage-limits), so editing one surface can never bleed into the other. A key with no budget entry resolves to "unlimited" — existing, unconfigured keys behave exactly as before.

Writes apply on the next request: a 60-second per-pod read cache is invalidated on every write, so a freshly-saved budget takes effect immediately rather than waiting out the cache TTL. A reseller subaccount is its own organization, so a partner budgets a downstream key by writing this config on the child organization — no cross-tenant plumbing.

## Read the budget surface

Owner / admin / developer scope.

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/developer/budgets \
  -H "X-API-Key: dv_live_sk_..."
```

Response (one key shown):

```json theme={null}
{
  "data": {
    "keys": [
      {
        "api_key_id": "key_abc123",
        "name": "production-backend",
        "key_prefix": "dv_live_sk_prod",
        "last_four": "a1b2",
        "active": true,
        "budget": {
          "limit_minor": 50000,
          "currency": "USD",
          "period": "monthly",
          "alert_thresholds": [50, 80, 100],
          "last_notified_percent": 50
        },
        "usage": {
          "requests_this_minute": 41,
          "requests_this_month": 812345
        }
      }
    ],
    "keys_truncated": false,
    "defaults": {
      "alert_thresholds": [50, 80, 100],
      "max_alert_thresholds": 10
    }
  }
}
```

Field notes:

* `budget` is `null` for a key with no spend ceiling (unlimited); `usage` is still reported for unbudgeted keys.
* `last_notified_percent` is the alert high-water mark — the highest threshold percent already notified for this key in the current month. See the alert semantics below.
* `usage.requests_this_minute` is the key's live in-flight count for the current minute window; `usage.requests_this_month` is the count for the current UTC month — the same live metering peek the rate-limit surface uses.
* The read hydrates at most 250 keys; `keys_truncated: true` tells you the list was cut.
* `defaults.alert_thresholds` is the threshold set that applies when you do not pass one; `defaults.max_alert_thresholds` caps how many thresholds a single key may carry.

## Set or clear a budget

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/developer/budgets/key_abc123 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "limit_minor": 50000,
    "currency": "USD",
    "alert_thresholds": [50, 80, 100]
  }'
```

* `limit_minor` — positive integer ceiling in minor currency units (50000 = \$500.00 when the currency is USD). A zero, negative, or fractional value is rejected with a `422`; to remove the ceiling send `null` instead of zero.
* `currency` — 3-letter ISO-4217 code, required whenever a `limit_minor` is set. It is upper-cased on save, so `usd` reads back as `USD`.
* `period` — only `"monthly"` is supported today; the ceiling resets on the 1st of each UTC month. Any other value is a `422`.
* `alert_thresholds` — ascending integer percentages in (0, 100]. Duplicates are dropped and the list is sorted ascending. Omit it to use the default `[50, 80, 100]`; send `null` to restore the default. At most 10 thresholds per key.

To remove the budget (unlimited):

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/developer/budgets/key_abc123 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "limit_minor": null }'
```

A key that does not belong to your organization returns `404` — a write can never be applied to another tenant's key. Writes are audit-logged (`developer.api_key_budget.updated`), and the persisted value reads back byte-identical to what metering enforces, because both paths use the same validation.

## Alert semantics

Thresholds are integer percentages of the ceiling in (0, 100], held ascending and de-duplicated. Each fires **once per threshold per month**, via the `last_notified_percent` high-water mark:

* The first time the month's usage crosses a threshold, that threshold fires and the mark advances to it.
* A threshold at or below the mark never re-fires within the same month, even if spend dips back under it and crosses again.
* The mark persists across edits to the budget's amount or currency, so a simple edit does not re-fire thresholds you have already acknowledged this month.
* Clearing the budget (`limit_minor: null`) removes the entry entirely; setting a fresh budget afterwards starts a new mark, so alerts re-arm.

Worked example — a key with `limit_minor: 50000` (`$500.00` USD) and thresholds `[50, 80, 100]`:

* Spend reaches `25,000` minor units (25000 / 50000 = 50%) → `usage_percent` is 50, the 50% threshold fires, `last_notified_percent` becomes 50.
* Spend reaches `40,000` (80%) → the 80% threshold fires, the mark advances to 80.
* Spend reaches the ceiling `50,000` (100%) → the 100% threshold fires; the key is at its cap.
* Usage percentage is floored — spend of `39,999` is 79.998% but reports 79%, so it does not prematurely trip the 80% threshold.

## Roles and audit

Reading the surface requires owner, admin, or developer scope. Setting or clearing a budget is additionally restricted to owner, admin, or developer, and every write is audit-logged with the key id, the new ceiling and thresholds, and whether the operation was a clear (`developer.api_key_budget.updated`).

## How it relates to the other budget guides

Three surfaces now govern per-key usage — set the ones that match what you need:

* **Spend budget (this guide):** a money ceiling per key per month, persisted on the organization, with alert-once-per-threshold notifications. Use it to bound what a key can *spend* and to alert the team.
* **Request-count budget ([usage budgets and threshold alerts](/guides/api-key-usage-budget-alerts)):** a per-browser dashboard badge that flags a key at a request threshold. Its Warning that "budgets are stored in your browser, not on the API" applies to that badge only — the spend budgets above ARE persisted on the API and shared with your team, which is exactly what that guide recommends pairing with.
* **Request quotas and rate limits ([usage limits and spend alerts](/guides/api-key-usage-limits)):** hard per-minute and per-month request caps that return `429` when exceeded. Use those for blocking by request volume; use a spend budget for bounding by money.

## Edge behaviour

* A key with no configured budget never appears as over-limit; budgets are strictly opt-in.
* A malformed value is rejected at write time with a `422`, never persisted — so a bad hand-edit cannot fabricate a tiny cap that blocks the key, and can never throw on the read path.
* At most 10 thresholds per key; duplicates and out-of-range values are rejected or de-duplicated at write time.
* Clearing the ceiling is a `null`, not a `0` — a zero is rejected because it would hard-block the key, which is never the intent (to disable a key, revoke it).
