# Guardrails

A guardrail is a set of rules for requests. You assign it to the organization, a workspace, a
member or a key. Every guardrail that covers a request applies. When two guardrails disagree,
the stricter rule wins.

Guardrails apply to `POST /v1/chat/completions`, `/v1/messages`, `/v1/responses`,
`/v1/images/generations`, `/v1/videos` and `/v1/decisions`. You manage them with the
[management API](api-management.md).

In the console, open Governance > Guardrails for the rules. Open Governance > Budgets for the
spending limits.

## Create and assign a guardrail

1. Create the guardrail with a management key:

   ```bash
   curl https://demo-api.routerplus.com/v1/guardrails \
     -H "Authorization: Bearer $TM_MANAGEMENT_KEY" \
     -H "Content-Type: application/json" \
     -d '{"name": "support-bot",
          "blocked_models": ["openai/gpt-oss-20b"],
          "content_rules": [{"kind": "pii", "preset": "email", "action": "redact"}]}'
   ```

   The response is `201`. `data` holds the guardrail and its `guardrail_id`.
2. Assign the guardrail. It has no effect until you do. `target_kind` is the organization, a
   workspace, a member or a key:

   ```bash
   curl https://demo-api.routerplus.com/v1/guardrails/$GUARDRAIL_ID/assignments \
     -H "Authorization: Bearer $TM_MANAGEMENT_KEY" \
     -H "Content-Type: application/json" \
     -d '{"target_kind": "org", "target_id": "'$ORG_ID'"}'
   ```

A change applies within 5 seconds. With the guardrail above:

- A call to `openai/gpt-oss-20b` returns `403` with `error.code` `guardrail_blocked`.
- A call that contains an email address goes to the provider with `[REDACTED:email]` in its
  place. The response carries `x-tm-guardrail-actions: redacted`.

## Rules

Unknown fields are refused.

