LLM guardrails: PII redaction, allowlists and regions

One /v1 endpoint for every provider. Point your OpenAI client at a new base URL and key, bring your own provider keys, and pay 0% markup on tokens.

A guardrail is a named policy you assign to a key or to a member of your organisation. It can restrict which models, providers and data regions a request may use, cap spend, and filter what requests contain. Policy merges organisation, then workspace, then key, and each level can only narrow what the level above allows. Budgets at different levels are checked independently, never added together.

POST/v1/guardrails
POST/v1/guardrails/:id/assign
GET/v1/guardrails
DELETE/v1/guardrails/:id

Which plan has what

  • Every plan, Solo included: PII redaction with the built-in detectors, for the whole organisation, in redact mode (below).
  • Team and Enterprise: guardrails themselves — custom patterns, block mode, the filter allowlist, budgets, model, provider and region allowlists, and per-workspace, per-key and per-member policies. Without the plan, creating or assigning one answers 402 subscription_required; what was set up before keeps working.

PII redaction for the whole organisation

The built-in detectors listed below, applied to every request from every key in the organisation, with no guardrail to assign. Matches are always redacted, never blocked. Owners and admins set it; [] turns it off. It combines with any guardrail on the key, so a guardrail can add detectors, patterns or block mode on top but never remove these. Asking this endpoint for a custom pattern, block mode or a workspace or key scope is a 402 on Solo and a 400 on Team, which point you to guardrails.

GET/v1/guardrails/redaction
PUT/v1/guardrails/redaction
curl
curl -X PUT https://autonomousrelay.com/v1/guardrails/redaction \
  -H "Authorization: Bearer $AUTONOMOUSRELAY_MANAGEMENT_KEY" \
  -H 'content-type: application/json' \
  -d '{"builtins": ["email", "phone", "credit-card", "secrets"]}'

builtins takes email, phone, ssn, credit-card, ip-address, secrets and prompt-injection.

Fields

  • name: required.
  • allowed_models, allowed_providers: lists. Empty means unrestricted.
  • allowed_data_regions: an endpoint must run in one of these regions. An endpoint with no recorded region is excluded, so the filter fails closed.
  • limit_usd, reset_interval (daily, weekly, monthly, lifetime) and include_byok_in_budgets: a spend ceiling for whatever the guardrail is assigned to. It counts spend on your own provider keys at list price unless include_byok_in_budgets is false; see what a ceiling counts.
  • content_filter_builtins, content_filters, filter_allowlist: below.

Content filtering and redaction

Filters run on the request’s messages after the spend checks and before routing, so a redacted prompt is what reaches the provider. The model’s reply is not filtered.

The built-in detectors, named in content_filter_builtins, always redact:

  • email, phone, ssn, ip-address, person-name, address.
  • credit-card: Luhn-checked, so a 16-digit order number is not mistaken for a card.
  • secrets: 36 credential formats, including cloud, AI provider, source hosting, payment and messaging keys, package-registry tokens, JWTs and private keys. Each is labelled by format, for example SECRET:github-pat.
  • prompt-injection: instructions that try to override the system prompt.

content_filters adds up to 100 of your own, each {"pattern", "action", "label"}. The pattern is a regular expression, matched without regard to case. A pattern that could backtrack without bound (nested quantifiers, backreferences, lookarounds) is refused when you save it. The action is flag (record only), redact or block.

If anything that matched blocks, the request is refused with 403 guardrail_blocked, naming the labels that matched and never the matched text. Otherwise each redacted span is replaced by its label in brackets, such as [email] or [SECRET:github-pat]; overlapping matches show every label, as in [email|customer-id]. A flag changes nothing in the request.

filter_allowlist lists phrases, up to 200 characters each, that no filter touches, matched without regard to case. Your own support address is the usual one, so it is not redacted out of every prompt that mentions it.

Each request records the filter’s action and labels in Activity, so you can see what was redacted or blocked without the content being stored.

curl
curl -X POST https://autonomousrelay.com/v1/guardrails \
  -H "Authorization: Bearer $AUTONOMOUSRELAY_MANAGEMENT_KEY" \
  -H 'content-type: application/json' \
  -d '{"name": "client-acme",
       "allowed_providers": ["anthropic", "openai"],
       "content_filter_builtins": ["email", "secrets"],
       "content_filters": [{"pattern": "ACME-\\d{6}", "action": "block", "label": "acme-id"}],
       "filter_allowlist": ["support@acme.example"]}'

curl -X POST https://autonomousrelay.com/v1/guardrails/$GUARDRAIL_ID/assign \
  -H "Authorization: Bearer $AUTONOMOUSRELAY_MANAGEMENT_KEY" \
  -H 'content-type: application/json' \
  -d '{"subject_type": "key", "subject_id": "'$KEY_HASH'"}'

subject_type is key (with the key’s hash from GET /v1/keys) or member (with the person’s user id from GET /v1/members).

Prompt injection

The prompt-injection detector looks for six kinds of attack: instructions to ignore or override the system prompt, “developer mode” requests, attempts to extract the prompt, model control tokens, role reassignment, and instructions to send data elsewhere. It also sees through three common disguises — letters spaced out, letters scrambled inside a word, and text hidden in base64 or hex, which it decodes and scans again — and reports a match against the span the request actually contains.

It is pattern-based: fast, predictable and the same on every request, and it can miss a new phrasing. Use block to refuse a match, or flag to record it while you see what it catches on your traffic. It is one layer, not a substitute for limiting what a model’s tools may do.

Data regions: check before you require them

allowed_data_regions fails closed. An endpoint qualifies only if it declares a deployment region on your list; one that declares nothing is excluded, not assumed to comply. A policy that no endpoint of a model meets refuses every request for that model.

Before you assign one, read deployment_region on GET /v1/models/:author/:slug/endpoints and send a real request body to POST /v1/route/preview with the key the guardrail will be assigned to: it answers which endpoints would be tried, or why none would, without contacting a provider.

Retention and training are not guardrails

A guardrail cannot require zero data retention or no training on prompts, and enforce_zdr with any true is a 400. Whether a provider keeps or trains on your prompts is set by your own agreement with it, and we do not record it. To keep a provider you have no such agreement with from seeing your prompts, leave it out of allowed_providers. See What we store.

Teams that also need single sign-on with SCIM, a private routing tier, seven-year ledger retention or an SLA: see Enterprise.