jackwener/OpenCLI · warning · CommandExecutionError
${label} returned HTTP 429 (rate limited)
Error message
${label} returned HTTP 429 (rate limited) What it means
openalexFetch detects HTTP 429 from api.openalex.org and throws CommandExecutionError indicating the request was rate limited, with a hint to wait and retry or set OPENALEX_MAILTO. OpenAlex throttles unauthenticated traffic to its common pool; providing a mailto= parameter moves requests into the faster 'polite pool'.
Source
Thrown at clis/openalex/utils.js:96
);
}
export async function openalexFetch(url, label) {
let resp;
try {
resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
}
catch (err) {
throw new CommandExecutionError(
`${label} request failed: ${err?.message ?? err}`,
'Check that api.openalex.org is reachable from this network.',
);
}
if (resp.status === 404) {
throw new EmptyResultError(label, `OpenAlex returned 404 for ${url}.`);
}
if (resp.status === 429) {
throw new CommandExecutionError(
`${label} returned HTTP 429 (rate limited)`,
'OpenAlex throttles unauthenticated traffic; wait a few seconds and retry, or set OPENALEX_MAILTO.',
);
}
if (!resp.ok) {
let detail = '';
try {
const text = await resp.text();
const match = text.match(/"message"\s*:\s*"([^"]+)"/);
if (match) detail = ` (${match[1]})`;
}
catch { /* ignore */ }
throw new CommandExecutionError(`${label} returned HTTP ${resp.status}${detail}`);
}
let body;
try {
body = await resp.json();
}View on GitHub (pinned to 49907e53dc)
Solutions
- Set OPENALEX_MAILTO (or append ?mailto=you@example.com) to join the polite pool
- Add throttling: sleep ~150-200ms between requests, or use a concurrency limit of 1-5
- Back off and retry on 429 honoring any Retry-After header
- Batch queries where possible (e.g. filter=ids:W1|W2|… in one request) to cut request count
Example fix
// before
for (const id of ids) await work(id); // bursts -> 429
// after
await Promise.all(ids.map(id =>
withBackoff(() => work(id))
));
async function withBackoff(fn, tries = 5) {
try { return await fn(); }
catch (e) {
if (tries && String(e).includes('429')) {
await new Promise(r => setTimeout(r, 2 ** (5 - tries) * 500));
return withBackoff(fn, tries - 1);
}
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
// ensure mailto is configured before bulk runs
if (!process.env.OPENALEX_MAILTO) {
console.warn('Set OPENALEX_MAILTO to join the OpenAlex polite pool and avoid 429s');
} Try / catch
async function fetchWithBackoff(fn, maxTries = 5) {
for (let i = 0; i < maxTries; i++) {
try { return await fn(); }
catch (e) {
const is429 = e.name === 'CommandExecutionError' && e.message.includes('429');
if (!is429 || i === maxTries - 1) throw e;
await new Promise(r => setTimeout(r, 2 ** i * 500));
}
}
} Prevention
- Set OPENALEX_MAILTO to opt into the polite pool
- Throttle to ~5-10 requests/sec max in bulk scripts
- Honor Retry-After headers when retrying
- Batch related lookups into single filter=… requests to reduce call count
When it happens
Trigger: Issuing many openalex requests in a short window without mailto, exceeding OpenAlex's unauthenticated rate limit (roughly 10 req/s), causing the API to respond 429.
Common situations: Bulk scripts looping over hundreds of IDs with no delay; CI jobs hammering the API in parallel; shared-IP environments (offices, CI runners) exhausting the collective unauthenticated quota.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- coingecko returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
- coingecko derivatives returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/239510ab0b800b6a.
Report an issue: GitHub.