400 from a security control is not a
bug in your prompt. It’s a policy doing its job. Your job is to find
which policy, then decide whether to fix the call or relax the rule.
1. Why was my llm request blocked? — start with the error code
Every security block on the hosted gateway returns HTTP 400 with a machine-readablecode in the OpenAI-shaped error body. That code is the
first fork in the road — it tells you which control plane to debug and
which feed to open.
firewall_approval_pending
A tool call is held for human approval — not denied. Resolve it,
don’t debug it. See §4.
All three codes are skip-retry: re-running the identical call routes
to the same policy and blocks again. Retrying is wasted latency — fix the
input or the rule instead. The full code table lives in
Error codes.
2. guardrail_blocked — find the rule in Matches
guardrail_blocked means a content policy attached to your key (or your
workspace default) ran a block action against the request input or
the model’s output. The block message names the guardrail and the rule,
and the request costs you no quota — input blocks fire before
metering; output blocks refund the pre-consumed quota.
Trace it:
Open the Matches feed
In the console, go to the Matches tab on the Guardrails page
(
GET /api/guardrail/match, Member). Every rule that fires lands
here — its RuleType, Action, Stage, and a Detail string like
pii: email, phone or matched 3 keyword(s).Filter to the block
Filter by
action = block and the time of your request. The matching
row tells you the rule type (pii, regex, keyword, max_chars,
llm_judge, grounding, external) and whether it fired on the
input or output stage.See what it actually matched
By default the feed records that a rule fired and its
Detail
meta-string — not the matched substring. Turn on Log raw
content on that guardrail (it’s off by default, the
privacy-conservative posture) to capture the offending string for
triage. The toggle is non-retroactive.One concrete example
You send a support reply that contains a customer SSN. Yourpii-shield
guardrail has an entity_actions override that blocks on ssn:
400 guardrail_blocked. The Matches feed shows
RuleType: pii, Action: block, Stage: input, Detail: pii: ssn. The
fix is a product decision, not a code change: relax the override to mask
(the model never sees the SSN, the call goes through), or keep the block
and strip the SSN upstream. See Guardrails for the
full rule-type and PII-entity reference.
3. firewall_blocked — find the verdict in Events
firewall_blocked means a Firewall policy
denied a tool call. On the inbound surface it surfaces as 400; through
the MCP gateway it surfaces as a tool error (firewall deny: <reason>)
so the model can react instead of crashing. The error metadata carries
the reason code, risk factors, and score.
Trace it in the Events feed (GET /api/workspace/firewall/events,
Developer+) — the raw record behind every evaluation. Each event carries
a verdict and the surface it was seen on:
| Verdict | What it means for your block |
|---|---|
deny | A rule (or the default_verdict) blocked the call. This is your firewall_blocked. |
audit | Allowed but logged — including a [shadow] “would deny” if the policy is in shadow mode. |
cap_cost | The run’s accumulated spend crossed a per-rule cents cap; resolves to a deny. |
Filter Events to the denial
Filter by
verdict=deny, then by tool, run_id, or session_id
to isolate the exact call. The event names the matched rule and the
surface — inbound, response, mcp, or egress.Read the reason on the matched rule
The reason string (e.g.
destructive shell command,
egress host not allowed) tells you whether the rule matched on the
tool name, an args_match clause, or an egress destination. The
verdict glossary and
glob & JSONPath reference decode
the matching.4. firewall_approval_pending — it’s held, not denied
firewall_approval_pending is the one 400 you should not treat as a
block. A pending_approval verdict held the tool call for a human; the
error body carries an approval id. The call hasn’t failed — it’s
waiting.
- A reviewer resolves it — from the console (Developer+) or via your own
HMAC webhook callback (
POST /api/v1/firewall/approvals/:id/callback). - Your agent polls
GET /api/v1/firewall/approvals/:id(gateway token) on the id from the error. - Once approved, re-submit the original call with the single-use
X-OrcaRouter-Firewall-Approvalheader, and the gateway lets it through that one time.
5. Not a security block? Rule out the key first
Not every400 is a guardrail or firewall verdict. Before you go feed-
diving, rule out the key constraints — these reject before any
policy runs and don’t carry the security codes above:
Model rejected before the upstream call
Model rejected before the upstream call
The key’s
model_limits allow-list doesn’t include the requested
model. Requests for a model outside the list are rejected up front.
Add the model to the key or call one that’s allowed.Rejected at authentication by IP
Rejected at authentication by IP
The key has an
allow_ips allow-list and the request came from an
address outside it. Add the caller’s IP / CIDR or call from an
allowed network.Spend cap reached
Spend cap reached
The key’s
credit_limit_usd ceiling is exhausted (0 means
unlimited). Raise the cap or rotate to a key with headroom.403 on /api/v1/firewall/* routes
403 on /api/v1/firewall/* routes
The gateway hooks (
evaluate, MCP dispatch) require a key with
is_firewall_gateway=true. A regular relay key gets 403. Mint a
firewall-gateway-scoped key for those routes.6. The two-minute triage flow
Block on prompt or response text
Code is
guardrail_blocked → open Matches, filter
action=block, read the Stage + Detail. Fix the content or the
rule; prove it in the guardrail Test tab.Block on a tool call
Code is
firewall_blocked → open Events, filter
verdict=deny, read the surface + reason. Fix the call or the rule;
prove it in the firewall Test tab.Call is held
Code is
firewall_approval_pending → poll the approval id and
re-submit with the approval header. Nothing to debug.None of the above
No security code → check the key:
model_limits, allow_ips,
credit_limit_usd, or 403 for a missing gateway scope.7. Related references
Error codes
The full code table — every block, hold, and rejection the gateway
can return.
Verdict glossary
What each firewall verdict means and when it resolves to a deny.
Glob & JSONPath
Decode the
tool_name_glob and args_match that matched your call.Guardrails vs Firewall
Which plane fired — text screening or action governance.
