Openrouter Guardrails Gateway Spend Governance Poc 2026
- Slug:
openrouter-guardrails-gateway-spend-governance-poc-2026 - Run date: 2026-07-27
- Track: api-backed-poc
- Evidence level: api-backed-lab (partial — see Limitations)
- Machine artifact:
data/lab-runs/openrouter-guardrails-gateway-spend-governance-poc-2026.openrouter.json - Runner: ad-hoc
python3+urllibscript (no SDK, no third-party deps) - Credentials: one existing Effloow workspace inference key (
sk-or-v1…). No management key, no provisioning key. Key material scrubbed from every recorded body. - Spend: under one cent total for the whole run.
Why this run exists
OpenRouter's Guardrails docs and OpenRouter's own launch post disagree about which HTTP status a blocked request gets back. The docs guide says 403. The 2026-05-29 announcement says 402 for a budget block, 404 for a disallowed model or provider, 403 for prompt-injection and DLP blocks. That difference is not cosmetic: retry logic, alerting, and billing dashboards all branch on the status code. So the question for this run was narrow and testable — what does the live API actually return, and does a blocked request cost money?
Environment
| Item | Value |
|---|---|
| Base URL | https://openrouter.ai/api/v1 |
| Auth | workspace inference key |
| Model under test | openai/gpt-4o-mini |
| Client | python3 stdlib urllib.request |
| Headers sent | Authorization, Content-Type, HTTP-Referer, X-Title |
| Date | 2026-07-27 |
Commands
Three scripts were run. Full source is reproducible from the recorded request bodies in the JSON artifact; the shape was:
req = urllib.request.Request(
"https://openrouter.ai/api/v1/chat/completions",
method="POST",
data=json.dumps(body).encode(),
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json",
"HTTP-Referer": "https://www.effloow.com", "X-Title": "Effloow Lab"},
)
Run 1 — read-only key and guardrail-management probes:
GET /key
GET /guardrails
Run 2 — five chat-completion variants, max_tokens: 8, prompt "Reply with exactly the word: OK":
control model=openai/gpt-4o-mini
bad model model=acme/definitely-not-a-real-model
allowlist block provider={"only":["Groq"],"allow_fallbacks":false}
price ceiling provider={"max_price":{"prompt":1e-7,"completion":1e-7},"allow_fallbacks":false}
zdr routing provider={"zdr":true,"allow_fallbacks":false}
Run 3 — billing check, executed twice:
GET /key (read usage)
5x POST /chat/completions (allowlist-block variant)
sleep 4
GET /key (read usage)
Results
Status-code matrix (measured)
| Check | Status | Body / notable fields |
|---|---|---|
GET /key |
200 | limit: null, limit_reset: null, limit_remaining: null, include_byok_in_limit: false, is_management_key: false, is_provisioning_key: false |
GET /guardrails with inference key |
401 | {"error":{"message":"Invalid management key","code":401}} |
| Control completion | 200 | provider: "Azure", usage.total_tokens: 16, usage.cost: 0.0000033 |
| Invalid model ID | 400 | "acme/definitely-not-a-real-model is not a valid model ID" |
| Provider allowlist violation | 404 | "No allowed providers are available for the selected model." + metadata.available_providers: ["azure","openai"], metadata.requested_providers: ["groq"] |
| Price ceiling too low | 404 | "No endpoints found that satisfy the max price for this request" |
| ZDR-only routing | 200 | succeeded, provider: "Azure", cost 0.0000033 |
Billing check
| Cycle | Blocked requests | Statuses | Usage delta (USD) |
|---|---|---|---|
| 1 | 5 | 404 ×5 | 0.0000066 |
| 2 | 5 | 404 ×5 | 0.0000000 |
Cycle 1's delta is exactly 2 × 0.0000033 — the two successful control calls from run 2 settling
late. Cycle 2 ran with no successful calls in between and moved account usage by exactly $0.00
across five blocked requests. Conclusion: requests rejected at the routing/restriction layer were
not billed.
Absolute account balance is deliberately omitted from the artifact; only deltas were recorded.
What worked
- Read-only key introspection is a genuine zero-cost pre-flight check.
GET /keyexposeslimit,limit_remaining,limit_reset, andinclude_byok_in_limit, so a deploy gate can assert "this key has a cap" before shipping an agent. - Restriction violations return machine-readable
metadatanaming both the requested and the available providers. That is enough to write a useful operator error message without guessing. - Rejections are free. Ten blocked calls, zero cents.
What failed / was not reachable
- No budget cap was created. The available key is an inference key;
GET /guardrailsreturned401 Invalid management key. So the actual USD-cap rejection path was never exercised. Whether a tripped budget returns 402 or 403 is docs-stated only, not measured here. - Prompt-injection and PII/DLP guardrails were not exercised at all. Detection accuracy, recall,
and false-positive behavior are
[DATA NOT AVAILABLE]as Effloow evidence. - The 404s measured here come from per-request provider-routing constraints, which exercise the same allow/deny decision path as an allowlist guardrail but are not the Guardrails feature itself. This is an important caveat and is stated as such in the article.
Limitations
- Single workspace, single model (
openai/gpt-4o-mini), single day. Status codes may differ by model, provider set, account tier, or after a vendor change. max_tokenswas 8 on every call; no long-context or streaming path was tested.- The
sleep 4before the second usage read is a heuristic for settlement lag, not a documented guarantee. Cycle 1 shows settlement can lag at least one request round. - No load, no concurrency, no sustained soak. This says nothing about behavior under burst.
- Vendor pricing and guardrail feature descriptions are quoted from OpenRouter's own docs and announcement; they were not independently priced or benchmarked.
Sources consulted
- OpenRouter Guardrails guide — https://openrouter.ai/docs/guides/features/guardrails
(re-read live on 2026-07-27; the guide states verbatim: "Note that budget limits and allowlist
restrictions also produce 403 responses, but only runtime content checks include
openrouter_metadatastage details." Our measured restriction responses were 404, matching the announcement's number rather than the guide's. Note the caveat in "What failed": our 404s came from per-request routing constraints, not from a configured Guardrails allowlist, so this is a documented mismatch on the routing layer and not a formal disproof of the guide's sentence.) - OpenRouter Guardrails announcement (2026-05-29) — https://openrouter.ai/blog/announcements/guardrails/
- OpenRouter Guardrails API reference (create/update/list guardrail) — https://openrouter.ai/docs/api/api-reference/guardrails/create-guardrail
- Live OpenRouter API responses recorded in this run (primary evidence)
Read the article
This note supports the public article and records what was actually checked.
Get the runs we publish next
One short weekly dispatch with new guides, tools, and what we actually tested. No spam, unsubscribe anytime.