# How to Troubleshoot 429 Rate Limit Errors in FreeLLMAPI: A Complete Guide

> Troubleshoot 429 rate limit errors in FreeLLMAPI effortlessly. Learn how to diagnose bottlenecks using response headers and the SQLite usage database. Optimize your API calls today.

- Repository: [Tashfeen/freellmapi](https://github.com/tashfeenahmed/freellmapi)
- Tags: how-to-guide
- Published: 2026-06-24

---

**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`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/router.ts) and [`server/src/services/ratelimit.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/router.ts) performs the following steps:

1. **Key Selection** – The router calls `canMakeRequest()`, `canUseTokens()`, and `isOnCooldown()` to filter available keys.
2. **Provider Response** – If the provider returns a 429, the router catches the error and calls `recordRateLimitHit(modelDbId)`.
3. **Cooldown Activation** – The specific key is immediately placed on cooldown via `setCooldown(keyId)`, preventing immediate reuse.
4. **Fallback Retry** – The router attempts the next model in the fallback chain (up to 20 attempts).
5. **Final Response** – If all models are exhausted, the original 429 is returned to the client, often with `X-Fallback-Attempts` headers indicating how many retries occurred.

The **rate-limit ledger** in [`server/src/services/ratelimit.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/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:

```bash
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:

```sql
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`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/router.ts), the functions `recordRateLimitHit()`, `recordSuccess()`, and `getPenalty()` manage this decay system.

To view current penalties programmatically:

```typescript
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`](https://github.com/tashfeenahmed/freellmapi/blob/main/ratelimit.ts) reads environment variables or defaults from `DEFAULT_PROVIDER_DAILY_REQUEST_CAPS`.

Check your environment for overrides:

```bash
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:

```javascript
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:

```typescript
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:

```bash
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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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:

```bash
PROVIDER_DAILY_REQUEST_CAP_OPENROUTER=0

```

## Summary

- **FreeLLMAPI** automatically retries 429 errors using a fallback chain managed by [`server/src/services/router.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/router.ts), but you can diagnose issues by inspecting `X-Routed-Via` and `X-Fallback-Attempts` headers.
- **Rate limit data** is stored in the SQLite `rate_limit_usage` table and in-memory sliding windows in [`server/src/services/ratelimit.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/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 adjusting `PROVIDER_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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/router.ts). Cooldowns last for `COOLDOWN_MS` (30 seconds by default) as defined in [`server/src/services/ratelimit.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/ratelimit.ts). After these intervals, previously rate-limited keys return to the available pool.