> ## 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.

# Metadata Validation Guardrail

> Enforce the presence and values of request metadata keys using TrueFoundry's built-in Metadata Validation guardrail.

This guide explains how to use TrueFoundry's built-in **Metadata Validation** guardrail to enforce that requests carry the metadata your organization requires, with the values you expect.

## What is Metadata Validation?

Metadata Validation is a built-in TrueFoundry guardrail that checks the custom metadata sent with a request against a set of rules you define. It runs directly within the AI Gateway without requiring external API calls, providing fast and cost-effective validation.

Metadata is passed to the AI Gateway using the `X-TFY-METADATA` header (see [Log Custom Metadata](/docs/ai-gateway/log-custom-metadata)). This guardrail lets you require that certain keys are always present, constrain their values to a regex pattern or a fixed list, and optionally reject any keys you have not explicitly allowed.

<Note>
  Metadata Validation only runs on the **LLM Input** hook (`beforeRequestHook`). When attached to any other hook (LLM Output, MCP Pre Tool, MCP Post Tool), it passes through without performing any checks.
</Note>

### Key Features

1. **Required Keys**: Ensure specific metadata keys are always present in the request.
2. **Value Constraints**: Validate a key's value against a regex pattern or restrict it to a fixed set of allowed values.
3. **Unknown Key Control**: Optionally reject any metadata key that is not explicitly declared in your rules.

## Adding Metadata Validation Guardrail

To add Metadata Validation to your TrueFoundry setup, follow these steps:

<Steps>
  <Step title="Navigate to Guardrails">
    Go to the AI Gateway dashboard and navigate to the **Guardrails** section.
  </Step>

  <Step title="Create or Select a Guardrails Group">
    Create a new guardrails group or select an existing one where you want to add the Metadata Validation guardrail.
  </Step>

  <Step title="Add Metadata Validation Integration">
    Click on **Add Guardrail** and select **Metadata Validation** from the TrueFoundry Guardrails section.
  </Step>

  <Step title="Configure the Guardrail">
    Fill in the configuration form with your desired settings (see Configuration Options below). Toggle **Allow unknown keys** and add a rule for each metadata key you want to enforce.

    <Frame caption="Configure Metadata Validation with per-key rules">
      <img src="https://mintcdn.com/truefoundry/KWb4VCNylKKniYjC/images/configure-metadata-validation.png?fit=max&auto=format&n=KWb4VCNylKKniYjC&q=85&s=b0c4a83ffe6bfc52b95fa7ab8a664338" alt="Metadata Validation configuration form showing per-key rules with the Is key required toggle, Value should be among the ones defined here toggle, and Regex or Allowed Values constraints" width="1185" height="683" data-path="images/configure-metadata-validation.png" />
    </Frame>
  </Step>

  <Step title="Save the Configuration">
    Click **Save** to add the guardrail to your group.
  </Step>
</Steps>

## Configuration Options

| Parameter              | Default                       | Description                                                                                          |
| ---------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Name**               | Required                      | Unique identifier for this guardrail                                                                 |
| **Operation**          | `validate`                    | Metadata Validation can only be used for validation                                                  |
| **Enforcing Strategy** | `enforce_but_ignore_on_error` | `enforce`, `enforce_but_ignore_on_error`, or `audit`                                                 |
| **Allow unknown keys** | `true`                        | When on, metadata keys not listed in the rules pass through. When off, only listed keys are accepted |
| **Keys**               | None                          | The per-key rules applied to specific metadata fields (see Key Rules below)                          |

