Rate limits are the default failure mode for LLM apps in production. Smart routing absorbs provider rate limits by redistributing load across models and vendors the moment a 429 hits. This guide lays out an actionable path to implement that pattern against an OpenAI-compatible gateway, so your users see fewer errors and your on-call sees fewer pages.
1. Map your rate limit surface
You cannot design fallback without knowing the ceilings. Pull the published RPM/TPM limits for each provider you call. Then measure your real traffic: peak requests per second, average prompt tokens, and max completion tokens.
A simple histogram of request interarrival times exposes burst patterns that trigger limits even when hourly averages look safe. If you send 50 requests in 200 ms to a provider with a 20 RPM limit, you will get 429s regardless of total volume.
Document these facts in a table:
| Provider | Model | RPM | TPM | Observed peak TPS |
|---|---|---|---|---|
| OpenAI | gpt-4o | 500 | 80k | 12 |
| Anthropic | claude-3.5-sonnet | 1000 | 100k | 8 |
This table becomes the input to your routing policy.
2. Configure a single OpenAI-compatible endpoint
Do not wire your code to multiple provider SDKs. Point one OpenAI-compatible client at a gateway that can talk to all backends. This collapses provider-specific auth, base URLs, and error shapes into one surface.
from openai import OpenAI
client = OpenAI(
base_url="https://gateway.example.com/v1",
api_key="YOUR_GATEWAY_KEY",
)
If you use n4n.ai, that endpoint fronts 240+ models and handles fallback automatically when a provider is degraded. Your application code stays identical whether you call a small tokenizer model or a frontier reasoning model.
3. Pass routing directives per request
Routing absorbs provider rate limits only if you tell the gateway which alternative models are acceptable. Most gateways accept a routing hint in the request body. Be explicit about fallbacks and any hard constraints (e.g., data residency, max latency).
{
"model": "openai/gpt-4o",
"messages": [{"role": "user", "content": "Extract entities from this text"}],
"routing": {
"fallback": [
"anthropic/claude-3.5-sonnet",
"google/gemini-1.5-pro"
],
"max_latency_ms": 2000
}
}
In Python with the OpenAI SDK, pass the same structure via extra_body:
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Extract entities"}],
extra_body={
"routing": {
"fallback": ["anthropic/claude-3.5-sonnet", "google/gemini-1.5-pro"],
"max_latency_ms": 2000
}
},
)
A gateway that honors client routing directives (like n4n.ai) forwards these hints to the underlying providers and applies cache-control headers where supported. That means a fallback call can still hit a provider-side prompt cache if the prefix matches.
Choose fallback order deliberately
Rank fallbacks by capability proximity, not just availability. Swapping gpt-4o for an 8B open model may dodge the 429 but break JSON mode. Keep fallbacks within the same task class.
4. Let the gateway absorb the 429
When a provider returns 429 or 503, the gateway should retry against the next candidate in your fallback list without involving your client. This is the core mechanism: routing absorbs provider rate limits by shifting the request transparently.
You still need to handle the case where all candidates are exhausted. The gateway will then return a 429 with a Retry-After header. Parse it.
from openai import RateLimitError
import time
try:
resp = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Go"}],
extra_body={"routing": {"fallback": ["anthropic/claude-3.5-sonnet"]}},
)
except RateLimitError as e:
retry_after = int(e.response.headers.get("Retry-After", "1"))
time.sleep(retry_after)
# optional: downgrade to async queue
Do not implement your own multi-provider retry loop in the client. That duplicates logic the gateway already runs and risks thundering herds when many clients retry simultaneously.
5. Monitor token usage and degradation
Per-token metering is not just for billing. Stream usage events to your observability stack and alert on fallback rate. If 30% of traffic to openai/gpt-4o falls back to Anthropic, your primary provider limit is mis-sized or under-provisioned.
{
"model": "openai/gpt-4o",
"usage": {"prompt_tokens": 120, "completion_tokens": 30, "total_tokens": 150},
"routing": {"served_by": "anthropic/claude-3.5-sonnet", "fallback_depth": 1}
}
Track fallback_depth over time. A rising curve means routing absorbs provider rate limits more often, which signals you should request a limit increase or shift baseline traffic.
6. Common pitfalls and tradeoffs
Latency tax
Cross-provider fallback adds round-trip time. A 429 from provider A plus a cold call to provider B can double p95 latency. Mitigate by warming connections and preferring fallbacks in the same region.
Output drift
Different models format responses differently. Even with identical prompts, Claude and GPT-4o diverge on edge cases. If your parser expects strict JSON, validate after every call and treat fallback responses as lower trust.
Cache invalidation
Provider-side prompt caches are keyed per provider. When routing absorbs provider rate limits by moving to a fallback, you lose the cached prefix. For high-repeat system prompts, this can spike cost. Consider sending cache-control hints only to the primary and accepting cache miss on fallback.
Partial failures
Streaming compounds the problem. If the gateway switches mid-stream, the client may see a truncated chunk from model A then continuation from model B. Disable mid-stream fallback for streaming endpoints; only fail over before the first token.
7. Actionable rollout checklist
- Inventory limits and traffic shape (Section 1).
- Switch SDK to single gateway endpoint (Section 2).
- Add
routing.fallbackto one non-critical endpoint; verify responses. - Wire usage metrics with
fallback_depthto dashboard. - Enable fallback on critical paths; set
max_latency_msto bound tail latency. - Load test at 2x peak to confirm routing absorbs provider rate limits without client errors.
Treat routing as a reliability layer, not a magic shield. It buys time to raise limits or redesign prompts, but it cannot make an 8B model reason like a frontier one.