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

# Configuring LLM Controls

> Set token budgets, rate limits, and model access policies on traffic flowing through the Barndoor LLM Gateway.

## Overview

Once your LLM Gateway is set up — see [Using the LLM Gateway](/how-tos/use-llm-gateway) — the **Controls** group of **LLM Management** is where admins govern how the people and services in their organization actually consume it: capping spend, throttling busy callers, and restricting which models a given role can use.

<Frame>
  <img width="250px" src="https://mintcdn.com/barndoor/aNN2f2MGdwSwBA-w/images/llm-controls/01-llm-controls-tabs.gif?s=da6a1211b0fa64eae32f964ccd88d7b9" alt="The Controls group of the LLM Management hub showing the Budgets, Rate Limits, and Model Access sections" data-path="images/llm-controls/01-llm-controls-tabs.gif" />
</Frame>

The **Controls** group of **LLM Management** has three sections:

| Section          | What it does                                                                                                                                                                                                                                                                  |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Budgets**      | Cap how many tokens — and optionally how much money — a scope (org, group, role, or user) is allowed to spend in a daily, weekly, or monthly period, optionally narrowed to a specific provider, model route, or upstream model. Block requests once exhausted, or warn only. |
| **Rate Limits**  | Throttle a scope with requests-per-minute and / or tokens-per-minute caps measured over a rolling 60-second window.                                                                                                                                                           |
| **Model Access** | Allowlist or denylist specific models, providers, or upstream model identifiers for an org, group, role, user, or API key.                                                                                                                                                    |

<Note>
  Per-request usage and cost observability lives separately, under **Reporting → LLM Usage Dashboard**.
</Note>

## Before You Begin

You'll need:

* A Barndoor account with **admin** privileges.
* An LLM Gateway that's already configured with at least one provider and one route — see [Using the LLM Gateway](/how-tos/use-llm-gateway).
* **Spending budgets** work out of the box — Barndoor costs requests against its managed default pricing catalog automatically. Set your own per-model pricing under **LLM Management → Model Pricing** only when you want to override those defaults. See [Managing Model Pricing](/how-tos/manage-model-pricing) for the full guide.

## How Limits Compose

When a caller sends a request to the gateway, Barndoor evaluates policies in a fixed order. The first one that denies stops the chain and returns an HTTP error.

```mermaid theme={null}
flowchart TB
    Request["Inbound request"]
    Auth["API-key auth<br/>(401 on failure)"]
    Concurrency["Concurrency admission<br/>(429 when at capacity)"]
    RateLimit["Rate limits<br/>RPM &amp; TPM (429)"]
    Budget["Token / spending budget<br/>(429 when exhausted)"]
    Resolve["Model resolution<br/>(404 when unknown)"]
    Access["Model access policy<br/>(403 when denied)"]
    Upstream["Forward to upstream provider"]

    Request --> Auth --> Concurrency --> RateLimit --> Budget --> Resolve --> Access --> Upstream
```

A few things worth knowing about this chain:

