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

# PII Redaction

> Redact personally identifiable information with the pii_redaction plugin before requests reach the LLM provider, then restore values in the response.

The `pii_redaction` **plugin** modifies request and response content directly. It runs on every matching request without needing a separate rule condition.

## Use cases

* Keeping names, emails, and account numbers out of third-party provider logs.
* Meeting data-handling requirements without rewriting prompts in every service.
* Sending sensitive support tickets to a model while preserving the reply for the end user.
* Excluding raw PII from trace storage while still tracing the request end to end.

The `pii_redaction` plugin detects personally identifiable information in the request, replaces each value with a placeholder before the provider sees it, and restores the original values in the response. The provider receives only placeholders such as `<EMAIL_ADDRESS_1>` or `<PERSON_2>`.

<Note>This feature is in Beta.</Note>

## Quick start

Add a `pii_redaction` entry to the `plugins` array.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://my.orq.ai/v3/router/responses \
    -H "Authorization: Bearer $ORQ_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "openai/gpt-5.4-mini",
      "input": "Email the invoice to jane.doe@example.com",
      "plugins": [{ "id": "pii_redaction", "language": "en" }]
    }'
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await client.responses.create({
    model: 'openai/gpt-5.4-mini',
    input: 'Email the invoice to jane.doe@example.com',
    // @ts-ignore - orq.ai extension
    plugins: [{ id: 'pii_redaction', language: 'en' }],
  });

  console.log(response.output_text);
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  response = client.responses.create(
      model="openai/gpt-5.4-mini",
      input="Email the invoice to jane.doe@example.com",
      extra_body={
          "plugins": [{"id": "pii_redaction", "language": "en"}]
      },
  )

  print(response.output_text)
  ```

  ```typescript TypeScript (Chat Completions) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await client.chat.completions.create({
    model: 'openai/gpt-5.4-mini',
    messages: [
      { role: 'user', content: 'Email the invoice to jane.doe@example.com' },
    ],
    // @ts-ignore - orq.ai extension
    plugins: [{ id: 'pii_redaction', language: 'en' }],
  });
  ```
</CodeGroup>

## Workspace-level redaction

Enable PII redaction for every request from **Settings** > **Plugins**, without passing a `plugins` array on each call. Once enabled, it applies automatically to every call that doesn't already specify a `pii_redaction` plugin.

<Frame caption="The Plugins settings page with enable toggles for PII Redaction, Trace Scrubbing, and Response Healing. PDF Inputs is marked Coming soon.">
  <img src="https://mintcdn.com/orqai/VdF72Z_gV0H1qi8Z/images/plugins-415.png?fit=max&auto=format&n=VdF72Z_gV0H1qi8Z&q=85&s=8236c143a5fa98c070599b6caedeef01" alt="Plugins settings page listing PII Redaction, Trace Scrubbing, PDF Inputs, and Response Healing cards, with enable toggles on the first, second, and fourth and a Coming soon label on PDF Inputs." width="1396" height="665" data-path="images/plugins-415.png" />
</Frame>

Once the toggle is on, a <Icon icon="sliders" /> icon appears next to it. Click it to open the [Configuration](#configuration) panel.

<Note>A request cannot turn off or reduce the workspace-level redaction settings. It can only add stricter rules of its own, such as lowering the detection threshold to redact more.</Note>

## Apply per routing rule

Attach a `pii_redaction` entry to a [Routing Rule](/ai-gateway/configuration/routing-rules) to run redaction on the traffic that rule matches, with its own per-rule configuration.

## How it works

The plugin runs a redaction round-trip around the generation:

1. **Redact**: detected PII in the request is replaced with typed placeholders before the request leaves the **AI Gateway**.
2. **Generate**: the provider processes the placeholder text and returns a response that keeps the placeholders intact.
3. **Restore**: the original values are substituted back into the response before it returns to the caller.

On `embeddings`, `rerank`, and `images/generations`, the plugin redacts the input only. The `rerank` response restores the echoed document text; `embeddings` and image generation have no echoed text to restore.

<Note>Detection runs on **Orq.ai**'s own model, hosted on **Orq.ai** infrastructure. Text is never sent to a third-party service for PII detection.</Note>

## Configuration

| Parameter                    | Type      | Required | Description                                                                                                                                                                |
| ---------------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                         | string    | Yes      | Plugin discriminator. Must be `pii_redaction`.                                                                                                                             |
| `language`                   | string    | No       | Detector language: `en`, `nl`, or `auto` to detect it per request. Defaults to `en`. Read the live set from `GET /v2/pii/capabilities`.                                    |
| `regions`                    | string\[] | No       | Region coverage. Lowercase ISO 3166-1 alpha-2 codes, or `["all"]`. Redacts every entity type the listed regions gate, alongside the base catalog.                          |
| `entities`                   | string\[] | No       | Explicit coverage. UPPERCASE entity types to redact. Alone it is a strict allowlist; alongside `regions` it adds to the region coverage. Omit both to redact every region. |
| `entity_thresholds`          | object    | No       | Per-entity confidence cutoffs in `[0,1]`, keyed by entity type. Every key must also appear in `entities`.                                                                  |
| `on_failure`                 | string    | No       | Behavior when redaction is unavailable: `block` or `passthrough`. Defaults to `block`.                                                                                     |
| `threshold`                  | number    | No       | Global detector confidence cutoff in `[0,1]`, applied to every type. Defaults to `0.5`.                                                                                    |
| `persist_redacted_to_traces` | boolean   | No       | Store the redacted form in traces rather than the restored original. Defaults to `true`.                                                                                   |

<Note>`GET /v2/pii/capabilities` is the source of truth for the live entity catalog, the supported regions, the region-to-entity mapping (`region_entities`), the supported languages, and the default thresholds. The catalog grows with the service, so treat any list on this page as a snapshot rather than the full set.</Note>

### Detection threshold

The `threshold` parameter sets the global first-pass confidence score at which a detected value is counted as PII, and it applies to every type. The range is `0` to `1`; the default is `0.5`.

| Value | Behavior                                                              |
| ----- | --------------------------------------------------------------------- |
| `1`   | Very high confidence required: fewer detections, more missed values.  |
| `0`   | Almost no confidence required: more detections, more false positives. |

Scores are boosted for language-specific formats (for example, a Dutch BSN scores higher when the detected or selected language is Dutch). Adjust the threshold only when there is an observed problem: lower it if real PII is being missed, raise it if too many non-PII values are being redacted.

When `entity_thresholds` names a type, that value replaces `threshold` for the type and may sit above or below it. A higher value detects less of that type, a lower value detects more, and types without an override stay at `threshold`.

### Coverage selection

Coverage is selected two ways. `regions` covers whole regions: every entity type the listed regions gate is redacted, alongside the base catalog. `entities` is an explicit list of entity types to redact, and on its own it is strict — nothing outside the list is redacted. Set both and the two are unioned: the region coverage plus the named types, so `entities` widens rather than narrows. Omit both and every region is redacted: selecting nothing is the widest request, not the narrowest. That default is gated on `entities` being empty, so it can never loosen an allowlist — `entities` alone stays strict.

#### Regions

Region codes are lowercase ISO 3166-1 alpha-2, for example `nl`, `gb`, `be`, `de`, `fr`, and `us`. The United Kingdom is `gb`, never `uk`. The value `["all"]` is exclusive: it covers every supported region and cannot be combined with other region codes. Read the live set of supported regions and the types each one gates from `GET /v2/pii/capabilities`.

```json Regions theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "pii_redaction",
  "language": "en",
  "regions": ["nl", "gb"],
  "threshold": 0.6,
  "on_failure": "block"
}
```

#### Entity types

Entity type names are UPPERCASE, for example `PERSON`, `EMAIL_ADDRESS`, and `BSN`. A named region-specific type fires even without naming its region, so the types belonging to a region can be picked individually instead of taking the whole region.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://my.orq.ai/v3/router/responses \
    -H "Authorization: Bearer $ORQ_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "openai/gpt-5.4-mini",
      "input": "Contact Jane Doe at jane.doe@example.com",
      "plugins": [{
        "id": "pii_redaction",
        "language": "en",
        "entities": ["EMAIL_ADDRESS"]
      }]
    }'
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await client.responses.create({
    model: 'openai/gpt-5.4-mini',
    input: 'Contact Jane Doe at jane.doe@example.com',
    // @ts-ignore - orq.ai extension
    plugins: [
      { id: 'pii_redaction', language: 'en', entities: ['EMAIL_ADDRESS'] },
    ],
  });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  response = client.responses.create(
      model="openai/gpt-5.4-mini",
      input="Contact Jane Doe at jane.doe@example.com",
      extra_body={
          "plugins": [{"id": "pii_redaction", "language": "en", "entities": ["EMAIL_ADDRESS"]}]
      },
  )
  ```
