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

# Guardrails using Anthropic Inference Hooks

> Run TrueFoundry guardrails on every governed Claude prompt across claude.ai, Cowork, and Claude Code using Anthropic Inference Hooks.

[Anthropic Inference Hooks](https://platform.claude.com/docs/en/manage-claude/inference-hooks) let a Claude Enterprise organization route every governed prompt through an HTTPS service — an *AI security server* — **before inference runs**. The AI Gateway implements that protocol at `POST /hooks/anthropic-inference`, so your existing TrueFoundry guardrails decide whether each prompt reaches the model.

Because the hook runs on Anthropic's servers, one configuration governs **claude.ai, Cowork, and Claude Code** at once, with nothing to install on user devices.

<Note>
  This is complementary to the [Claude Code hooks integration](/docs/ai-gateway/claude-code-hooks). That runs client-side, is opt-in per developer, and can **rewrite** tool inputs and outputs. This page is org-wide, server-side, and **allow/deny only**. You can use either or both.
</Note>

<Warning>
  Inference Hooks are a **Claude Enterprise beta**. They are not available on Amazon Bedrock or Google Vertex AI, and Anthropic states that field names, request shapes, and headers may change before GA.
</Warning>

## How it works

1. A user submits a prompt in claude.ai, Cowork, or Claude Code.
2. Anthropic `POST`s the conversation transcript to your configured endpoint — the AI Gateway's `POST /hooks/anthropic-inference`.
3. The AI Gateway authenticates the request with the `x-tfy-api-key` custom header, flattens the transcript into a scan target, and runs the guardrails nominated in `x-tfy-hook-guardrails`.
4. The AI Gateway returns `{"action": "allow"}` or `{"action": "deny", ...}`. A denied prompt never reaches the model, and the user sees your `deny_reason`.

## What you can and can't do

Inference Hooks support **validation only**. Anthropic's protocol has no mechanism to substitute a modified prompt, so **mutating guardrails cannot be used here**.

| Capability                                        |                 Supported                 |
| ------------------------------------------------- | :---------------------------------------: |
| Block a prompt before inference                   |                     ✅                     |
| Block on tool results flowing back into the model |                     ✅                     |
| Rewrite / redact content                          |      ❌ not supported by the protocol      |
| Scan the model's response                         |    ❌ no response-side hook event today    |
| Intercept a tool call *before* it runs            | ❌ tools run client-side, outside the hook |

Which pipeline your guardrails run through is derived from what the transcript **ends with**:

| Last message contains                            | Guardrails run as    |
| ------------------------------------------------ | -------------------- |
| `tool_result` block(s)                           | MCP tool post-invoke |
| Anything else (`text`, `attachment`, `tool_use`) | LLM input            |

<Warning>
  If you nominate a **mutator** guardrail, the AI Gateway returns a **deny** with a configuration message rather than allowing the prompt through unredacted. Nominate validation guardrails only.
</Warning>

## Choosing which guardrails run

As with the other hook endpoints, guardrails are nominated via the `x-tfy-hook-guardrails` header — a flat JSON array of `group/guardrail-name` selectors. The endpoint does not read tenant Guardrail Config rules.

```json theme={"dark"}
["security/email-regex"]
```

<Warning>
  Anthropic's custom request headers are **static per organization**. Unlike Claude Code hooks — where each developer's `settings.json` can nominate different guardrails per project — every governed prompt in the org runs the *same* selector list. Any per-user or per-surface variation has to come from the guardrail's own logic, not from the header.
</Warning>

Two behaviours to know:

* **Header absent** — no guardrails are nominated and the prompt is **allowed**. Nothing was checked.
* **Header present but malformed** (not a JSON array of strings) — fail closed: the prompt is **denied**.

### Limiting how much of the transcript is scanned

Anthropic sends the **full, untruncated transcript** on every turn — up to 10 MB. By default the AI Gateway scans all of it. Use the optional `x-tfy-guardrails-scope` custom header to narrow the window:

| Value                        | Scans                        |
| ---------------------------- | ---------------------------- |
| `all` (default)              | The entire transcript        |
| `last`                       | Only the most recent message |
| A positive integer, e.g. `5` | The last N messages          |

Narrowing the scope reduces guardrail latency and cost on long conversations. Routing is unaffected — whether the turn is treated as an LLM-input or tool-result scan is always decided from the last message of the full transcript, not the scan window.

## Prerequisites

1. A **Claude Enterprise** organization, and a user with the `organization:manage` permission.
2. A guardrail **group** and at least one **validation** guardrail integration — see [Create a guardrail](/docs/ai-gateway/guardrails-getting-started). Note the selector, e.g. `security/email-regex`.
3. A TrueFoundry **virtual account** API key with access to that guardrail group. This key is pasted into Anthropic's admin console, so it should be a dedicated service credential, not a personal token.
4. A gateway host that meets Anthropic's endpoint requirements: `https://` on **port 443**, publicly routable, a certificate that validates against the public CA trust store, and **no redirects** — the configured URL must be the final destination.

## Configure the hook in Anthropic's console

Go to **claude.ai → Organization settings → Data and privacy → Inference hooks**.

1. **Endpoint URL** — `https://<your-gateway-host>/hooks/anthropic-inference`

   <Note>
     This is your **gateway host root**, without the `/api/llm` suffix used for model traffic.
   </Note>

2. **Custom request headers** — add:

   | Header                   | Value                                             |
   | ------------------------ | ------------------------------------------------- |
   | `x-tfy-api-key`          | Your TrueFoundry virtual-account API key          |
   | `x-tfy-hook-guardrails`  | `["security/email-regex"]` — your selectors       |
   | `x-tfy-guardrails-scope` | *(optional)* `all`, `last`, or a positive integer |

3. **Prompt verdict timeout** — 1–10,000 ms, default 5,000 ms. This budget covers connection, TLS handshake, request, and response, so size it against your guardrails' p99, not just their average.

4. **Failure handling** — start in **Shadow mode**, then move to *Block the request* (fail closed) or *Allow the request* (fail open) once you've confirmed the endpoint is healthy.

5. **Rollout %** — begin below 100 to limit blast radius. Anthropic rolls the dice once per conversation turn, so a single conversation can be partly inspected.

6. **Enforce verdicts** — the master switch. Changes take roughly a minute to propagate.

<Tip>
  Anthropic's **Test connection** uses the values currently in the form, not the saved ones. Because stored header values are write-only (only names are shown after a save), re-enter your API key before testing. Changing the endpoint URL **clears all stored header values**.
</Tip>

## Verify

You can exercise the endpoint directly before enabling enforcement:

```bash theme={"dark"}
export TFY_API_KEY="your-truefoundry-api-key"
export TFY_GUARDRAIL_URL="https://<your-gateway-host>"

curl -s -X POST "$TFY_GUARDRAIL_URL/hooks/anthropic-inference" \
  -H "x-tfy-api-key: $TFY_API_KEY" \
  -H 'content-type: application/json' \
  -H 'x-tfy-hook-guardrails: ["security/email-regex"]' \
  -d '{
        "type": "prompt",
        "request_id": "req_test_1",
        "actor": { "type": "user", "email_address": "alice@example.com" },
        "source": { "application": "config-test" },
        "messages": [
          { "role": "user", "content": [ { "type": "text", "text": "reach me at a@b.com" } ] }
        ]
      }'
```

A blocked prompt returns:

```json theme={"dark"}
{
  "action": "deny",
  "deny_reason": "Request blocked by guardrail.",
  "reference_id": "4b1f0c9e2a7d..."
}
```

An allowed prompt returns `{"action": "allow"}`.

<Warning>
  `{"action": "allow"}` on its own does **not** mean a guardrail approved the prompt — it is also what you get when no guardrail was nominated. If you did not send `x-tfy-hook-guardrails`, or its value isn't a JSON array of strings, nothing was checked. Confirm by looking at the trace for the request in [Metrics](/docs/ai-gateway/analytics).
</Warning>

## Response contract

The AI Gateway **always responds HTTP 200**, including for denials, authentication failures, and internal errors. This is required by the protocol: any non-200 response is treated by Anthropic as a *webhook failure*, not a deny, and sustained failures trip the org-wide circuit breaker.

| Field          | Notes                                                                                                                                                                                                                                                                                    |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action`       | `"allow"` or `"deny"`                                                                                                                                                                                                                                                                    |
| `deny_reason`  | Shown to the end user. Truncated to 500 characters                                                                                                                                                                                                                                       |
| `reference_id` | The AI Gateway trace ID, sanitized to Anthropic's 50-character `[A-Za-z0-9._:/-]` charset. Recorded on Anthropic's `inference_hooks_request_denied` compliance activity, never shown to the user — use it to join a denial in Anthropic's Activity Feed back to the trace in TrueFoundry |

Behaviour in each case:

* **Guardrail blocked** — `action: "deny"`.
* **Invalid or missing API key** — `action: "deny"` with `deny_reason: "Unauthorized"` (still HTTP 200).
* **Malformed body, or internal AI Gateway error** — `action: "deny"`. Fail closed.
* **Mutator guardrail nominated** — `action: "deny"` with a message telling you to remove mutators from `x-tfy-hook-guardrails`.
* **A turn made up entirely of content-block types the AI Gateway doesn't recognize** — `action: "deny"`, because nothing could be scanned safely. This is a protocol-churn safeguard.
* **Unknown top-level frame `type`** — `action: "allow"`. Anthropic may add new event types (only `prompt` exists today); allowing them is required so a future event doesn't trip the circuit breaker.

## Observability

Every request produces a trace with Inference-Hook-specific attributes, including Anthropic's `request_id` and `tenant_id`, the `source.application` (`claude-ai`, `claude-code`, or `config-test`), the model, session ID, whether an `actor` was present along with its ID and email, the transcript size in bytes, the message count, and which content-block types were scanned. Run in shadow mode first and read these traces to size real transcripts and latency before you turn on enforcement. Aggregate guardrail pass/block rates are also available in [Metrics](/docs/ai-gateway/analytics).

## Caveats

* **Enforcement adds a round trip to every governed prompt in the org.** Your guardrails must complete inside Anthropic's verdict timeout (5,000 ms by default), including TLS setup. Measure in shadow mode first.
* **Request signatures are not verified today.** Anthropic signs each delivery with a Standard Webhooks HMAC header, but the AI Gateway currently authenticates using the `x-tfy-api-key` custom header alone. Restrict the endpoint to Anthropic's outbound range **`160.79.106.0/24`** at your ingress, and treat fields in the request body — `actor.email_address` in particular — as **untrusted**: they are recorded on traces for attribution, but are not used to make identity or authorization decisions.
* **`actor.email_address` can be null.** Connection tests (`source.application: "config-test"`) carry no human actor, machine-credential traffic is always inspected, and future actor types are only guaranteed to have a `type`. Don't write guardrail logic that assumes an email is present.
* **`source.application` is advisory, not a trust boundary.** It is an open string — new values can appear — and Anthropic explicitly warns against resting a security-critical decision on it alone.
* **The circuit breaker is org-wide and recovers manually.** Sustained failures attributable to your endpoint stop enforcement for the entire organization, and an admin has to re-enable *Enforce verdicts* after the endpoint is fixed. Alert on gateway hook errors rather than relying on Anthropic's monitoring panel, which is best-effort and shows zero rather than erroring.
* **Transcripts can reach 10 MB.** Check that every proxy and ingress in front of the AI Gateway accepts bodies that large — a rejected body is a webhook failure, so under *Allow the request* an oversized prompt reaches the model completely uninspected.
* **No coverage of voice mode, system prompts, tool definitions, or raw file bytes.** Anthropic does not send these. Attachments arrive as extracted text only.
* **Prompts are only inspected once per inference call.** Tool calls execute on the client between calls, so this cannot stop a dangerous tool from running the way Claude Code's `PreToolUse` hook can. Use both integrations if you need that.
