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

# Rate limits and spend controls

> The limits that can refuse a request, what the spend rate cap counts, and the one lever that makes it bind less often.

export const FeatureStatus = ({missing, feature, instead, detail, children}) => <Info>
    <strong>Not available yet: {missing ?? feature}.</strong>
    {instead ?? detail ? ` ${instead ?? detail}` : null} This page documents what
    the product does today. When that changes, this page changes with it.
    {children}
  </Info>;

export const NotDocumented = ({subject, reason}) => <Info>
    <strong>{subject} is not documented here.</strong> {reason} We would rather
    say nothing than describe something you cannot use.
  </Info>;

Several limits can refuse a request, and they answer with different codes on
purpose: you should be able to tell "too fast for this key" from "too much money
attempted too quickly" from "this key is done" without asking us.

**Nothing is charged for a request we refuse**, whichever limit refused it.

## The refusals, and what each one means

| Status | `code`                     | What ran out                                                                 | What to do                                                                                              |
| ------ | -------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| 429    | `rate_limit_exceeded`      | This key's request rate.                                                     | Wait the number of seconds in `Retry-After`.                                                            |
| 429    | `spend_rate_exceeded`      | Your spend rate cap, which counts money reserved rather than money spent.    | Wait up to 60 seconds, or send `max_tokens`. See below.                                                 |
| 429    | `upstream_rate_limited`    | Nothing of yours. The model's provider rate-limited the call.                | Wait the number of seconds in `Retry-After`. Nothing about the request is wrong and nothing is charged. |
| 413    | `payload_too_large`        | Nothing. The request body is over the 8388608 byte maximum this API accepts. | Send fewer messages, a shorter prompt, or fewer inputs.                                                 |
| 402    | `insufficient_credit`      | Available credit at admission.                                               | Buy credit.                                                                                             |
| 402    | `key_limit_exceeded`       | This key's own credit limit, for its current window.                         | Wait for the window to roll, raise the limit, or send on another key.                                   |
| 402    | `member_limit_exceeded`    | Your own monthly spend limit, for the current calendar month.                | Ask the workspace owner to raise or clear it. Your colleagues are unaffected.                           |
| 402    | `workspace_limit_exceeded` | The workspace's monthly spend limit, for the current calendar month.         | Contact us: there is no self-service control for this figure yet.                                       |
| 401    | `key_expired`              | Nothing. The key's own expiry date has passed.                               | Create a new key. Expired keys are never renewed.                                                       |
| 503    | `paused_by_owner`          | Nothing. The workspace owner has paused requests.                            | Lift the pause in Settings.                                                                             |

Every refusal arrives in the standard error envelope, and the `code` field is the one
to branch on. Messages are prose and may be reworded; the fields are the contract.

## The per key request rate

Each API key has a request rate, counted per minute. Over it, the request is refused
with HTTP 429, code `rate_limit_exceeded`, and a `Retry-After` header carrying the
number of seconds to wait:

```json theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
{
  "error": {
    "message": "Rate limit reached for this key. Try again in 12 seconds. Request id req_XXXXXXXXXXXX.",
    "type": "rate_limit_error",
    "param": null,
    "code": "rate_limit_exceeded",
    "request_id": "req_XXXXXXXXXXXX"
  }
}
```

This limit is counted per key, so splitting work across two keys splits the counter.
The figure in force is shown in the Limits section of Settings.

## The spend rate cap

The spend rate cap is the control that stops a key leaked into a public repository
costing you your whole balance in the minute before you notice.

**The default is 5000 minor units per rolling 60 seconds.** Over it, a request is
refused with HTTP 429 and code `spend_rate_exceeded`:

```json theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
{
  "error": {
    "message": "This account has already reserved 5000 minor units in the last 60 seconds, which is at its ceiling of 5000. Requests are admitted again within 60 seconds of the earliest reservation counted, with nothing to do in the meantime.",
    "type": "rate_limit_error",
    "param": null,
    "code": "spend_rate_exceeded",
    "request_id": "req_XXXXXXXXXXXX"
  }
}
```

It clears itself. Sixty seconds after the earliest reservation counted, requests are
admitted again, with nothing for you to do in the meantime and nothing to reset.

### What it counts, which is not what you spent

**The cap counts money reserved, and a reservation is an estimated maximum.**

When a request is admitted we hold the most it could possibly cost. That figure comes
from the request's own `max_tokens` where it names one, and from the model's published
maximum output length where it does not. The request then settles for what it actually
produced and the rest of the hold comes straight back.

The window counts what was reserved, when it was reserved, and nothing is ever taken
back out of it. A released hold, an expired hold and a settled hold all go on counting
for their full 60 seconds. That is deliberate rather than convenient: a leaked key
makes requests that finish, thousands a minute, holding almost nothing at any one
instant, and a ceiling computed from live holds would be a limit on concurrency that
such traffic never approaches.

Two consequences you can see:

* A minute of ordinary traffic that all completed successfully still counts against
  the ceiling for a minute afterwards.
* On an expensive model, requests that omit `max_tokens` hold the model's whole
  published maximum output. The cap can therefore bind well below 50.00 a minute of
  real spending, because the money it is counting is money you were never going to
  spend.

### The lever: send `max_tokens`

