Rate limits
Rate limits
Every API key is subject to a rate limit enforced on a sliding 60-second window, plus a monthly or daily quota depending on your plan. Limits protect the shared infrastructure that backs Forge and keep latency predictable for everyone.
Response headers
Every /v1 response, successful or not, carries three headers describing your current standing:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per 60-second sliding window for your plan. |
X-RateLimit-Remaining | Requests remaining in the current window. |
X-RateLimit-Reset | Unix timestamp (seconds) when the current window resets. |
Plans
| Plan | Per-minute limit | Monthly / daily quota | Price |
|---|---|---|---|
| Free | 60 req/min | 1,000 req/day | $0 |
| Starter | 300 req/min | 100,000 req/month | $29/mo |
| Pro | 1,000 req/min | 1,000,000 req/month | $99/mo |
| Enterprise | Custom | Custom | Custom |
Going over the limit
When you exceed your per-minute limit, Forge returns 429 Too Many Requests with a Retry-After header (seconds to wait) and a structured error body:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1752489672
{
"status": "error",
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Retry after the window resets.",
"doc_url": "https://forge-api.dev/docs/rate-limits"
}
}
Handling 429s with backoff
Treat 429 as a signal to slow down, not an error to surface to your users. Respect Retry-After when present, and fall back to exponential backoff with jitter otherwise:
async function forgeFetch(url, options = {}, attempt = 0) {
const res = await fetch(url, options);
if (res.status === 429 && attempt < 5) {
const retryAfter = Number(res.headers.get('Retry-After')) || 0;
const backoff = retryAfter * 1000 || (2 ** attempt) * 500 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, backoff));
return forgeFetch(url, options, attempt + 1);
}
return res;
}
Best practices
- Read
X-RateLimit-Remainingon every response and throttle proactively before you hit zero. - Cache geocoding results — city coordinates rarely change, so there's no need to re-resolve
city=londonon every request. - Batch or debounce client-driven weather polling instead of issuing one request per UI render.
- If you consistently need more headroom, upgrade your plan from the dashboard rather than working around limits with multiple keys, which violates the terms of service.