Governance
Governance
Built for the regulatory demands of Korean enterprises — prompt bodies are never stored, personal data is masked before it leaves the gateway, and both spend and data are governed through three budget tiers (organization, team/department, key) plus organization-specific guardrails (custom PII patterns, banned words, model policy). Configure it through the admin API or the governance console in the dashboard.
ZDR — Zero Data Retention
The gateway stores no prompt or response bodies in its database. The usage ledger and the audit trail record metadata only:
ts·keyId·orgId·endpointmodel·provider·tierpromptTokens·completionTokens·costUsd·savedUsdcacheHit·latencyMs·status
Internal debug logging follows the same policy — the content, messages, input, and prompt fields are replaced with "[ZDR]" right before anything is logged. What we export for observability is metadata only.
PII masking
Masking runs when the effective guardrail policy has detectors.pii enabled — set it on the organization (governance.guardrails), on the key (guardrails), or from the governance console. It is off by default: a key with no guardrail policy masks nothing. Once enabled, the five categories below are replaced with per-type tokens before the request leaves for the upstream provider.
| Type | Pattern | Replacement token |
|---|---|---|
| Resident registration number (RRN) | YYMMDD-NNNNNNN — with date validation | [MASKED:RRN] |
| Credit card | 13–19 digits (spaces/dashes allowed), only when the Luhn checksum passes | [MASKED:CARD] |
| Mobile phone | 01[016789]-XXXX-XXXX, including the +82 international format | [MASKED:PHONE] |
| Standard email format | [MASKED:EMAIL] | |
| Passport number | M/S + 8 digits | [MASKED:PASSPORT] |
Input: My number is 010-1234-5678 and my email is kim@acme.co.kr
Sent: My number is [MASKED:PHONE] and my email is [MASKED:EMAIL]- The masking order is fixed as RRN → CARD → PHONE → EMAIL → PASSPORT to prevent false positives (for example, a phone-number-shaped run inside a card number).
- Per-key on/off — controlled by the
piiMaskingflag on the key record, and can be turned off per request withwarp.no_pii_mask. - The number of masked items is reported in the
pii_maskedresponse metadata. - Masking is one-way — the gateway keeps no mapping for restoring the original text (ZDR).
Organization-specific patterns and banned words
Beyond the five defaults, an organization can register its own policies under governance.guardrails on the org record. When guardrails is set on a key, the key takes precedence (key override → organization policy → gateway default).
| Field | Details |
|---|---|
pii.entities | Individual on/off for the five defaults — e.g. { "EMAIL": false } excludes only email from masking |
pii.custom | Custom regex patterns (up to 64) — { "name": "EMPLOYEE_ID", "pattern": "EMP-\\d{6}" }. Matched spans are replaced with [MASKED:EMPLOYEE_ID]. Invalid or pathologically slow patterns are rejected with 400 at registration time. |
bannedWords | A banned-word list (up to 500, case-insensitive substring match) — each entry follows the policy mode by default, and its action can individually set block/mask/log-only. An action: "block" entry always blocks even when the policy is mask/shadow. |
Model policy
Restrict which models can be used per organization or key — register blockedModels/allowedModels under the org's governance.models or the key's modelPolicy (the key wins, blocks win over allows, and an empty allow list permits everything). Entries accept catalog model ids, namespace wildcards like openai/*, and warp virtual ids.
- Directly requesting a blocked model is rejected with
403(model_blocked) — this applies across every surface: chat, embeddings, images, and audio. warp/autorouting drops blocked models from the candidate set and routes around them to allowed models — if an entire tier is blocked, it escalates to the tier above.GET /v1/modelsreturns only the list reflecting the key's effective policy — blocked models do not appear in the catalog.
Tiered guardrail stack
The PII masking above is stage 1 of the guardrail stack. Before a request leaves for the upstream, it passes through the detectors below in fastest-first order — the cheap regex stages catch most of it, and the expensive stages run only when needed. Per-detector on/off is controlled by the key's guardrails.detectors policy (see the API reference).
| Stage | Detector | Latency | Details |
|---|---|---|---|
| 1 | Structured PII detectors.pii | sub-ms | Regex — RRN, card (Luhn), phone, email, passport (same as the table above) |
| 2 | Secrets / entropy detectors.secrets | ~1–2ms | API-key and cloud-token patterns plus high-entropy strings (leaked credentials) |
| 3 | Presidio NER detectors.presidio | external call | Unstructured PII (names, addresses, organizations) — an external Presidio instance, enabled only when PRESIDIO_URL is set |
| 4 | Prompt-injection heuristics detectors.injection | sub-ms | Screens for jailbreak and instruction-override patterns — the integration point for Prompt Guard 2 |
The three enforcement modes
Separate from what gets detected, how a detection is handled is set by the key's guardrails.mode.
| Mode | Name | Behavior |
|---|---|---|
block | Validation | Rejects the request immediately on a violation — only a summary of the violated categories is returned, never the body. |
mask | Mutation | Automatically masks the detected spans with [MASKED:*] tokens, then forwards to the upstream (one-way, not reversible). |
shadow | Shadow | Passes the request through unchanged and only records the detections (audit only) to the Audit Trail — ideal for a dry run before rolling out a policy. |
In any mode, the per-request result is reported in the guardrails.findings response metadata and the x-warp-guardrail-findings header — it carries only counts and categories, never the original text (ZDR).
Streaming output guardrails
When guardrails.streamOutput is enabled on a key, the model's output is inspected too — SSE chunks are collected in a sliding buffer and validated incrementally, so even a resident registration number or secret split across token boundaries is caught. On a confirmed violation, the stream is halted immediately with finish_reason: "content_filter".
Audit Trail
Every guardrail decision is recorded to an immutable, append-only metadata log — only the mode, per-category detection counts, whether it was blocked, and the request id are stored; prompt and response bodies are not (ZDR). Query it with GET /admin/audit (see the API reference).
BYOK — Bring Your Own Key
Use the upstream contracts your organization already holds (OpenAI, Anthropic, and so on) as-is. Register a key per provider, and calls to that provider use your organization's key instead of the platform's shared key — across chat, embeddings, image and audio.
The simplest way is the console BYOK tab (owner only, one field per provider). Keys are stored at the organization level; to point a single API key at a different account, set a per-provider override under byokKeys on that key record via the admin API. Stored keys are encrypted at rest and never returned on read — you only ever see which providers are configured.
The platform fee still applies to BYOK. A request served on your own key still draws the 5% platform fee from your credits on its usage, and is blocked with 402 when your balance is exhausted. That usage is priced at Warp's catalog rates — if you have negotiated different rates or a subscription with the provider, the fee is still computed from the catalog price, which may differ from your actual provider bill.
Registering through the admin API (master key required; see the API reference for the full schema):
curl -X POST https://api.warp.inc/admin/keys \
-H "Authorization: Bearer $WARP_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"orgId": "org_abc123",
"name": "prod-backend",
"piiMasking": true,
"zdr": true,
"byokKeys": {
"openai": "sk-proj-…",
"anthropic": "sk-ant-…"
}
}'Budget policy — organization, team (department), key
Budgets are tallied by UTC calendar month and enforced at three levels: organization → team (department) → key. null means unlimited. A key joins a team via teamId, and team spend is recorded against its membership at request time — moving a key to another team leaves past spend with the original team.
| Scope | Behavior when exceeded |
|---|---|
| Organization budget | A hard cap — always rejected with 402 (budget_exceeded). It takes precedence regardless of the team and key policies. |
| Team (department) budget | Follows the team's overBudgetBehavior — "reject" denies with 402, "downgrade" forces a switch to Tier 3. When the team and key both exceed at once, the stricter decision wins. |
| Key budget | Follows the key's overBudgetBehavior — "reject" denies with 402, "downgrade" forces the tier to 3 to keep serving at low cost. |
downgradeapplies on every path. The forced tier acts as a spend cap, so a request that names a specific model above that cap re-routes through the forced tier instead of keeping the named model — otherwise only clients with a hardcoded model id would escape the downgrade.- Embeddings have no downgrade concept — exceeding the budget only ever rejects with
402.