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

# Spend limits and concurrency

> Capping what a key can spend per day, how the number is computed, and the ceiling on simultaneous rentals.

A key with `rentals:write` can spend money. Two independent rails bound how much:
a **daily spend limit** per key, and a **ceiling on concurrent rentals**.

Neither is on by default. Both are worth setting before a scheduler goes anywhere
near production.

## Daily spend limit

Set it in **Settings → API keys → Edit** on any live key. It is a per-day budget
in dollars; the API works in cents.

When a rental would cross the limit, creation is refused with `402
key_spend_limit_exceeded` — and the error carries the numbers, so your handler
can report the situation instead of guessing at it:

```json theme={null}
{
  "error": {
    "type": "billing_error",
    "code": "key_spend_limit_exceeded",
    "message": "This key's daily spend limit would be crossed by this rental. Raise the limit, use another key, or wait for the reset.",
    "limit_cents": 5000,
    "committed_cents": 4700,
    "resets_at": "2026-07-30T00:00:00.000Z",
    "doc_url": "https://docs.gpuoutlet.ai/api/errors#key_spend_limit_exceeded",
    "request_id": "req_c41f7b09ea56"
  }
}
```

<Warning>
  `billing_error` means the request was well-formed and permitted, but the account
  cannot pay for it. **Retrying it unchanged will not help.** Back off until
  `resets_at`, or use a key with room.
</Warning>

### What counts as spent

This is the part worth reading twice, because "spent" is ambiguous while a
machine is still running.

For each rental the key has started since 00:00 UTC, we take the **larger** of:

* what it has **accrued** so far (plus any overage), and
* the **hold** currently placed on your balance.

then add those up across rentals.

<Note>
  Taking the larger of the two, rather than one or the other, is what makes the
  limit safe in both directions.

  Counting only accrual would let a key start twenty machines against a \$50 limit
  — none of them has accrued anything yet in the first second, so all twenty pass.

  Counting only holds would *undercount* a long-running rental: the hold is
  roughly an hour of runtime, so a machine twelve hours in would still look like
  one hour of spend.

  The maximum tracks whichever is currently the honest figure — the hold while the
  rental is young, the accrual once it outgrows it.
</Note>

### Resets

The window is a **UTC calendar day**, not a rolling 24 hours. At 00:00 UTC the
committed figure returns to zero. `resets_at` in the error tells you exactly when.

A rental that started yesterday and is still running keeps accruing, and that
accrual counts toward *today's* figure — the limit bounds what a key commits per
day, not what it started per day.

### Bounds

|               | Value                  |
| ------------- | ---------------------- |
| Minimum limit | **\$1.00** (100 cents) |
| Maximum       | None                   |
| No limit      | Leave it empty         |

A limit below the minimum hold on a rental would reject every rental including
the first, which is a broken key rather than a cautious one — so we refuse to set
one, and say why.

### Removing a limit

Clear the field. The key returns to unlimited. There is no "set it to zero to
block the key" — a zero limit is indistinguishable in effect from a revoked key,
and revocation is the honest way to express that.

## Concurrent rentals

Separately from money, an account can hold a limited number of rentals in a live
state (`provisioning`, `running`, or `stopping`) at once.

**The default is 3.** Ask support if you need more; both the account ceiling and
a per-key one can be raised.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "max_active_rentals_reached",
    "message": "This account is at its limit of concurrent rentals. Stop one before starting another.",
    "doc_url": "https://docs.gpuoutlet.ai/api/errors#max_active_rentals_reached",
    "request_id": "req_77b0e2ca4d19"
  }
}
```

Two things to know:

* **The ceiling is per account, not per key.** Three keys do not get nine
  rentals. See [keys scope to the account](/api/keys#keys-scope-to-the-account-not-to-themselves).
* **`stopping` still counts.** A rental you asked to stop occupies a slot until
  it reaches `stopped`. If you stop one and immediately start its replacement,
  you can hit the ceiling with what looks like one machine. Poll for `stopped`
  first.

When both a per-key and an account ceiling exist, the **lower** applies. A key
allowed 10 on an account allowed 3 gets 3.

## Watching the numbers

The dashboard shows, per key, what it has spent today against its limit and which
rentals it started. See [Monitoring](/api/key-security#watching-what-a-key-does).

From the API, the same picture comes from listing what is live:

```bash theme={null}
curl -s -G https://api.gpuoutlet.ai/v1/rentals \
  -H "Authorization: Bearer $GPUOUTLET_API_KEY" \
  -d active=true
```

Summing `billing.accrued_cents` over that page gives you what is currently
accruing — close to, but not identical to, the committed figure the limit uses,
because it ignores holds on very young rentals. For a hard number, read the
`committed_cents` that comes back in the error.

## A safe scheduler

<CardGroup cols={2}>
  <Card title="Set a daily limit" icon="gauge-high">
    Sized to a bad day, not an average one.
  </Card>

  <Card title="Handle 402 as terminal" icon="hand">
    Back off to `resets_at`; do not retry in a loop.
  </Card>

  <Card title="Poll for `stopped`" icon="clock">
    Before starting a replacement, so `stopping` does not eat your slot.
  </Card>

  <Card title="One key per integration" icon="key">
    So one runaway loop cannot spend the other integration's budget.
  </Card>
</CardGroup>