</CodeGroup>

#### Per-entity thresholds

`entity_thresholds` maps an entity type to a confidence cutoff in `[0,1]`. It only tunes confidence; it never changes which types are redacted. Every key must also appear in `entities`: a key that is absent from `entities`, or any key at all while `entities` is empty, is rejected with a validation error.

Per-entity tuning applies to the types named in `entities`. To tune a type a region gates, name that type in `entities` — alongside `regions` if the rest of the region should stay covered — and set its cutoff. `region_entities` from `GET /v2/pii/capabilities` lists the types each region gates.

```json Entity types with per-entity thresholds theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "pii_redaction",
  "language": "en",
  "entities": ["PERSON", "EMAIL_ADDRESS", "BSN"],
  "entity_thresholds": {
    "PERSON": 0.85,
    "BSN": 0.7
  },
  "threshold": 0.5,
  "on_failure": "block"
}
```

## Failure modes

The `on_failure` policy decides what happens when the detection service cannot run:

| Mode          | Behavior                                                                         |
| ------------- | -------------------------------------------------------------------------------- |
| `block`       | Fails closed. The request is rejected and nothing reaches the provider. Default. |
| `passthrough` | Fails open. The original, un-redacted text is sent to the provider.              |

The detection service waits up to 90 seconds by default before timing out and applying the `on_failure` policy.