* **Counters are debited *after* the upstream call returns**, using actual prompt + completion token counts. The pre-check is what enforces the limit; the post-debit is what keeps the counter honest.
* **Warn-only budgets never deny.** They still record usage and fire alerts at their configured thresholds, but the request goes through.
* **Multiple policies can apply at once.** If a user is covered by both a user-scoped budget and an org-scoped budget, both are pre-checked and both are debited; the most restrictive one is what denies.
* **Target-bound budgets are the exception to this ordering.** A budget pinned to a [Target](#token-budgets) is evaluated *after* model resolution, per route — it can remove an exhausted target and let the request fail over rather than denying outright.

***

## Token Budgets

Budgets cap how much a scope is allowed to consume in a rolling window — daily, weekly, or monthly. They can cap **tokens**, **spending**, or both (whichever hits its limit first denies the request), and can optionally apply to just one slice of traffic via a **Target**.

<Frame>
  <img src="https://mintcdn.com/barndoor/R2cAUz0zJ6SKh319/images/llm-controls/02-budgets-list.png?fit=max&auto=format&n=R2cAUz0zJ6SKh319&q=85&s=e127b3fb63e0b4d5024732c4435516d5" alt="Budgets section listing with token and spending progress per budget" width="1442" height="238" data-path="images/llm-controls/02-budgets-list.png" />
</Frame>

Once you have more than a handful, the list supports search, pagination, and sorting — by **usage** (percent of limit consumed, the default so near-exhausted budgets float to the top), **period**, or **name**.

### Creating a budget

<Steps>
  <Step title="Open LLM Management → Budgets and click Create Budget">
    The dialog opens with sensible defaults; fill in the fields below.
  </Step>

  <Step title="Name and scope">
    * **Name** — anything short and descriptive. The name surfaces in the denial message the caller sees, so make it identifiable (for example `Engineering monthly` or `Sales-team daily cap`).
    * **Scope** — pick *Org* to cover everyone, or *Group*, *Role*, or *User* to target a slice. Picking a scope without choosing a specific entity means "every entity of that type in this org".
  </Step>

  <Step title="Period and action">
    * **Period** — `Daily`, `Weekly`, or `Monthly`. Counters reset at the start of each period.
    * **Action when exhausted** — choose **Block Requests** (default) to return `429` once the limit is hit, or **Warn Only** to keep accepting requests and just fire alerts at the threshold percentages.
  </Step>

  <Step title="Target (optional)">
    By default a budget counts **all** traffic that matches its scope. Set a **Target** to narrow it to a single routing dimension instead:

    | Target                      | Counts only…                                                              |
    | --------------------------- | ------------------------------------------------------------------------- |
    | **All targets** *(default)* | Everything in scope — no narrowing.                                       |
    | **LLM Provider**            | Requests served by one specific provider connection.                      |
    | **Upstream Model**          | One specific upstream model name (for example `gpt-4o`).                  |
    | **Model Route**             | One specific client-facing route name (the value callers put in `model`). |

    **Scope and Target are independent dimensions**: Scope is *who* the budget covers (org / group / role / user); Target is *what* it counts. A target-bound budget only debits usage for requests matching **both**. Target can't be edited after creation — to retarget a budget, delete it and create a new one.
  </Step>

  <Step title="Limits">
    * **Token limit** — total prompt + completion tokens allowed in the period.
    * **Spending limit (optional)** — a dollar cap. Costed from per-model pricing, which Barndoor's default catalog supplies automatically (override it under [Managing Model Pricing](/how-tos/manage-model-pricing)).
    * **Alert thresholds** — percentages at which Barndoor emits an alert (default `80`, `90`).
  </Step>

  <Step title="Save">
    The new budget appears in the tab with a progress bar. Status refreshes roughly every 30 seconds.
  </Step>
</Steps>

<Frame>
  <img width="500px" src="https://mintcdn.com/barndoor/aNN2f2MGdwSwBA-w/images/llm-controls/03-create-budget_v2.png?fit=max&auto=format&n=aNN2f2MGdwSwBA-w&q=85&s=da94a89ed7e53bab9f2a270375694ac4" alt="Create Budget dialog with name, period, scope, action, target, and limits filled in" data-path="images/llm-controls/03-create-budget_v2.png" />
</Frame>

### How budgets are counted

* **Tokens** — prompt + completion tokens reported by the upstream provider on every request, summed per (budget, period).
* **Spending** — for each request, Barndoor multiplies prompt and completion tokens by the input and output prices in **LLM Management → Model Pricing** for that model.
* **Target** — a target-bound budget only counts requests that match its Target dimension; an *All targets* budget counts every request in scope.
* **Reset** — at the start of the next period (next day at 00:00 UTC for daily, next Monday for weekly, the 1st for monthly).

### When a caller hits the limit

A blocking budget returns:

```http theme={null}
HTTP/1.1 429 Too Many Requests
content-type: application/json
```

```json theme={null}
{
  "error": {
    "message": "Token monthly budget exhausted (budget: Engineering monthly) (100% used: 1001234 / 1000000 tokens).",
    "type": "budget_exhausted",
    "code": null
  }
}
```

Spending budgets use the same shape with `"Spending …"` in the message.

<Note>
  **Target-bound budgets fail over instead of hard-blocking.** A budget with **Target = All targets** is checked up front and returns the `429` above as soon as it's exhausted. A budget pinned to a specific Target (provider, upstream model, or route) is enforced *after* the gateway resolves the route plan: exhausting it removes only the **matching** target from that route, and the gateway fails over to any remaining healthy targets. Callers see the `429` only when **every** eligible target for the route is exhausted or otherwise unavailable.
</Note>

<Note>
  You'll usually see `tokens_used` slightly *above* `token_limit` in the denial. That's expected: the gateway pre-checks the counter on the way in, forwards the call, and debits the actual prompt + completion tokens once the provider responds. The request that pushed the counter over the limit is allowed to finish; the next one is denied. The percentage is rounded for display, so a counter of 1,001,234 / 1,000,000 still prints as `100%`.
</Note>

<Tip>
  Use a **token** budget for predictability and a **spending** budget when you care about dollar amounts independent of model. Setting both together is the safest configuration — token caps absorb a price-list change without a sudden spike, and spending caps catch a high-priced model nobody noticed.
</Tip>

***

## Rate Limits

Rate limits throttle a scope on a **rolling 60-second window**. They're the right tool when you want to smooth bursts and prevent a single noisy caller from drowning out everyone else. (Budgets are the right tool when you want to control total spend over a day, week, or month.)

<Frame>
  <img src="https://mintcdn.com/barndoor/R2cAUz0zJ6SKh319/images/llm-controls/04-rate-limits-list.png?fit=max&auto=format&n=R2cAUz0zJ6SKh319&q=85&s=cf86e668473b3442ec49df33e1f9409f" alt="Rate Limits section listing with live RPM and TPM usage bars" width="1446" height="183" data-path="images/llm-controls/04-rate-limits-list.png" />
</Frame>

The list supports search, pagination, and sorting — by **live** usage (rolling 60-second window, the default so throttled policies surface first), **name**, **scope**, **RPM**, **TPM**, or **enabled** status.

### Creating a rate limit

<Steps>
  <Step title="Open LLM Management → Rate Limits and click Create Rate Limit">
    The dialog appears with the same scope picker pattern as Budgets.
  </Step>

  <Step title="Name and scope">
    * **Name** — appears in the denial message the caller sees.
    * **Scope** — *Org*, *Group*, *Role*, *User*, *API Key*, or *Model*. Selecting *Model* lets you cap a particular alias across all users.
  </Step>

  <Step title="Limits">
    Fill in at least one of:

    * **Requests per minute** — caps the number of inbound requests, regardless of size.
    * **Tokens per minute** — caps the sum of prompt + completion tokens over the same window.

    You can set both, or only one. The form rejects the policy if both are blank.
  </Step>

  <Step title="Save">
    The new policy appears in the table with live RPM and TPM bars. Status refreshes roughly every 10 seconds.
  </Step>
</Steps>

<Frame>
  <img src="https://mintcdn.com/barndoor/R2cAUz0zJ6SKh319/images/llm-controls/05-create-rate-limit.png?fit=max&auto=format&n=R2cAUz0zJ6SKh319&q=85&s=0ec5acdc2bf9723ae486d5b5e22608bc" alt="Create Rate Limit dialog with name, scope, and both requests-per-minute and tokens-per-minute set" width="501" height="349" data-path="images/llm-controls/05-create-rate-limit.png" />
</Frame>

### When a caller hits the limit

```http theme={null}
HTTP/1.1 429 Too Many Requests
retry-after: 60
content-type: application/json
```

```json theme={null}
{
  "error": {
    "message": "Rate limit exceeded (policy: Sales-team rate cap). Try again in 60 seconds.",
    "type": "rate_limit_error",
    "code": null
  }
}
```

<Note>
  The `Retry-After` header and the seconds-value in the message always match the **window length** — 60 seconds, since rate limits use a rolling 60-second window. It's a conservative "wait one full window and you're guaranteed to be unblocked" hint; the caller may often succeed sooner as older events fall out of the window. Clients that respect `Retry-After` (the OpenAI / Anthropic SDKs do) will back off appropriately.
</Note>

<Tip>
  RPM and TPM live on the same policy and use the same scope, but Barndoor enforces them independently. A caller that's under the RPM cap can still get throttled by the TPM cap (and vice versa).
</Tip>

***

## Model Access

Model access policies decide whether a caller is allowed to invoke a particular model at all. They support both **allowlists** ("only let this scope call these models") and **denylists** ("this scope must not call these models").

<Frame>
  <img src="https://mintcdn.com/barndoor/x6czZKU8D69Pp3tU/images/llm-controls/06-model-access-list.png?fit=max&auto=format&n=x6czZKU8D69Pp3tU&q=85&s=ac3137f1bd202b481f16bbcaff4e23e0" alt="Model Access tab with allowlist and denylist policies" width="1419" height="313" data-path="images/llm-controls/06-model-access-list.png" />
</Frame>

### Creating a policy

<Steps>
  <Step title="Open LLM Management → Model Access and click Create Policy">
    Pick **Allowlist** for "only these models" or **Denylist** for "everything except these models".
  </Step>

  <Step title="Name and scope">
    * **Name** — appears in the denial message.
    * **Scope** — *Org*, *Group*, *Role*, *User*, or *API Key*.
  </Step>

  <Step title="Add at least one target">
    Each target is one rule about *which* models the policy covers. You can mix and match any number of these:

    | Target kind          | Matches when…                                                                                                  | Example                    |
    | -------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------- |
    | **Model route**      | The caller's `model` field matches a route you've defined in **Model Routes**. Supports trailing `*` wildcard. | `claude-*`                 |
    | **Upstream model**   | The resolved upstream model name (the string the gateway sends to the provider).                               | `gpt-4o`                   |
    | **Provider**         | Any model served by the selected provider.                                                                     | `openai-prod`              |
    | **Provider + model** | A specific provider/upstream-model pair.                                                                       | `azure-east + gpt-4o-mini` |
  </Step>

  <Step title="Save">
    The new policy appears in the table. Enable / disable any policy from the table without deleting it.
  </Step>
</Steps>

<Frame>
  <img src="https://mintcdn.com/barndoor/x6czZKU8D69Pp3tU/images/llm-controls/07-create-model-access.png?fit=max&auto=format&n=x6czZKU8D69Pp3tU&q=85&s=727b0153c9c4327681b48e2510fb40b6" alt="Create Model Access dialog showing the target builder with multiple target kinds" width="504" height="672" data-path="images/llm-controls/07-create-model-access.png" />
</Frame>

### How allowlists and denylists combine

* If **any allowlist** applies to a caller, at least one of its targets must match — otherwise the call is denied.
* If **any denylist** applies, none of its targets may match — otherwise the call is denied.
* A caller with both an allowlist and a denylist must satisfy both.
* A caller with neither has unrestricted access (subject to budgets and rate limits).

### When a caller is denied

```http theme={null}
HTTP/1.1 403 Forbidden
content-type: application/json
```

```json theme={null}
{
  "error": {
    "message": "Model 'claude-opus-4' is blocked by access policy: External-models-only",
    "type": "permission_error",
    "code": null
  }
}
```

The exact wording differs slightly for allowlist denials ("not allowed by access policy …") vs denylist denials ("blocked by access policy …").

<Tip>
  Use **denylists** to carve out a small set of exceptions to an otherwise-open posture. Use **allowlists** when you want to start closed and only open up known-good models. Allowlists are the safer default for highly regulated environments.
</Tip>

***

## What callers see when something is denied

| Response                       | Triggered by                                                                       | Example message                                                                                       |
| ------------------------------ | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **401** `authentication_error` | Missing, invalid, or revoked API key                                               | `invalid API key`                                                                                     |
| **403** `permission_error`     | Model access policy denial                                                         | `Model 'foo' is not allowed by access policy: Production-only`                                        |
| **404** `not_found_error`      | The `model` field doesn't resolve to a configured route                            | `model 'foo' not found or not available`                                                              |
| **429** `rate_limit_error`     | Rate-limit policy or concurrency limit                                             | `Rate limit exceeded (policy: Sales-team rate cap). Try again in 60 seconds.`                         |
| **429** `budget_exhausted`     | Token or spending budget exhausted (and the budget's action is **Block Requests**) | `Token monthly budget exhausted (budget: Engineering monthly) (100% used: 1001234 / 1000000 tokens).` |

Denials use a consistent JSON error shape (`{ "error": { "message", "type", "code" } }`) so callers using common LLM SDKs surface them as normal SDK errors without any special handling.

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="A user keeps getting 429s — which policy is responsible?" icon="gauge">
    Look at the `error.message` in the response — Barndoor names the policy that denied the request (`"… (policy: Sales-team rate cap)"` or `"… (budget: Engineering monthly)"`). Open that policy in the relevant tab to inspect the configuration and the live usage bars.
  </Accordion>

  <Accordion title="A budget says it's at 100% but I expected resets" icon="rotate">
    Confirm the budget's **period** — Daily resets at 00:00 UTC, Weekly resets on Monday at 00:00 UTC, Monthly resets on the 1st at 00:00 UTC. If the period is correct and the counter hasn't reset, contact [support@barndoor.ai](mailto:support@barndoor.ai) with the budget name and your org name.
  </Accordion>

  <Accordion title="My model-access policy isn't firing" icon="shield">
    Check the policy's **scope** — a policy scoped to a specific user only applies to that user. A policy with no entity selected applies to *every* entity of that scope type. Also confirm the policy is **enabled** (toggle on the row).

    If you're using wildcards, remember the `*` must be **trailing** (`claude-*` is valid; `*-mini` is not).
  </Accordion>

  <Accordion title="Spending budget is always $0 used" icon="dollar-sign">
    The models being called are covered by neither one of your pricing rules nor a Barndoor default — so there's no price to debit. This is uncommon, usually a brand-new or custom model name. Open [Managing Model Pricing](/how-tos/manage-model-pricing) and add a rule covering those models. Spending budgets only accumulate when a price (yours or a Barndoor default) matches the request.
  </Accordion>

  <Accordion title="I want to know who's at risk of hitting a budget" icon="bell">
    Set **alert thresholds** on the budget (defaults: `80`, `90`). Barndoor emits alerts when usage crosses each threshold so you can intervene before the budget actually blocks anyone. To watch consumption trends across budgets, use the **Reporting → LLM Usage Dashboard**.
  </Accordion>
</AccordionGroup>

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="When should I use a budget vs a rate limit?">
    Use a **budget** when you care about total consumption over a long horizon (a team's monthly spend, a project's daily token allowance). Use a **rate limit** when you care about short-term load (smoothing bursts, protecting upstream providers from a runaway loop). They compose naturally: a budget for *how much*, a rate limit for *how fast*.
  </Accordion>

  <Accordion title="Can the same scope have more than one budget or rate limit?">
    Yes. You can have, for example, one org-wide monthly budget and a separate per-team daily budget. Both are pre-checked on every request and both are debited on success; the most restrictive applicable limit is what denies.
  </Accordion>

  <Accordion title="Do policies apply to embeddings and completions too?">
    Yes. Every request that flows through `/v1/chat/completions`, `/v1/completions`, `/v1/embeddings`, `/v1/responses`, and `/v1/messages` goes through the same enforcement chain. Embedding requests consume tokens against budgets just like chat requests do.
  </Accordion>

  <Accordion title="Are warn-only budgets useful?">
    Yes — they let you observe a team's actual consumption against a target threshold without disrupting anyone. A common pattern is to start a budget in **Warn Only** for one or two periods to calibrate the limit, then switch it to **Block Requests** once you're confident.
  </Accordion>

  <Accordion title="How can a developer test their own policy without disrupting other users?">
    Create the policy with a tight scope — for example a single test User or a dedicated API Key — and call the gateway with that user / key while you verify the denial behavior. Then widen the scope to *Group* or *Org* once you're happy.
  </Accordion>
</AccordionGroup>

## Need Help?

Reach out to [support@barndoor.ai](mailto:support@barndoor.ai) with:

* The name of the policy you're configuring or troubleshooting.
* The exact `error.message` returned to the caller (if any).
* The scope (org / group / role / user / API key) involved.
