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.