The hold is the smaller of the `max_tokens` you asked for and the model's published
maximum output length. A client that names a realistic ceiling gets several times
more requests a minute out of the same cap **and is charged exactly what it was
before**, because the charge has always been for what was actually produced.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
  curl https://api.hopscotchlabs.ai/v1/chat/completions \
    -H "Authorization: Bearer ub_live_XXXXXXXX" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "openai/gpt-4o-mini",
      "max_tokens": 400,
      "messages": [{"role": "user", "content": "Say hello."}]
    }'
  ```

  ```python Python theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
  completion = client.chat.completions.create(
      model="openai/gpt-4o-mini",
      max_tokens=400,
      messages=[{"role": "user", "content": "Say hello."}],
  )
  ```

  ```typescript Node theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
  const completion = await client.chat.completions.create({
    model: 'openai/gpt-4o-mini',
    max_tokens: 400,
    messages: [{ role: 'user', content: 'Say hello.' }],
  });
  ```
</CodeGroup>

If you are running a batch job, a load test or an evaluation sweep, this is the first
thing to change.

### Out of credit and over the cap at the same time

You are told about the money. An account that is both out of credit and over its
ceiling gets the 402, because the cap clears itself in 60 seconds and telling someone
with no credit to slow down is advice that cannot work.

## The limits that belong to one key

The limits above this line belong to the workspace or to the account, so they stop
every key at once. Two limits belong to a single key, and the difference is the point:
the rest of the workspace goes on serving while one key is refused.

**A key may carry its own expiry date.** A key presented after that date is refused
with HTTP 401 and code `key_expired`, and the message names the instant it expired on,
so a workspace holding several keys can tell which one ran out. It is checked before
the per key request rate, so an expired key never consumes that allowance. Nothing
renews a key: create a new one on the Keys screen and replace the old one wherever it
is configured.

The code is its own rather than `invalid_api_key` because the two are opposite advice.
An invalid key means the string is not a key here, and the fix is to find the right
one. An expired key means this was your key and the date you chose has passed.

**A key may also carry its own credit limit.** A request that would take the key past
it is refused with HTTP 402 and code `key_limit_exceeded`, before any credit is held,
and the message names what that key has settled and holds inside its current window
against what it may spend.

That is a different refusal from `insufficient_credit`, which is the account having no
money and stops every key on it. A key at its own limit leaves the other keys in the
workspace serving from the same balance, and buying credit does not clear it. What
clears it is the window rolling, where the limit resets on a cadence, or somebody
raising or removing it on the Keys screen. A limit set never to reset does not clear
by waiting.

Requests served on your own provider key do not count against a key's credit limit and
are never refused by it: the money it counts is money spent from your balance, and
those requests spend none of it. See [Bring your own
key](/concepts/bring-your-own-key).

**Nothing is charged when either refuses**, and no credit is held.

## Which limits are yours to raise

The Limits section of Settings lists your limits one unit at a time, and marks each
one as yours or shared. The distinction is the rule, not a label:

> This protects your own money. You may raise it yourself, up to the limit shown.

applies to the spend rate cap, and

> This protects a provider account we share across every customer. Raising it is a
> decision we make, not one you can make yourself.

applies to the request and token rates. Raising a shared rate trades away another
customer's room, so it is a decision made by someone weighing the whole pool. There
is no self service path for those figures, and there is no hidden one either.

Two of the rows carry no figure yet, and the screen says why:

> Not yet enforced. We are not checking your traffic against this figure yet.

They are drawn because the units we meter you in are fixed now rather than later. A
row with no figure means nobody is checking it, and it never means you have no room.

### Setting your own spend rate cap

The spend rate cap is a whole number of at least 100 minor units per rolling 60
seconds. The screen shows the highest value you may set yourself; above that, ask us.
Lowering your cap always works, including from a figure above the ceiling.

Setting a cap of 0 is refused. Stopping requests entirely is what the pause is for,
and it has its own control, its own state and its own sentence.

<Note>
  Only the workspace owner can manage its billing: the card and the buying of
  credit are the owner's own. Members may read the balance and the statement of
  the workspace they are in.
</Note>

Reading your limits is open to every member of the workspace. Changing the spend rate
cap is the owner's.

The Limits section and the self service cap control are shipped: the figures and the
sentences behind them are the platform's own, read live rather than written into this
page.

## Pausing everything

The pause refuses every request on the workspace's keys straight away, without
revoking anything. Requests are refused with HTTP 503 and code `paused_by_owner`, and
nothing is charged for them. Lifting it starts everything again with no keys to
re-create.

Requests that were already running when you paused are not cut off. Each finishes and
is charged for what it really used.

Any member may read whether requests are paused. Only the owner may press it or lift
it. The full flow, including the numbers the screen states before you press, is in
[Spend controls](/guides/spend-controls).

## Next

<CardGroup cols={2}>
  <Card title="Spend controls" href="/guides/spend-controls">
    Setting the cap, the low credit notice, and the pause, step by step.
  </Card>

  <Card title="Credits and billing" href="/concepts/credits-and-billing">
    Holds, available balance, and what happens at zero.
  </Card>
</CardGroup>

<Note>
  Every response carries the same id on two headers: `x-hopscotch-request-id`,
  which is ours, and `x-request-id`, which is the one the official OpenAI SDKs
  surface on their error objects. Every error envelope repeats it as
  `request_id`. Quote it when you ask us about a request. Without it we are
  guessing at which of your requests you mean.
</Note>
