> For the complete documentation index, see [llms.txt](https://docs.trezalabs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trezalabs.com/ai-gateway/proxies-and-policies.md).

# Proxies & Policies

A **proxy** is a customer-configured redaction endpoint: it bundles an upstream LLM provider, an optional stored provider key, and a redaction policy describing which PII entity types to strip. Your application selects a proxy per request with the `x-treza-proxy` header.

Proxies are managed from the **Control Plane** page in the dashboard, or programmatically via the `/api/proxies` REST endpoints described below.

{% hint style="info" %}
The `/api/proxies` management endpoints authenticate as the **dashboard user** — send your Treza session access token as `Authorization: Bearer <token>`. They are not Treza-API-key endpoints; runtime traffic through `/api/redact/chat/completions` is what uses your `treza_live_...` key.
{% endhint %}

### Supported upstream providers

| Provider     | `upstreamProvider` | Default upstream URL                         | `upstreamUrl` required? | Plan     |
| ------------ | ------------------ | -------------------------------------------- | ----------------------- | -------- |
| OpenAI       | `openai`           | `https://api.openai.com/v1/chat/completions` | No                      | Starter+ |
| Azure OpenAI | `azure-openai`     | — (your resource URL)                        | Yes                     | Pro+     |
| Anthropic    | `anthropic`        | `https://api.anthropic.com/v1/messages`      | No                      | Pro+     |
| Custom       | `custom`           | —                                            | Yes                     | Pro+     |

Non-OpenAI providers require the **multiple providers** entitlement (Pro and above); creating one on Starter returns `402` with `code: "PLAN_FEATURE"`.

{% hint style="warning" %}
The proxy forwards the redacted request body as-is and authenticates upstream with `Authorization: Bearer <your upstream key>`. Custom endpoints must therefore accept OpenAI-style chat-completion JSON with Bearer authentication — OpenAI-compatible gateways (vLLM, LiteLLM, OpenRouter, and similar) work out of the box.
{% endhint %}

### The proxy object

```json
{
  "id": "proxy_1718039482_ab12cd34e",
  "accountId": "acct_...",
  "name": "Production OpenAI",
  "upstreamProvider": "openai",
  "upstreamUrl": "https://api.openai.com/v1/chat/completions",
  "redactionPolicy": { "entities": [] },
  "status": "active",
  "hasUpstreamKey": true,
  "createdAt": "2026-06-10T12:00:00.000Z",
  "updatedAt": "2026-06-11T09:30:00.000Z",
  "lastRequestAt": "2026-06-11T09:30:00.000Z"
}
```

The stored upstream key is never returned — only the `hasUpstreamKey` flag.

### List proxies

```bash
curl https://trezalabs.com/api/proxies \
  -H "Authorization: Bearer $TREZA_SESSION_TOKEN"
```

Returns `{ "proxies": [...] }`.

### Create a proxy

```bash
curl -X POST https://trezalabs.com/api/proxies \
  -H "Authorization: Bearer $TREZA_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production OpenAI",
    "upstreamProvider": "openai",
    "upstreamKey": "sk-...",
    "entities": []
  }'
```

| Field              | Type      | Notes                                                               |
| ------------------ | --------- | ------------------------------------------------------------------- |
| `upstreamProvider` | string    | Required. One of `openai`, `azure-openai`, `anthropic`, `custom`    |
| `name`             | string    | Optional; defaults to `"<provider> proxy"`                          |
| `upstreamUrl`      | string    | Required for `azure-openai` and `custom`; overrides the default URL |
| `upstreamKey`      | string    | Optional. Encrypted with KMS envelope encryption at rest            |
| `entities`         | string\[] | Optional custom redaction policy (Pro+, see below)                  |

Returns `201` with `{ "proxy": {...} }`. Plan limits are enforced server-side: exceeding your plan's proxy cap returns `402` with `code: "PLAN_LIMIT"`.

### Update a proxy

`PUT /api/proxies` updates fields by proxy `id`:

```bash
curl -X PUT https://trezalabs.com/api/proxies \
  -H "Authorization: Bearer $TREZA_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "proxy_1718039482_ab12cd34e",
    "status": "paused"
  }'
```

Updatable fields: `name`, `status` (`active` | `paused`), `upstreamUrl`, `entities`, `upstreamKey` (rotate the stored key), `clearUpstreamKey: true` (remove the stored key — callers must then send `x-model-key` per request).

Requests routed to a paused proxy are rejected with `403 Proxy is paused`.

### Delete a proxy

```bash
curl -X DELETE "https://trezalabs.com/api/proxies?id=proxy_1718039482_ab12cd34e" \
  -H "Authorization: Bearer $TREZA_SESSION_TOKEN"
```

### Test a policy (dry run)

`POST /api/proxies/test` previews what a proxy's policy strips from sample text. It makes no upstream call and is **not billed**:

```bash
curl -X POST https://trezalabs.com/api/proxies/test \
  -H "Authorization: Bearer $TREZA_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Email jane.doe@acme.com, card 4111 1111 1111 1111",
    "proxyId": "proxy_1718039482_ab12cd34e"
  }'
```

```json
{
  "redacted": "Email [EMAIL_1], card [CC_1]",
  "entities": [
    { "type": "EMAIL", "placeholder": "[EMAIL_1]", "start": 6, "end": 15 },
    { "type": "CC", "placeholder": "[CC_1]", "start": 23, "end": 29 }
  ],
  "entityCount": 2,
  "mode": "standard"
}
```

Omit `proxyId` to preview the default policy (redact everything detected).

### Custom redaction policies (Pro+)

A proxy's `redactionPolicy.entities` array lists the entity types to redact:

* **Empty array (default):** redact *every* entity type the pipeline detects.
* **Non-empty array:** redact only the listed types; anything else detected is left in the prompt and passed through to the provider.

```bash
curl -X PUT https://trezalabs.com/api/proxies \
  -H "Authorization: Bearer $TREZA_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "proxy_1718039482_ab12cd34e",
    "entities": ["EMAIL", "PHONE", "SSN", "CC"]
  }'
```

Setting a non-empty policy requires the **custom policies** entitlement (Pro and above); otherwise the API returns `402` with `code: "PLAN_FEATURE"`.

Valid entity types: `NAME`, `EMAIL`, `PHONE`, `ADDRESS`, `SSN`, `CC`, `MRN`, `DOB`, `ACCOUNT`, `URL`, `DATE`, `SECRET`. See the [overview](/ai-gateway/ai-control-plane.md#what-gets-redacted) for descriptions.

{% hint style="info" %}
Policies are applied **after** detection: excluded entity types are restored from the redaction map before forwarding, and only the entities actually redacted are counted in the audit log.
{% endhint %}

### Selecting a proxy at request time

Runtime requests pick their configuration with headers on `POST /api/redact/chat/completions`:

| Header              | Purpose                                                                                |
| ------------------- | -------------------------------------------------------------------------------------- |
| `x-treza-proxy`     | Proxy id to use (upstream URL, stored key, and policy). `404` if unknown               |
| `x-model-key`       | Upstream provider key, when the proxy has no stored key (or no proxy is selected)      |
| `x-treza-rehydrate` | Set to `1` to receive the placeholder map in the `x-treza-rehydration` response header |

Without `x-treza-proxy`, the request is forwarded to the default OpenAI upstream with the full redact-everything policy, and `x-model-key` is required. If neither a stored key nor `x-model-key` is available the request fails with `400`.

{% hint style="info" %}
**Streaming.** Set `stream: true` in the request body to receive the upstream Server-Sent Events stream piped straight through. Redaction happens on the request before the first token, so the placeholder→original map is sent up-front in the `x-treza-rehydration` header — rehydrate client-side against accumulated text, since a placeholder may span SSE chunks. The [`treza redact proxy`](https://github.com/treza-labs/treza-docs-site/tree/main/developers/treza-cli.md) CLI handles this for you. Both streaming and buffered responses are billed per request.
{% endhint %}

{% hint style="warning" %}
If the redaction step fails, the request is **not** forwarded upstream — the proxy fails closed and returns `502 redaction_failed`. Your original messages never leave Treza unredacted.
{% endhint %}
