koala73/worldmonitor · error · Error
Cloudflare Radar API error: ${resp.status}
Error message
Cloudflare Radar API error: ${resp.status} What it means
fetchOutages() calls the Cloudflare Radar outage annotations endpoint with a Bearer token and a 15s timeout, and throws this error for any non-2xx HTTP status (401, 403, 429, 5xx, etc.). It surfaces only the numeric status, so diagnosing requires mapping the status code to its usual cause.
Solutions
- Check CLOUDFLARE_API_TOKEN is set and valid: curl the endpoint with `Authorization: Bearer $CLOUDFLARE_API_TOKEN` and read the response body/errors.
- Recreate the token with Cloudflare Radar read permission (Account > API Tokens) and reload it through loadEnvFile().
- For 429, back off and retry with exponential delay; stagger requests if seeding multiple Radar datasets.
- For 5xx, retry after a delay and include the response body in the error message for diagnosis; verify status on https://www.cloudflarestatus.com.
Example fix
// before
if (!resp.ok) throw new Error(`Cloudflare Radar API error: ${resp.status}`);
// after
if (!resp.ok) {
const body = await resp.text().catch(() => '');
if (resp.status === 429) { await sleep(backoffMs); return fetchOutages(); }
throw new Error(`Cloudflare Radar API error: ${resp.status} ${body.slice(0, 500)}`);
} Defensive patterns
Strategy: retry
Validate before calling
if (!process.env.CLOUDFLARE_API_TOKEN) {
throw new Error('CLOUDFLARE_API_TOKEN is not set; cannot call Cloudflare Radar');
}
// smoke test before seeding:
const probe = await fetch(radarUrl, { headers: { Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`, 'User-Agent': CHROME_UA } });
if (!probe.ok) throw new Error(`Radar probe failed: ${probe.status} ${await probe.text()}`); Try / catch
const fetchWithRetry = async (url, opts, attempts = 3) => {
for (let i = 0; ; i++) {
const resp = await fetch(url, opts);
if (resp.ok) return resp;
if ((resp.status === 429 || resp.status >= 500) && i < attempts - 1) {
await new Promise(r => setTimeout(r, 2 ** i * 1000));
continue;
}
throw new Error(`Cloudflare Radar API error: ${resp.status} ${(await resp.text().catch(() => '')).slice(0, 500)}`);
}
}; Prevention
- Load CLOUDFLARE_API_TOKEN through loadEnvFile() and fail fast with a clear message when missing.
- Create tokens with Radar read scope and rotate on a schedule; test after every rotation.
- Add exponential backoff for 429/5xx and stagger requests when seeding multiple Radar datasets.
- Include the response body in error messages — the numeric status alone hides the root cause.
- Watch https://www.cloudflarestatus.com during seeding windows.
When it happens
Trigger: GET to the Radar outage annotations endpoint returns resp.ok === false: 401/403 for missing/invalid token or missing Radar permission, 429 for rate limiting, 5xx for Cloudflare-side errors, or network-level failures surfacing as non-OK statuses from a proxy.
Common situations: CLOUDFLARE_API_TOKEN unset, expired, or rotated without updating the environment; token created without the Radar API scope; bursty seeding loops hitting Radar's rate limits; transient Radar outages returning 5xx.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- NHC layer ${layerId}: ${cause.message}
- Exa ${endpoint.slice(1)} failed HTTP ${response.status}: ${d
- DNS ${recordType} lookup failed: HTTP ${response.status}
- Redis HTTP ${resp.status}
- HTTP ${response.status}
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/a00eceb30c0f8895.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/seed-internet-outages.mjs:146
}
return value;
}
async function fetchOutages() {
const token = process.env.CLOUDFLARE_API_TOKEN;
if (!token) {
console.log('CLOUDFLARE_API_TOKEN not set — skipping');
process.exit(0);
}
const resp = await fetch(`${CF_RADAR_URL}?dateRange=28d&limit=50`, {
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': CHROME_UA,
},
signal: AbortSignal.timeout(15_000),
});
if (!resp.ok) throw new Error(`Cloudflare Radar API error: ${resp.status}`);
const result = requireRadarResult(await resp.json(), 'outage annotations');
const annotations = requireRadarArray(result, 'annotations', 'outage annotations');
const outages = [];
for (const raw of annotations) {
if (!raw.locations?.length) continue;
const countryCode = raw.locations[0];
if (!countryCode) continue;
const coords = COUNTRY_COORDS[countryCode];
if (!coords) continue;
const countryName = raw.locationsDetails?.[0]?.name ?? countryCode;
const categories = ['Cloudflare Radar'];
if (raw.outage?.outageCause) categories.push(raw.outage.outageCause.replace(/_/g, ' '));
if (raw.outage?.outageType) categories.push(raw.outage.outageType);View on GitHub (pinned to 7d06c8633d)