<Note>
  See [Guardrails Overview](/docs/ai-gateway/guardrails-overview#operation-modes) for details on Operation Modes and Enforcing Strategy.
</Note>

## Key Rules

Each entry under **Keys** maps a metadata key name to a rule. There are two kinds of rules:

### Key must exist

Only the presence of the key is checked — any value passes. Once configured, the key must be present in the request metadata.

### Value must match

Validates the key's value, with an optional requirement that the key be present:

| Field                | Default  | Description                                                                                                                 |
| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Required**         | `true`   | When on, the key must be present. When off, the key is optional, but if present its value must still satisfy the constraint |
| **Value constraint** | Required | How the value is validated — either a regex pattern or a fixed list of allowed values                                       |

The value constraint is one of:

| Constraint         | Description                                                                         |
| ------------------ | ----------------------------------------------------------------------------------- |
| **Regex**          | The value must match the provided regular expression                                |
| **Allowed values** | The value must exactly match one of the listed values. Comparison is case-sensitive |

## How It Works

When a request reaches the LLM Input hook, the guardrail evaluates the request metadata as follows:

1. If **Allow unknown keys** is off, every metadata key that is not declared in your rules raises an `unknown_key` violation. Keys injected by the AI Gateway itself (for example `subject`, `subjectType`, `tfy_agent_name`, and any default gateway metadata) are exempt from this check.
2. For each declared key rule:
   * **Key must exist**: a missing key raises a `missing_required` violation.
   * **Value must match (required)**: a missing key raises a `missing_required` violation.
   * **Value must match (regex)**: if the key is present, its value must match the pattern, otherwise a `pattern_mismatch` violation is raised. An invalid regex pattern raises an `invalid_regex_pattern` violation.
   * **Value must match (allowed values)**: if the key is present, its value must be in the allowed set, otherwise a `value_not_allowed` violation is raised.
3. If any violations are found, the verdict fails. The response handling then depends on the configured **Enforcing Strategy**.

### Violation Reasons

| Reason                  | Meaning                                                                                                |
| ----------------------- | ------------------------------------------------------------------------------------------------------ |
| `missing_required`      | A required key was not present in the request metadata                                                 |
| `pattern_mismatch`      | The key's value did not match the configured regex pattern                                             |
| `value_not_allowed`     | The key's value was not in the configured allowed values list                                          |
| `unknown_key`           | A metadata key was present that is not declared in the rules (only when **Allow unknown keys** is off) |
| `invalid_regex_pattern` | The configured regex pattern itself was invalid                                                        |

## Examples

Consider a guardrail with **Allow unknown keys** turned off and the following key rules:

* `environment` — Value must match, allowed values: `prod`, `staging`, `dev`
* `customer_id` — Value must match, regex: `^cust_[0-9]+$`
* `team` — Key must exist

<AccordionGroup>
  <Accordion title="Allowed: all rules satisfied">
    **Metadata**:

    ```json theme={"dark"}
    {"environment": "prod", "customer_id": "cust_12345", "team": "billing"}
    ```

    **Result**: Allowed — all keys are present and valid, and no unknown keys are sent.
  </Accordion>

  <Accordion title="Blocked: missing required key">
    **Metadata**:

    ```json theme={"dark"}
    {"environment": "prod", "customer_id": "cust_12345"}
    ```

    **Result**: Blocked — `team:missing_required` (the `team` key is missing).
  </Accordion>

  <Accordion title="Blocked: value not allowed">
    **Metadata**:

    ```json theme={"dark"}
    {"environment": "production", "customer_id": "cust_12345", "team": "billing"}
    ```

    **Result**: Blocked — `environment:value_not_allowed` (`production` is not in `[prod, staging, dev]`).
  </Accordion>

  <Accordion title="Blocked: pattern mismatch">
    **Metadata**:

    ```json theme={"dark"}
    {"environment": "dev", "customer_id": "12345", "team": "billing"}
    ```

    **Result**: Blocked — `customer_id:pattern_mismatch` (`12345` does not match `^cust_[0-9]+$`).
  </Accordion>

  <Accordion title="Blocked: unknown key">
    **Metadata**:

    ```json theme={"dark"}
    {"environment": "dev", "customer_id": "cust_1", "team": "billing", "debug": "true"}
    ```

    **Result**: Blocked — `debug:unknown_key` (the `debug` key is not declared and unknown keys are not allowed).
  </Accordion>
</AccordionGroup>

## Sending Metadata

Metadata is sent using the `X-TFY-METADATA` header as a JSON object of string key-value pairs:

```bash theme={"dark"}
curl -X POST "{GATEWAY_BASE_URL}/chat/completions" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'X-TFY-METADATA: {"environment":"prod","customer_id":"cust_12345","team":"billing"}' \
  -H 'X-TFY-GUARDRAILS: {"llm_input_guardrails":["my-guardrail-group/metadata-validation"],"llm_output_guardrails":[]}' \
  -d '{
    "model": "openai-main/gpt-4o-mini",
    "messages": [
      {"role": "user", "content": "Hello"}
    ]
  }'
```

See [Log Custom Metadata](/docs/ai-gateway/log-custom-metadata) for how to attach metadata using the OpenAI, LangChain, and Node.js SDKs.

## Use Cases

| Scenario                            | Configuration                                                                                                |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| **Mandatory cost-attribution tags** | Require keys like `team` or `customer_id` so every request can be attributed in analytics                    |
| **Restrict environment values**     | Constrain `environment` to an allowed list (`prod`, `staging`, `dev`) to prevent typos and unexpected values |
| **Enforce ID formats**              | Use a regex to ensure identifiers such as `customer_id` follow your organization's format                    |
| **Lock down metadata**              | Turn off **Allow unknown keys** to reject any metadata field that is not explicitly approved                 |

## Example use cases

<AccordionGroup>
  <Accordion title="Restrict the prod gateway to production applications">
    When you run more than one gateway against the same control plane — for example a **dev** gateway and a **prod** gateway — you may want to lock down the prod gateway so only production applications can call it, and a dev application that points at it is rejected.

    You can enforce this with Metadata Validation, without changing any application code, by combining two pieces of metadata the AI Gateway already provides:

    * **The caller's environment** comes from [virtual account tags](/docs/ai-gateway/request-headers#automatic-metadata-injection). Tagging a virtual account with `env: prod` means every request made with that account's token carries `env: prod`.
    * **The AI Gateway's environment** comes from [default gateway metadata](/docs/platform/deploy-control-plane-faq#how-do-you-add-default-metadata-to-all-requests-passing-via-the-gateway). Each gateway stamps every request that passes through it with its own value, for example `tfy_gateway_region: PROD`.

    A [guardrail rule](/docs/ai-gateway/guardrails-configuration) then matches on the AI Gateway's stamp (`tfy_gateway_region`) and applies a Metadata Validation guardrail that requires the caller's `env` to be the matching value. Because the rule only fires on the AI Gateway whose stamp it matches, the prod-environment guardrail runs only on the prod gateway:

    | Caller (virtual account tag) | Gateway (stamped value)                   | Result  |
    | ---------------------------- | ----------------------------------------- | ------- |
    | Prod app — `env: prod`       | Prod gateway — `tfy_gateway_region: PROD` | Allowed |
    | Dev app — `env: dev`         | Prod gateway — `tfy_gateway_region: PROD` | Blocked |

    <Steps>
      <Step title="Tag each virtual account with its environment">
        Add a tag to every [virtual account](/docs/platform/virtual-account-management): set `env: prod` on the accounts used by production applications and `env: dev` on the ones used by dev applications. These tags are injected as metadata on every request made with that account's token.
      </Step>

      <Step title="Stamp each gateway with its environment">
        Set [`DEFAULT_GATEWAY_METADATA`](/docs/platform/deploy-control-plane-faq#how-do-you-add-default-metadata-to-all-requests-passing-via-the-gateway) on each gateway plane so it tags every request that passes through it. On the **prod** gateway plane's values file:

        ```yaml theme={"dark"}
        tfy-llm-gateway:
          env:
            DEFAULT_GATEWAY_METADATA: '{"tfy_gateway_region":"PROD"}'
        ```

        On the **dev** gateway plane's values file:

        ```yaml theme={"dark"}
        tfy-llm-gateway:
          env:
            DEFAULT_GATEWAY_METADATA: '{"tfy_gateway_region":"DEV"}'
        ```
      </Step>

      <Step title="Create a Metadata Validation guardrail for the production environment">
        Add a Metadata Validation guardrail (for example `gateway-checker`) with a single key rule:

        * **Key Name**: `env`
        * **Is key required?**: on
        * **Value should be among the ones defined here**: on, with **Allowed Values**: `prod`

        Set the **Enforcing Strategy** to **Enforce** so non-matching requests are blocked. This guardrail blocks any request whose `env` is not `prod`.

        <Frame caption="Metadata Validation guardrail requiring env=prod">
          <img src="https://mintcdn.com/truefoundry/ytiDPffQzxk60vAW/images/ai-gateway/gateway-pinning-guardrail.png?fit=max&auto=format&n=ytiDPffQzxk60vAW&q=85&s=5cf107dc8cc72b3c8f911120c98abca2" alt="Update Guardrails form for a Metadata Validation guardrail named gateway-checker, with Operation set to Validate, Enforcing Strategy set to Enforce, and a key rule requiring the env key with allowed value prod" width="1024" height="924" data-path="images/ai-gateway/gateway-pinning-guardrail.png" />
        </Frame>
      </Step>

      <Step title="Apply it with a rule that matches the production gateway">
        Create a [guardrail rule](/docs/ai-gateway/guardrails-configuration) under **AI Gateway → Policies → Guardrails** that runs `gateway-checker` on the **LLM Input** hook only when the request carries the prod gateway's stamp. In the rule builder, add a **WITH METADATA** condition of `tfy_gateway_region` = `PROD`, set **APPLY ON HOOKS → LLM Input** to the `gateway-checker` guardrail, and optionally set a **Custom Error Message** such as `This gateway can only be accessed from prod env`.

        <Frame caption="Guardrail rule applying gateway-checker only when tfy_gateway_region is PROD">
          <img src="https://mintcdn.com/truefoundry/ytiDPffQzxk60vAW/images/ai-gateway/gateway-pinning-rule.png?fit=max&auto=format&n=ytiDPffQzxk60vAW&q=85&s=f4ca4ec9ae53e522659513988604a331" alt="Edit Guardrail Rule form named prod-gateway-check with a WITH METADATA condition of tfy_gateway_region equal to PROD, the AI Gateway-checker guardrail applied on the LLM Input hook, and a custom error message reading This gateway can only be accessed from prod env" width="1024" height="834" data-path="images/ai-gateway/gateway-pinning-rule.png" />
        </Frame>

        The equivalent [GitOps](/docs/setup-gitops-using-truefoundry) configuration is:

        ```yaml theme={"dark"}
        name: gateway-env-pinning
        type: gateway-guardrails-config
        rules:
          - id: prod-gateway-check
            when:
              target:
                operator: or
                conditions:
                  metadata:
                    tfy_gateway_region: PROD
            llm_input_guardrails:
              - <guardrail-group>/gateway-checker
            llm_output_guardrails: []
            mcp_tool_pre_invoke_guardrails: []
            mcp_tool_post_invoke_guardrails: []
            custom_error_message: "This gateway can only be accessed from prod env"
        ```

        The rule fires only on requests stamped `tfy_gateway_region: PROD` — that is, requests reaching the prod gateway — so a dev application (`env: dev`) calling the prod gateway is rejected, while it is unaffected on the dev gateway.
      </Step>

      <Step title="Verify the behavior">
        Send a request with `env: prod` in its metadata through the prod gateway — it passes. Send one with any other `env` value (or none) and it is blocked with your custom error message.

        <Frame caption="A request without env=prod is blocked on the prod gateway">
          <img src="https://mintcdn.com/truefoundry/ytiDPffQzxk60vAW/images/ai-gateway/gateway-pinning-blocked.png?fit=max&auto=format&n=ytiDPffQzxk60vAW&q=85&s=ea5fd87eac247526cc51252e3b623ac2" alt="AI Gateway playground showing a blocked response with the message This gateway can only be accessed from prod env" width="1024" height="555" data-path="images/ai-gateway/gateway-pinning-blocked.png" />
        </Frame>
      </Step>
    </Steps>

    <Note>
      Only the **prod** gateway is pinned. Production traffic comes from applications using [virtual accounts](/docs/platform/virtual-account-management), whose `env: prod` tag is injected automatically — so requiring it on the prod gateway is safe. The dev gateway is intentionally left open: developers typically access it interactively with [Personal Access Tokens (PATs)](/docs/generating-truefoundry-api-keys#personal-access-tokens-pats), which do not carry the virtual account's `env` tag. Enforcing the same guardrail on the dev gateway would block those legitimate users.
    </Note>

    <Warning>
      This assumes every production virtual account is tagged with `env: prod`. With **Is key required?** on, a request that reaches the prod gateway without that tag is blocked. Tag all production virtual accounts first, and consider starting the guardrail in **Audit** mode to confirm legitimate traffic passes before switching to **Enforce**.
    </Warning>
  </Accordion>

  <Accordion title="Mandate cost-attribution metadata on every request">
    To guarantee every request can be attributed in analytics and cost reports, require the metadata keys your finance and platform teams group by — for example `team` and `cost_center` — so no request reaches a model without them.

    <Steps>
      <Step title="Create a Metadata Validation guardrail with the required keys">
        Add a Metadata Validation guardrail (for example `cost-attribution`) with one rule per key:

        * **Key Name**: `team` — **Is key required?**: on (Key must exist)
        * **Key Name**: `cost_center` — **Is key required?**: on, **Value should be among the ones defined here**: on, **Regex**: `^cc-[0-9]{4}$`

        Set the **Enforcing Strategy** to **Enforce**. Requests missing `team`, or with a `cost_center` that does not match the format, are rejected before reaching the model.
      </Step>

      <Step title="Apply it to all requests on the LLM Input hook">
        Add a [guardrail rule](/docs/ai-gateway/guardrails-configuration) with an empty `when` block so it applies to every request:

        ```yaml theme={"dark"}
        name: cost-attribution-enforcement
        type: gateway-guardrails-config
        rules:
          - id: require-cost-attribution
            when: {}
            llm_input_guardrails:
              - <guardrail-group>/cost-attribution
            llm_output_guardrails: []
            mcp_tool_pre_invoke_guardrails: []
            mcp_tool_post_invoke_guardrails: []
        ```
      </Step>
    </Steps>

    <Tip>
      Combine this with [automatic metadata injection](/docs/ai-gateway/request-headers#automatic-metadata-injection) — tag virtual accounts and teams so `team` and `cost_center` are added without any client-side changes — and the guardrail simply confirms they are always present.
    </Tip>
  </Accordion>
</AccordionGroup>

## Recommended Hooks

| Hook          | Use Case                                                  |
| ------------- | --------------------------------------------------------- |
| **LLM Input** | Validate request metadata before the request is processed |

<Note>
  Metadata Validation runs only on the **LLM Input** hook. Attaching it to other hooks has no effect.
</Note>

## Best Practices

<Tip>
  Start with the **Audit** enforcing strategy to observe metadata violations in Request Traces before switching to **Enforce**. This helps you confirm that callers are already sending the metadata your rules expect.
</Tip>

<Warning>
  All metadata values are compared as strings, and allowed-value matching is case-sensitive. Make sure your allowed values and regex patterns account for the exact casing and format your applications send.
</Warning>
