{"record":{"id":"a00eceb30c0f8895","repo":"koala73/worldmonitor","slug":"cloudflare-radar-api-error-resp-status","errorCode":null,"errorMessage":"Cloudflare Radar API error: ${resp.status}","messagePattern":"Cloudflare Radar API error: (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/seed-internet-outages.mjs","lineNumber":146,"sourceCode":"  }\n  return value;\n}\n\nasync function fetchOutages() {\n  const token = process.env.CLOUDFLARE_API_TOKEN;\n  if (!token) {\n    console.log('CLOUDFLARE_API_TOKEN not set — skipping');\n    process.exit(0);\n  }\n\n  const resp = await fetch(`${CF_RADAR_URL}?dateRange=28d&limit=50`, {\n    headers: {\n      Authorization: `Bearer ${token}`,\n      'User-Agent': CHROME_UA,\n    },\n    signal: AbortSignal.timeout(15_000),\n  });\n  if (!resp.ok) throw new Error(`Cloudflare Radar API error: ${resp.status}`);\n\n  const result = requireRadarResult(await resp.json(), 'outage annotations');\n  const annotations = requireRadarArray(result, 'annotations', 'outage annotations');\n\n  const outages = [];\n  for (const raw of annotations) {\n    if (!raw.locations?.length) continue;\n    const countryCode = raw.locations[0];\n    if (!countryCode) continue;\n\n    const coords = COUNTRY_COORDS[countryCode];\n    if (!coords) continue;\n\n    const countryName = raw.locationsDetails?.[0]?.name ?? countryCode;\n\n    const categories = ['Cloudflare Radar'];\n    if (raw.outage?.outageCause) categories.push(raw.outage.outageCause.replace(/_/g, ' '));\n    if (raw.outage?.outageType) categories.push(raw.outage.outageType);","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/seed-internet-outages.mjs#L128-L164","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif (!resp.ok) throw new Error(`Cloudflare Radar API error: ${resp.status}`);\n// after\nif (!resp.ok) {\n  const body = await resp.text().catch(() => '');\n  if (resp.status === 429) { await sleep(backoffMs); return fetchOutages(); }\n  throw new Error(`Cloudflare Radar API error: ${resp.status} ${body.slice(0, 500)}`);\n}","handlingStrategy":"retry","validationCode":"if (!process.env.CLOUDFLARE_API_TOKEN) {\n  throw new Error('CLOUDFLARE_API_TOKEN is not set; cannot call Cloudflare Radar');\n}\n// smoke test before seeding:\nconst probe = await fetch(radarUrl, { headers: { Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`, 'User-Agent': CHROME_UA } });\nif (!probe.ok) throw new Error(`Radar probe failed: ${probe.status} ${await probe.text()}`);","typeGuard":null,"tryCatchPattern":"const fetchWithRetry = async (url, opts, attempts = 3) => {\n  for (let i = 0; ; i++) {\n    const resp = await fetch(url, opts);\n    if (resp.ok) return resp;\n    if ((resp.status === 429 || resp.status >= 500) && i < attempts - 1) {\n      await new Promise(r => setTimeout(r, 2 ** i * 1000));\n      continue;\n    }\n    throw new Error(`Cloudflare Radar API error: ${resp.status} ${(await resp.text().catch(() => '')).slice(0, 500)}`);\n  }\n};","preventionTips":["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."],"tags":["cloudflare","http","authentication","rate-limit","network"],"backgroundTag":"http-error-response","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}