← Back to Payloads
Tutorial2026-06-16

OpenRouter Cascading Fallbacks in 30 Lines of Python (No More Downtime)

OpenRouter's `models` array auto-tries the next provider on rate limits, downtime, or moderation refusals — here is the 30-line wrapper that makes it production-grade, with cost routing and per-error telemetry.
Quick Access
Install command
$ mrt install openrouter
Browse related skills
OpenRouter Cascading Fallbacks in 30 Lines of Python (No More Downtime)

OpenRouter Cascading Fallbacks in 30 Lines of Python (No More Downtime)

Hey guys, Mr. Technology here. I have been waiting for this one. If you ship an LLM feature in 2026 and you do not have a fallback strategy, you are a single 429 away from a customer escalation. I am going to be honest with you — the day I added OpenRouter's models cascade, my on-call pager went from "weekly" to "almost never." Thirty lines, one list, zero vendor lock-in.

What You Get

OpenRouter accepts a models array. If model #1 returns a rate limit, downtime, context-length error, or moderation flag, OpenRouter silently tries model #2, then #3, then #N. You are billed for the model that actually answered, not the one you asked for. That is it. No SDK. No retry loop. No extra dependency.

**Why this beats a Python try/except loop:** OpenRouter's cascade runs at the edge, with provider-level health telemetry you do not have. A 429 from Bedrock Anthropic and a 429 from Vertex Anthropic are not the same outage. OpenRouter knows. Your Python loop does not.

Setup

bash pip install requests

One env var:

bash export OPENROUTER_API_KEY=sk-or-v1-... # https://openrouter.ai/keys

The Code

```python import os, time, requests from typing import List, Optional

API = "https://openrouter.ai/api/v1/chat/completions"

Order matters: cheapest/fastest first, frontier as the safety net.

CASCADE: List[str] = [ "google/gemini-2.5-flash", # cheap, fast, big context "openai/gpt-4.1-mini", # good fallback, broad tooling "anthropic/claude-sonnet-4-5", # frontier, expensive but reliable "gryphe/mythomax-l2-13b:free", # last resort, free tier ]

def chat(prompt: str, cascade: Optional[List[str]] = None) -> dict: models = cascade or CASCADE body = { "models": models, # the magic line "messages": [{"role": "user", "content": prompt}],

Optional: pin to specific providers so a flaky upstream can't pick the wrong one

"provider": {"order": ["Anthropic", "Google"], "allow_fallbacks": True}, } r = requests.post( API, headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}", "Content-Type": "application/json"}, json=body, timeout=60, ) r.raise_for_status() data = r.json()

OpenRouter returns the model that actually answered in data["model"]

return {"text": data["choices"][0]["message"]["content"], "used": data["model"]}

if __name__ == "__main__": out = chat("Summarize why cascading fallbacks matter in one sentence.") print(f"[{out['used']}] {out['text']}") ```

Run it. The terminal will print something like [anthropic/claude-sonnet-4-5] ... — that is the model that actually served the request, which is your billable model and the one you should log.

Gotchas

  • **provider.order is a soft preference, not a hard pin.** OpenRouter may still route to a different upstream. For a hard pin (compliance, residency), set "allow_fallbacks": False and accept the downtime risk.
  • Context-length errors trigger fallbacks too. A 200k prompt on a 128k model silently falls through. Check data.get("error") before parsing choices if you need to surface it.
  • Tools and JSON-mode schemas are per-model. All entries must support the same tools / response_format shape, or the fallback returns a schema error. Test the full chain with your exact tool definitions.
  • Free-tier models have aggressive rate limits. Putting a :free model last is fine. Putting it first burns through your daily quota in an hour.

Variations

For agent loops, prepend a fast cheap model so a single quick re-route costs nothing. For batch jobs, swap the frontier entry for a local Ollama model (ollama/llama3.1:8b) — free fallbacks against your own hardware. Wrap chat() with tenacity for app-level retries on top of the cascade (network blips, edge 502s).

The pattern: let the router do the work. Stop writing retry decorators. Stop catching RateLimitError. Push the list to OpenRouter and ship.

Mr. Technology

Sources

Related Dispatches