### The guardrail is always fail-closed

`on_failure` belongs to the `pii_redaction` plugin only. The `orq_pii_detection` guardrail always fails closed: if the detection service is unavailable or the detect call errors, the text is treated as containing PII and the request is blocked. There is no way to make a guardrail fail open, and setting `on_failure` in a guardrail's `options` is rejected at write time rather than silently ignored: a safety control whose stored configuration disagrees with what it does is worse than one with no setting at all. Use the plugin when you need configurable failure behavior.

One consequence is worth knowing before an incident rather than during one: a guardrail rule with no project and no expression applies workspace-wide, because project-less rules are indexed as globals and an empty expression matches every request. A detection service outage therefore blocks all `/responses`, `/chat/completions` and `/messages` traffic in that workspace. That is the intended fail-closed behavior, not a defect, but it means a workspace-wide PII guardrail couples inference availability to the availability of the detection service.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://my.orq.ai/v3/router/responses \
    -H "Authorization: Bearer $ORQ_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "openai/gpt-5.4-mini",
      "input": "Email the invoice to jane.doe@example.com",
      "plugins": [{ "id": "pii_redaction", "language": "en", "on_failure": "passthrough" }]
    }'
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await client.responses.create({
    model: 'openai/gpt-5.4-mini',
    input: 'Email the invoice to jane.doe@example.com',
    // @ts-ignore - orq.ai extension
    plugins: [{ id: 'pii_redaction', language: 'en', on_failure: 'passthrough' }],
  });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  response = client.responses.create(
      model="openai/gpt-5.4-mini",
      input="Email the invoice to jane.doe@example.com",
      extra_body={
          "plugins": [{"id": "pii_redaction", "language": "en", "on_failure": "passthrough"}]
      },
  )
  ```
</CodeGroup>

## Supported entity types

`GET /v2/pii/capabilities` is the live catalog and the only authoritative list: it grows with the detector, and `languages`, `base_entities`, `all_entities`, `regions` and `region_entities` all come from it. The lists below are a snapshot for orientation.

Base entity types need no region: they are detected whenever `regions` is set. Setting neither `regions` nor `entities` covers every region, base types included. Regional types are detected when their region is selected, or when the type is named in `entities`. With `entities` alone nothing is detected but the named types, base types included.

<Note>The base list is what the catalog classifies as region-independent, not what a given detector build emits in practice. Several of these types have narrower recognizers than the catalog entry suggests, so probe the detector before relying on a specific type firing with no configuration.</Note>

<AccordionGroup>
  <Accordion title="Base (every region)">
    `AGE`, `API_KEY`, `BANK_ROUTING`, `BIC`, `BIOMETRIC_ID`, `BLOOD_TYPE`,
    `CREDIT_CARD`, `CRYPTO`, `DATE_TIME`, `DEVICE_ID`, `EDUCATION_LEVEL`,
    `EMAIL_ADDRESS`, `EMPLOYMENT_STATUS`, `GENDER`, `HEALTH_PLAN_ID`,
    `HTTP_COOKIE`, `IBAN_CODE`, `ID`, `ID_CARD`, `IMEI`, `IP_ADDRESS`,
    `JOB_TITLE`, `JWT`, `LANGUAGE`, `LICENSE_NUMBER`, `LICENSE_PLATE`,
    `LOCATION`, `MAC_ADDRESS`, `MEDICAL_RECORD`, `NRP`, `ORGANIZATION`,
    `PASSPORT`, `PASSWORD`, `PERSON`, `PHONE_NUMBER`, `PIN`,
    `POLITICAL_VIEW`, `RACE_ETHNICITY`, `RELIGIOUS_BELIEF`, `SEXUALITY`,
    `TAX_ID`, `URL`, `UUID`, `VEHICLE_ID`
  </Accordion>

  <Accordion title="Belgium (be)">
    `BE_ENTERPRISE_NUMBER`, `BE_NATIONAL_NUMBER`
  </Accordion>

  <Accordion title="Germany (de)">
    `DE_TAX_ID`, `DE_VAT_NUMBER`
  </Accordion>

  <Accordion title="France (fr)">
    `FR_INSEE`, `FR_SIREN`
  </Accordion>

  <Accordion title="United Kingdom (gb)">
    `GB_NHS`, `GB_NINO`
  </Accordion>

  <Accordion title="Netherlands (nl)">
    `BSN`, `KVK_NUMBER`, `NL_DRIVER_LICENSE`, `NL_ONDERWIJSNUMMER`,
    `NL_PAYROLL_TAX_ID`, `NL_RSIN`
  </Accordion>

  <Accordion title="United States (us)">
    `MEDICAL_LICENSE`, `US_BANK_NUMBER`, `US_DRIVER_LICENSE`, `US_ITIN`,
    `US_PASSPORT`, `US_SSN`
  </Accordion>
</AccordionGroup>

### Entity type names that changed

Three keys were renamed or dropped when the catalog became region-scoped. A stored `entities` list carrying an old key is now rejected at write time, so update it before configuring anything else:

| Old key    | Replacement            |
| ---------- | ---------------------- |
| `JOBTITLE` | `JOB_TITLE`            |
| `UK_NHS`   | `GB_NHS`               |
| `TITLE`    | none: the type is gone |

The US types keep their names but are now gated by the `us` region rather than by `language: en`, so a config that relied on English selecting them needs `regions: ["us"]` or an explicit `entities` list.

## Tracing

Redaction is traced in-process as child spans of the request: `pii-redact` for the input pass and `pii-restore` for the output pass. Each span carries the configured `language`, the failure policy, the requested entity count, the placeholder count, and the outcome. `persist_redacted_to_traces` controls whether the redacted (placeholder) form or the restored original is stored in trace content; it defaults to `true` (redacted form stored).
