How to Troubleshoot 429 Rate Limit Errors in FreeLLMAPI: A Complete Guide
FreeLLMAPI automatically handles 429 errors by backing off, marking keys as rate-limited, and retrying fallback models, but you can diagnose specific bottlenecks by inspecting response headers and querying the SQLite usage database.
FreeLLMAPI (tashfeenahmed/freellmapi) routes every request through an intelligent router that manages multiple provider keys and enforces rate limits. When a provider returns a 429 Too Many Requests error, the system automatically retries with fallback models, but understanding the underlying mechanism in server/src/services/router.ts and server/src/services/ratelimit.ts helps you identify which specific key or provider is throttling your requests.
How FreeLLMAPI Handles 429 Errors
The router implements a sophisticated fallback chain that activates when rate limits are hit.
When you send a request, the router in server/src/services/router.ts performs the following steps:
- Key Selection – The router calls
canMakeRequest(),canUseTokens(), andisOnCooldown()to filter available keys. - Provider Response – If the provider returns a 429, the router catches the error and calls
recordRateLimitHit(modelDbId). - Cooldown Activation – The specific key is immediately placed on cooldown via
setCooldown(keyId), preventing immediate reuse. - Fallback Retry – The router attempts the next model in the fallback chain (up to 20 attempts).
- Final Response – If all models are exhausted, the original 429 is returned to the client, often with
X-Fallback-Attemptsheaders indicating how many retries occurred.
The rate-limit ledger in server/src/services/ratelimit.ts maintains in-memory sliding-window counters backed by SQLite, tracking every request and token usage while pruning old entries automatically.
Diagnosing Which Key or Provider Hit the Limit
When you encounter 429 errors, use these diagnostic methods to pinpoint the exact bottleneck.
Inspect Response Headers
Every successful response includes headers that reveal the routing path:
X-Routed-Via– Shows the provider and model that ultimately served the request.X-Fallback-Attempts– Indicates how many fallback retries were required before success or final failure.
Inspect these headers using curl:
curl -i -H "Authorization: Bearer <your-key>" \
https://your-api-endpoint/v1/chat/completions
If X-Fallback-Attempts is high or the request fails with 429, your primary providers are hitting limits.
Query the SQLite Usage Database
The rate_limit_usage table stores every request and token count. Access it directly to see recent activity:
SELECT platform, model_id, key_id,
SUM(CASE WHEN kind='request' THEN 1 ELSE 0 END) AS reqs,
SUM(CASE WHEN kind='tokens' THEN tokens ELSE 0 END) AS toks
FROM rate_limit_usage
WHERE created_at_ms > strftime('%s','now')*1000 - 86400000
GROUP BY platform, model_id, key_id
ORDER BY reqs DESC;
Run this with the bundled SQLite CLI or any database viewer to identify which keys are approaching their limits.
Check the Penalty System
Each 429 error adds a penalty that demotes the model in the fallback order. In server/src/services/router.ts, the functions recordRateLimitHit(), recordSuccess(), and getPenalty() manage this decay system.
To view current penalties programmatically:
import { getAllPenalties } from './server/src/services/router.ts';
console.table(getAllPenalties());
Penalties automatically decay over DECAY_INTERVAL_MS (2 minutes by default), but accumulated penalties can push viable models down the fallback chain, causing delays.
Verify Provider-Wide Caps
Some providers (like OpenRouter) enforce a single daily quota shared across all models. The function getProviderDailyRequestCap() in ratelimit.ts reads environment variables or defaults from DEFAULT_PROVIDER_DAILY_REQUEST_CAPS.
Check your environment for overrides:
echo $PROVIDER_DAILY_REQUEST_CAP_OPENROUTER
Common Causes and Fixes
| Cause | Symptoms | Solution |
|---|---|---|
| Per-minute request limit (RPM) | canMakeRequest returns false; frequent 429s on high-volume keys |
Reduce request concurrency, add X-Session-Id for sticky sessions, or distribute load across additional keys |
| Per-day request quota (RPD) | Hitting provider caps (e.g., OpenRouter's 1,000 req/day) | Add more keys for that provider, reorder the fallback chain, or upgrade to paid tiers |
| Token budget exceeded (TPM/TPD) | canUseTokens returns false despite low request count |
Lower max_tokens in requests, or use keys with larger token budgets |
| Penalty buildup | Models with available capacity are skipped due to high penalty scores | Wait for automatic decay (2 minutes) or restart the server to clear in-memory penalties |
| Provider-wide daily cap | All models from a provider fail simultaneously regardless of individual key health | Add another provider to your fallback chain, or adjust PROVIDER_DAILY_REQUEST_CAP_* environment variables |
| Stale cooldown flags | Keys remain unavailable after provider recovers | Restart the server to clear in-memory cooldowns, or wait for COOLDOWN_MS (default 30 seconds) |
Practical Debugging Techniques
Analyzing Usage with a Node.js Script
Create a debug script to aggregate usage across all keys:
import { getDb } from './server/src/db/index.js';
const db = getDb();
const now = Date.now();
const dayAgo = now - 24 * 60 * 60 * 1000;
const rows = db.prepare(`
SELECT platform, model_id, key_id,
SUM(CASE WHEN kind='request' THEN 1 ELSE 0 END) AS reqs,
SUM(CASE WHEN kind='tokens' THEN tokens ELSE 0 END) AS toks
FROM rate_limit_usage
WHERE created_at_ms > ?
GROUP BY platform, model_id, key_id
`).all(dayAgo);
console.table(rows);
Run this from the repository root with node debug-usage.js to see per-key request and token counts, helping you identify which keys are near their limits.
Clearing Cooldowns Manually (Development Only)
For local debugging, you can manually clear a cooldown:
import { clearCooldown } from './server/src/services/ratelimit.ts';
// Remove cooldown for key ID 3
clearCooldown(3);
Warning: Only use this in development environments. Never expose cooldown manipulation in production.
Using the Dashboard API
Query the key health status via the internal API:
curl -H "Authorization: Bearer <unified-key>" \
http://localhost:3001/api/keys
The response includes each key's status field (healthy, rate_limited, or error), along with health-check logs.
Adjusting Configuration and Environment Variables
Fine-tune rate limiting behavior through environment variables:
| Variable | Effect | Example |
|---|---|---|
PROVIDER_DAILY_REQUEST_CAP_<PROVIDER> |
Overrides the default daily request cap for the given provider | PROVIDER_DAILY_REQUEST_CAP_OPENROUTER=2000 |
RATE_LIMIT_COOLDOWN_MS |
Duration a key stays on cooldown after a 429 (modify constant in ratelimit.ts) |
Default is 30000 (30 seconds) |
FREELLMAPI_CONTEXT_HANDOFF |
Enables automatic system-message injection when switching models after fallback | FREELLMAPI_CONTEXT_HANDOFF=on_model_switch |
To disable a provider-wide cap entirely (which will cause 429s to surface immediately rather than being masked by the router), set the cap to 0:
PROVIDER_DAILY_REQUEST_CAP_OPENROUTER=0
Summary
- FreeLLMAPI automatically retries 429 errors using a fallback chain managed by
server/src/services/router.ts, but you can diagnose issues by inspectingX-Routed-ViaandX-Fallback-Attemptsheaders. - Rate limit data is stored in the SQLite
rate_limit_usagetable and in-memory sliding windows inserver/src/services/ratelimit.ts. - Penalties from repeated 429s decay over 2 minutes and can be viewed via
getAllPenalties(). - Common fixes include adding more keys, reducing
max_tokens, clearing stale cooldowns via server restart, and adjustingPROVIDER_DAILY_REQUEST_CAP_*environment variables.
Frequently Asked Questions
How do I know which specific provider key is causing 429 errors?
Check the X-Routed-Via response header to see which provider served the final request, and query the rate_limit_usage table in SQLite to see which keys have the highest request counts in the last 24 hours. If X-Fallback-Attempts is greater than zero, your primary keys were already exhausted.
Why does FreeLLMAPI return 429 even when I have available credits with my provider?
FreeLLMAPI maintains its own rate-limit counters in server/src/services/ratelimit.ts that track per-minute (RPM) and per-day (RPD) limits independently of your provider's billing. If you see 429s, you have likely hit these tracked limits rather than your provider's credit limits. Check the rate_limit_usage database table to confirm.
Can I disable the automatic fallback behavior to see raw provider errors?
While you cannot completely disable the router, you can set PROVIDER_DAILY_REQUEST_CAP_<PROVIDER>=0 for your specific provider to prevent the router from attempting fallback within that provider's model group. Alternatively, configure only a single model in your request to effectively bypass the fallback chain.
How long do rate limit penalties last before they expire?
Penalties decay automatically every DECAY_INTERVAL_MS (2 minutes by default) as defined in server/src/services/router.ts. Cooldowns last for COOLDOWN_MS (30 seconds by default) as defined in server/src/services/ratelimit.ts. After these intervals, previously rate-limited keys return to the available pool.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →