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

# Gateway Guardrails

> Opt-in input/output policies that the Theo gateway enforces on every completion bound to an API key.

Gateway Guardrails let you attach policy-driven safety, privacy, and reliability checks to any API key. Bound policies fire on every completion — including `/v1/completions` (streaming + non-streaming), `/v1/images`, `/v1/code`, `/v1/documents`, the playground, and embed widgets.

## Strict opt-in contract

**Nothing is live until you bind a policy to an API key.** New keys, all existing keys, and every Clerk-session caller bypass guardrails entirely. There is no automatic org or user default — guardrails are an enforcement boundary, not a routing preference, so the gateway never enforces something you didn't explicitly opt into.

This means:

* **Existing API keys are unaffected** until you explicitly bind a policy. Roll-out is risk-free.
* **No fallback chain.** Clearing a binding (`policy_id: null`) drops the key back to no enforcement.
* **Per-key, 1:1.** A key can be bound to at most one policy; the policy can be bound to any number of keys.

## The five built-in protections

Each protection is a pure evaluator: identical inputs always produce identical outputs. They are vendor-neutral and never name an upstream provider in customer-facing strings.

<AccordionGroup>
  <Accordion title="pii_redactor — Hide personal info">
    Scrubs emails, phone numbers, US Social Security numbers, Luhn-valid credit cards, and driver-license-shaped IDs from the prompt **before** any model call.

    Allowed verdicts: `redact` (default), `flag` (logged only, prompt untouched).

    Phase: `input` only.

    ```json theme={null}
    { "guardrail_id": "pii_redactor", "phase": "input", "verdict": "redact" }
    ```
  </Accordion>

  <Accordion title="prompt_injection — Block jailbreak attempts">
    Catches the canonical OWASP-LLM01 patterns: "ignore previous instructions", role takeovers, fake system messages, prompt exfiltration probes.

    Allowed verdicts: `deny` (default — surfaces a vendor-neutral 422 to the caller), `flag`.

    Phase: `input` only.

    ```json theme={null}
    { "guardrail_id": "prompt_injection", "phase": "input", "verdict": "deny" }
    ```
  </Accordion>

  <Accordion title="json_repair — Always return valid JSON">
    Detects malformed JSON in model output. On a `repair` verdict the runner makes a single repair attempt and falls back to the original buffer if it can't produce valid JSON.

    Allowed verdicts: `repair` (default), `flag`.

    Phase: `output` only.

    Config: `{ "fenced": true }` (default `true`) — when set, the evaluator first strips a fenced `json` block before parsing, mirroring the most common LLM output shape.

    ```json theme={null}
    { "guardrail_id": "json_repair", "phase": "output", "verdict": "repair", "config": { "fenced": true } }
    ```
  </Accordion>

  <Accordion title="max_length — Cap input or output length">
    Truncates the buffer to a configured cap and appends a small `…[truncated]` marker.

    Allowed verdicts: `truncate` (default), `flag`.

    Phase: `input` and/or `output`.

    Config (one of):

    * `{ "maxChars": 16000 }` — hard character cap.
    * `{ "maxTokens": 4000, "charsPerToken": 4 }` — token cap (default 4 chars/token).

    ```json theme={null}
    { "guardrail_id": "max_length", "phase": "output", "verdict": "truncate", "config": { "maxTokens": 4000 } }
    ```
  </Accordion>

  <Accordion title="profanity — Keep it clean">
    Detects common profanity in either direction. Defaults to `flag` (visibility only) so you can review activity before deciding to block.

    Allowed verdicts: `flag` (default), `deny`.

    Phase: `input` and/or `output`.

    ```json theme={null}
    { "guardrail_id": "profanity", "phase": "output", "verdict": "flag" }
    ```
  </Accordion>
</AccordionGroup>

## The five verdicts

A rule's verdict tells the runner what to do when the protection fires. The runner aggregates the worst verdict across all matched rules for the phase and applies it once:

* **flag** — Pass-through. The match is recorded in the execution log; nothing else changes.
* **redact** — Replace the matched text with `[REDACTED:<kind>]` before forwarding.
* **truncate** — Trim to the configured limit and append `…[truncated]`.
* **repair** — Run a single repair pass on the buffer. Used by `json_repair` only.
* **deny** — Block the request. On `input` the gateway returns a `422 invalid_request_error` with `code: "guardrail_violation"` before any model call. On `output` the response content is replaced with the deny reason and artifacts are dropped.

Customer-facing messages never name an upstream provider — the SDK surfaces a stable `guardrail_violation` code so your application can branch on policy enforcement without knowing which detector fired.

## Quick start — dashboard

