{"record":{"id":"c523df9c0d1b2fa4","repo":"koala73/worldmonitor","slug":"cf-radar-ddos-api-error-protocol-protocolresp-status-vector","errorCode":null,"errorMessage":"CF Radar DDoS API error: protocol=${protocolResp.status} vector=${vectorResp.status}","messagePattern":"CF Radar DDoS API error: protocol=(.+?) vector=(.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/seed-internet-outages.mjs","lineNumber":237,"sourceCode":"      return {\n        items: requireRadarArray(result, 'top_0', 'DDoS target locations')\n          .filter((item) => item && typeof item === 'object' && !Array.isArray(item)),\n        degraded: false,\n      };\n    } catch (err) {\n      console.warn(`  CF Radar DDoS target locations unavailable (optional slice): ${err?.message || err}`);\n      return { items: [], degraded: true };\n    }\n  };\n\n  const [protocolResp, vectorResp, targetSlice] = await Promise.all([\n    fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/summary/protocol?dateRange=7d`, { headers, signal: AbortSignal.timeout(15_000) }),\n    fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/summary/vector?dateRange=7d`, { headers, signal: AbortSignal.timeout(15_000) }),\n    fetchOptionalTargetLocations(),\n  ]);\n\n  if (!protocolResp.ok || !vectorResp.ok) {\n    throw new Error(`CF Radar DDoS API error: protocol=${protocolResp.status} vector=${vectorResp.status}`);\n  }\n\n  const [protocolResult, vectorResult] = await Promise.all([\n    protocolResp.json().then((data) => requireRadarResult(data, 'DDoS protocol')),\n    vectorResp.json().then((data) => requireRadarResult(data, 'DDoS vector')),\n  ]);\n\n  function toEntries(summary) {\n    return Object.entries(summary).map(([label, pct]) => ({ label, percentage: parseFloat(pct) || 0 }))\n      .sort((a, b) => b.percentage - a.percentage);\n  }\n\n  const topTargetLocations = targetSlice.items.map((item) => {\n    const code = item.clientCountryAlpha2 || '';\n    const coords = COUNTRY_COORDS[code] || null;\n    return {\n      countryCode: code,\n      countryName: item.clientCountryName || code,","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/seed-internet-outages.mjs#L219-L255","documentation":"fetchDdosData fetches the Cloudflare Radar layer3 attacks summary endpoints for protocol and vector in parallel (plus the optional target-locations fetch) and throws this combined error when either summary response is non-2xx. Unlike [145], this one is treated as fatal: without protocol/vector summaries the DDoS record cannot be written at all. The message embeds both status codes so you can tell at a glance which of the two upstream calls failed.","triggerScenarios":"Either `GET /radar/attacks/layer3/summary/protocol?dateRange=7d` or `GET /radar/attacks/layer3/summary/vector?dateRange=7d` returns non-2xx (401/403 bad token, 429 rate limit, 5xx outage) after the 15s timeout window, triggering `if (!protocolResp.ok || !vectorResp.ok) throw`.","commonSituations":"Expired Cloudflare API token in the seed environment; Radar API partial outage affecting one summary route; running seeds frequently enough to trip Radar rate limits; token scoped to the wrong account/plan.","solutions":["Read protocol=NNN vector=NNN in the message: the non-zero/non-200 side is the failing endpoint; curl it with the same headers to reproduce.","401/403: regenerate the Cloudflare token with Radar read permissions and update the seed env; 429: add backoff/spacing between seed runs; 5xx: retry later.","Add one retry with short backoff around the two fetches to ride out transient 5xx/429.","Confirm the responses are also valid JSON afterwards — a 200 with an HTML error page would instead fail later in requireRadarResult."],"exampleFix":"null","handlingStrategy":"retry","validationCode":"// Pre-flight both endpoints cheaply:\nfor (const path of ['radar/attacks/layer3/summary/protocol', 'radar/attacks/layer3/summary/vector']) {\n  const r = await fetch(`${CF_RADAR_BASE}/${path}?dateRange=7d`, { headers });\n  if (!r.ok) throw new Error(`precheck ${path}: ${r.status}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const ddos = await fetchDdosData(headers);\n} catch (err) {\n  const m = err.message.match(/protocol=(\\d+) vector=(\\d+)/);\n  if (m && (m[1] === '429' || +m[1] >= 500 || m[2] === '429' || +m[2] >= 500)) {\n    await sleep(backoff); // then retry once before giving up\n  } else throw err;\n}","preventionTips":["Keep the Radar token valid and scoped; rotate before expiry and test with a curl precheck.","Space seed runs to respect Radar rate limits; add exponential backoff on 429/5xx.","Log which side (protocol vs vector) failed using the status pair in the message.","Check Cloudflare status page before long seed batches during incidents."],"tags":["http","cloudflare-radar","seed-script","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"}