| Field | Type | Effect |
|---|---|---|
| `allowed_models` | string[] | Only these models. `*` matches any run of characters, `?` one character. Empty means every model. |
| `blocked_models` | string[] | Never these models. |
| `allowed_providers` | string[] | Only these providers. Empty means every provider. |
| `blocked_providers` | string[] | Never these providers. |
| `zdr` | boolean | Only providers that keep no prompts or completions. |
| `data_collection` | `"deny"` | Only providers that do not train on or keep request data. |
| `allowed_regions` | string[] | Only providers that process requests in these regions (`us`, `eu`, `in`, ...). `global` allows all. |
| `blocked_infrastructure` | string[] | Never providers that run on these clouds (`aws`, `gcp`, `azure`, ...). |
| `budgets` | object[] | Spending limits. See [Budgets](#budgets). |
| `limit_usd`, `reset_interval`, `include_byok_in_budgets` | | One budget in a short form: a USD limit, its period, and whether BYOK spend counts. |
| `rpm`, `tpm` | integer | Requests and tokens per minute for each key the guardrail covers. |
| `max_output_tokens` | integer | The most output a request may ask for. RouterPlus lowers a larger limit to this value. |
| `server_tools` | `"allow"` or `"block"` | `block` refuses requests that ask the provider to run tools (web search, web fetch, code execution). |
| `content_rules` | object[] | Rules on request text. See [Content rules](#content-rules). |
| `on_scan_limit` | `"allow"` or `"block"` | What to do with a request too long to scan in full. RouterPlus scans the first 2,000,000 characters. Default `allow`. |
| `overridable` | `false` | Makes the guardrail a hard limit. See [Grants](#grants). |

A filter on a provider fact leaves out each provider for which RouterPlus does not know that
fact. For example, `zdr: true` leaves out a provider that does not publish its retention.

### Regions and infrastructure

- RouterPlus records a provider's regions and infrastructure only from the provider's published
  terms: its privacy policy, data processing terms, subprocessor list or docs.
- `allowed_regions` keeps only providers whose terms state where they process requests.
- RouterPlus does not record a region that a provider offers only as an option RouterPlus does
  not use.
- `blocked_infrastructure` leaves out a provider that names a blocked cloud for serving or
  hosting.
- `GET /v1/models/<model>/endpoints` shows each provider's `regions` and `infrastructure` under
  `data_policy`.

### Budgets

```json
{ "budgets": [
  { "id": "monthly", "limit_usd": 500, "period": "month" },
  { "id": "opus", "limit_usd": 50, "period": "week", "model": "claude-opus-*" },
  { "id": "team-pool", "limit_usd": 2000, "period": "month", "shared": true, "include_byok": true }
] }
```

- `period` is `day`, `week` (from Monday), `month` or `lifetime`. Periods start at 00:00 UTC. A
  budget with no period is `lifetime`.
- `model` and `provider` count only the spend on that model or provider.
- `include_byok` also counts the provider cost of requests on your own provider keys.
- `shared: true` makes one budget for everything the guardrail is assigned to. Without it, each
  assignment has its own budget. For example, a guardrail on a workspace counts the spend of that
  workspace.
- A key also has its own limit: `limit` with `limit_reset` on the key.

RouterPlus refuses a request when its budget is used up. Requests that are already running
finish. Spend can go past the limit by the cost of those requests.

### Content rules

```json
{ "content_rules": [
  { "kind": "pii", "preset": "credit_card", "action": "block" },
  { "kind": "pii", "preset": "email", "action": "redact" },
  { "kind": "regex", "pattern": "PROJ-[0-9]{4}", "action": "redact", "label": "project_code" },
  { "kind": "injection", "action": "block" }
] }
```

- `pii` presets: `email`, `phone`, `ssn`, `credit_card`, `iban`, `ip_address`, `credential`,
  `aws_access_key`, `private_key`, `jwt`, `us_passport`.
- `regex`: up to 512 characters, with flags `i`, `m`, `s` and `u`. A pattern with lookarounds,
  backreferences or nested quantifiers returns `invalid_regex_pattern`.
- `injection` finds prompt-injection and jailbreak patterns in user text and tool results.
- `action`:
  - `redact` puts `[REDACTED:<label>]` in place of the match before RouterPlus sends the request.
  - `block` refuses the request.
  - `flag` records the match and sends the request unchanged.
- When two guardrails have the same pattern with different actions, `block` wins over `redact`,
  and `redact` wins over `flag`.

Content rules read message text, tool-call arguments and tool results. They do not read images
or tool definitions. RouterPlus does not store the matched text. The record holds the rule, the
count and the place in the request where it matched.

## Errors

| Status | `error_type` | When |
|---|---|---|
| 401 | `key_expired` | The key's `expires_at` has passed. |
| 403 | `ip_not_allowed` | The key has an IP allowlist and the request comes from another address. |
| 403 | `guardrail_blocked` | A model, server tool or content rule refused the request. The message names the guardrail and its id, or the content rule ids. |
| 403 | `budget_exceeded` | A budget is used up. `x-tm-cap-reset` gives the time it resets. |
| 429 | `rate_limit` | A guardrail's `rpm` or `tpm` is reached. |
| 404 | `model_unavailable` | No provider of the model meets the guardrails. |

The `401` and `403` refusals carry `x-should-retry: false`. Do not retry them.

Response headers:

- `x-tm-guardrails`: the ids of the guardrails that applied.
- `x-tm-guardrail-actions`: `redacted` or `flagged`, when a content rule acted.
- `x-tm-guardrails: unavailable`: RouterPlus could not read your guardrails, so the request ran
  without them.

## Grants

A grant is a time-boxed exception to one rule. An approved grant applies from `starts_at` until
`expires_at`. After `expires_at`, the base rule applies again.

| Grant type | What it changes |
|---|---|
| `budget` | Raises one budget of a guardrail to a higher `limit_usd`. |
| `key_limit` | Raises a key's own limit to a higher `limit_usd`. |
| `guardrail` | Lets one model (`allow_model`, an id or pattern) pass that guardrail's `allowed_models` and `blocked_models`. Every other guardrail still applies. |

- A grant lasts at most 90 days.
- A budget or key limit grant must raise the limit. When two grants cover the same limit, the higher one applies.
- A guardrail with `overridable: false` is a hard limit. It takes no grant. Its organization-level budgets also cap every budget grant for the same period.
- A management key can request, approve, reject, cancel and list grants. An API key can request a grant for itself, and list, read and cancel the grants it requested.
- RouterPlus sends a notice when a grant is requested, decided or cancelled, and one day before it expires.

In the console, open Access > Grants. For the endpoints, see [Grants](api-management.md#grants)
in the management API.

## Activity

`GET /v1/guardrails/events` lists the requests that a guardrail blocked, redacted or flagged. In
the console, open Governance > Guardrail events.