1. Open **Dashboard → Guardrails** and pick a preset (or click **New policy** for a blank policy).
2. Add protections from the picker; each card lets you choose the phase and verdict.
3. Use the **Test bench** in the right rail to replay a fixture prompt + optional model output through the policy. You'll see the verdict trail per rule.
4. In the **Bindings** panel below the editor, toggle the policy on each API key you want to enforce against.

That's it. The policy is live for every bound key.

## Quick start — SDK

```typescript theme={null}
import { Theo } from "@hitheo/sdk";

const theo = new Theo({ apiKey: process.env.THEO_API_KEY! });

// 1. (Optional) Start from a preset
const presets = await theo.guardrails.presets.list();
const enterprise = presets.find((p) => p.id === "enterprise-default")!;

// 2. Create a policy
const policy = await theo.guardrails.policies.create({
  name: "Production defaults",
  description: "PII redaction + jailbreak deny + JSON repair on output.",
  rules: enterprise.rules,
});

// 3. Bind it to an API key
await theo.keys.setGuardrailPolicy("<key-uuid>", policy.id);

// 4. Verify the binding
const bindings = await theo.guardrails.policies.bindings(policy.id);
console.log(`Policy is enforcing on ${bindings.count} key(s).`);

// 5. Tail the audit log
const recent = await theo.guardrails.executions.list({ policyId: policy.id, limit: 20 });
```

<Note>
  The management endpoints (`/api/v1/guardrail-policies/*`, `/api/v1/keys/{id}/guardrails`) require the `billing` API key scope — matching the routing-preferences convention. Mint a key with this scope explicitly if you plan to manage policies programmatically; the default `completions` scope is sufficient for normal traffic.
</Note>

## Error envelope

When an input-phase `deny` fires, the API returns:

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "guardrail_violation",
    "message": "Request blocked by your active guardrail policy.",
    "request_id": "req_..."
  }
}
```

HTTP status is `422`. For streaming completions the same envelope is emitted as an `error` SSE event:

```
event: error
data: { "error": { "type": "invalid_request_error", "code": "guardrail_violation", ... } }
```

The deny reason is the customer-facing copy the protection surfaced; the matched-pattern id (e.g. `ignore_previous`) is intentionally **not** included so attackers can't probe which detector fired.

## Streaming behavior

Output guardrails run after the full token stream has been collected on the server. This means:

* **Tokens stream live as the model produces them.** The client sees the raw, pre-guard text in real time.
* **The `done` event carries the guarded buffer.** Any client that re-renders the message body from the `done` event (the official SDK, the playground, the embed widget) ends up displaying the policy-compliant version.
* **On `deny`, an additional `error` SSE event is emitted** before `done`. The `done.content` is replaced with the deny reason and any artifacts produced during the turn are dropped.

If your client renders tokens live and doesn't re-read `done.content`, plan on switching to non-streaming mode (`stream: false`) when you need byte-exact policy enforcement on every visible character.

## Team policies

`scope: "team"` policies are visible to every member of the owning organization and require the `manageWebhooks` permission to create/update (re-used as the generic team-config gate; there is no dedicated `manageGuardrails` bit today).

Personal policies are only visible to their author. Team keys can only be bound to team policies in the same org — the binding endpoint enforces this with `guardrail_policy_not_bindable` when the scopes don't match.

## Audit & telemetry

Every evaluation writes an immutable row to `guardrail_executions` (90-day retention, enforced by the BullMQ retention worker). Each row captures:

* `policy_id`, `key_id`, `user_id` — who/what triggered the evaluation.
* `guardrail_id`, `phase`, `verdict` — the rule and outcome.
* `matched_pattern`, `redacted_count` — bounded forensic context (no raw prompts).
* `latency_ms` — per-rule overhead, surfaced in the dashboard's activity feed.

Tail the log via `theo.guardrails.executions.list({ keyId, policyId, since })` or `GET /api/v1/guardrail-executions`. The dashboard's **Recent activity** panel renders the same rows in plain language.

## What guardrails do not change

* **Model allowlists, routing rules, skills, brand soul, response style, and personality** all keep working independently. Guardrails layer around the request/response path; they don't override other key-level features.
* **Existing keys without a binding stay unchanged.** You opt in per key.
* **Cost is unaffected.** Builtins are pure evaluators (sub-millisecond); `json_repair`'s repair pass adds a single small call only when malformed JSON is detected.

## API reference

* [List policies](/api-reference/guardrails/list-policies)
* [Create policy](/api-reference/guardrails/create-policy)
* [Get / update / delete policy](/api-reference/guardrails/get-policy)
* [Test policy](/api-reference/guardrails/test-policy)
* [List bindings for a policy](/api-reference/guardrails/policy-bindings)
* [List presets](/api-reference/guardrails/presets)
* [Get / set key binding](/api-reference/guardrails/key-binding)
* [List executions](/api-reference/guardrails/executions)
