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 never stores prompt or response bodies. The usage ledger records 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. The observability integration (Langfuse) also sends metadata only.
PII masking
When piiMasking is enabled on a key, the five categories of personal data 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 withcelon.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 celon virtual ids.
- Directly requesting a blocked model is rejected with
403(model_blocked) — this applies across every surface: chat, embeddings, images, and audio. celon/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-celon-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 per-provider keys under byokKeys on the key record, and calls to that provider use your organization's key instead of the platform's shared key — this applies to both chat and embeddings.
Register them through the admin API (master key required; see the API reference for the full schema):
curl -X POST https://api.celon.ai/admin/keys \
-H "Authorization: Bearer $CELON_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 celon/auto requests to Tier 3 to keep serving at low cost. |
downgradeapplies only tocelon/auto— a request that names a specific model keeps that model.- Embeddings have no downgrade concept — exceeding the budget only ever rejects with
402.