Skip to main content
When a guardrail rejects a call, the gateway answers your application with HTTP 400 and the error code guardrail_blocked. This page is the landing reference for that one error: what the body looks like, why it behaves the way it does, and how to handle it in client code. For the policy engine behind it, see Guardrails overview and the full reference.

1. When you see guardrail_blocked

A guardrail is an ordered list of rules the gateway runs against request input and model output. When a rule whose action is block fires, the call is rejected — the upstream model is never called (input stage) or its answer is withheld (output stage). The client receives a 400 carrying guardrail_blocked. No other action produces this error. mask redacts the match and lets the sanitized text through, flag records a match without changing the traffic, and the prompt-shaping actions (annotate, spotlight) let the call proceed while adding a note or wrapping untrusted text. Of the five actions, only block rejects. See Actions.
guardrail_blocked is a content rejection (text in, text out). The companion tool-policy denial is firewall_blocked from the Agent Firewall — a different error with a different shape. See guardrails vs. firewall.

2. The response body

The block is returned in the gateway’s standard OpenAI-shaped error envelope. On an OpenAI-style endpoint (/v1/chat/completions, /v1/responses):
{
  "error": {
    "message": "request blocked by guardrail \"pii-shield\": pii(ssn)",
    "type": "orcarouter_api_error",
    "param": "",
    "code": "guardrail_blocked"
  }
}
The fields you key off:
The stable machine identifier. Branch on this, not on the message string. It is the same value on every endpoint and never localized.
Human-readable. The form is request blocked by guardrail "<name>": <detail>, where <detail> summarizes the rule type(s) that fired as <type>(<rule-detail>) — for example pii(pii: ssn) or keyword(matched 1 keyword(s)). An output-stage block reads response blocked by guardrail "<name>": <detail>, so the verb tells you which stage rejected the call. The message is passed through sensitive-info masking before it leaves the gateway, so don’t expect the raw matched substring here.
The generic gateway error type on OpenAI-style endpoints. The distinguishing signal is code, not type.
On the native Anthropic surface (/v1/messages) the envelope is Claude-shaped — {"error": {"type": ..., "message": ...}} — and guardrail_blocked surfaces in the type field, so a native Claude SDK can distinguish a policy denial from a generic gateway failure.
Want the exact verdict before you ship a rule? The console Test tab evaluates the current policy over sample text with no upstream call and no quota — see testing & eval.

3. Why guardrail_blocked costs no quota

A blocked request is free — it never debits your credit balance.
StageWhen the block firesQuota effect
inputBefore the upstream call, ahead of meteringNothing is metered
outputAfter the model answers, before the answer returnsPre-consumed quota is refunded
So an input block is charged nothing because metering hasn’t happened yet, and an output block reverses the hold the gateway placed before forwarding. Either way the caller pays for the block with a 400, not with credits. See input stage and output stage.

4. Why guardrail_blocked skips retry

The error is tagged skip-retry. The gateway’s own routing won’t fail this request over to another channel, because the block is a property of your content and your policy — re-running the identical prompt would just block again on the next channel and waste the attempt.
Don’t put guardrail_blocked in your client’s retry loop either. It is deterministic: the same prompt against the same policy blocks every time. Retrying burns latency for an outcome that cannot change. Treat it as a terminal rejection — fix the input, or fix the policy.

5. Handling it in client code

Branch on the code field and surface a useful message to the end user instead of retrying.
import httpx

resp = httpx.post(
    "https://api.orcarouter.ai/v1/chat/completions",
    headers={"Authorization": "Bearer sk-orca-..."},
    json={
        "model": "openai/gpt-4o-mini",
        "messages": [{"role": "user", "content": "My SSN is 123-45-6789"}],
    },
)

if resp.status_code == 400:
    err = resp.json().get("error", {})
    if err.get("code") == "guardrail_blocked":
        # Terminal — do not retry. Tell the user what to change.
        raise ValueError(f"Blocked by policy: {err['message']}")

resp.raise_for_status()
The sk-orca-... key here is a relay key — it only carries /v1/* traffic. You never edit a guardrail with it; authoring and attaching policy is a console / management-API action on your session, and creating or editing a guardrail requires the Developer+ role.

6. Confirming and tuning a block

Every rule that fires — block included — lands in the workspace Matches feed with its type, action, stage, and a detail string. That’s where you confirm which rule rejected a given call and triage false positives.

Matches feed

See every block, mask, and flag with type, action, and stage. The matched substring appears only when Log raw content is on.

Logging & privacy

Raw content is off by default — the privacy-conservative posture. Turn it on per guardrail when you need the substring for triage.

Tune false positives

A false positive is a tuning signal, not a reason to disable the rule. Mark it and narrow the pattern.

Versioning

Changed the policy and want to undo it? Diff any two versions and revert as a new version — history is never mutated.
On a streaming response, an output block still applies: the scanner cuts the stream mid-flight before any blocked content reaches the client. Output mask also applies in-band on streams — the scanner rewrites the match in the rolling buffer before the safe prefix is emitted. See streaming coverage and stream-safe rules.