koala73/worldmonitor · error · Error

HTTP ${resp.status}

Error message

HTTP ${resp.status}

What it means

fetchOptionalTargetLocations calls the Cloudflare Radar layer3 attacks/top/locations/target endpoint (7d range) and throws `HTTP ${resp.status}` when `resp.ok` is false, i.e. any status outside 200-299. This seed marks the DDoS target-locations feed as optional/degradable, but the throw still propagates unless caught upstream, so a persistent 4xx from this endpoint blanks the DDoS map's target countries while protocol/vector record counts never move and overall health stays OK. The error string carries only the numeric status, not the body, so diagnosis usually needs the status code alone.

Solutions

  1. Verify the Cloudflare API token used for `headers` is valid and has Radar read scope (curl the endpoint manually with the same headers).
  2. Check the exact status in the message: 401/403 → fix token/permissions; 429 → back off and rerun; 5xx → retry later or during a Radar incident check status.cloudflare.com.
  3. Since this feed is deliberately optional, wrap the `fetchOptionalTargetLocations()` call (in fetchDdosData's Promise.all) with `.catch(() => ({ items: [], degraded: true }))` so a broken target-locations route degrades instead of killing the seed.
  4. Log `degraded: true` to seed-meta/health output so the blank DDoS target map is observable instead of silent.

Example fix

// before
fetchOptionalTargetLocations(),
// after
fetchOptionalTargetLocations().catch((err) => {
  console.warn(`DDoS target locations degraded: ${err.message}`);
  return { items: [], degraded: true };
}),
Defensive patterns

Strategy: try-catch

Validate before calling

// Best-effort pre-check before the seed run:
const resp = await fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/top/locations/target?dateRange=7d`, { headers: { Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}` } });
if (!resp.ok) console.warn(`target-locations precheck failed: ${resp.status}; feed will degrade`);

Try / catch

try {
  const targets = await fetchOptionalTargetLocations();
} catch (err) {
  if (!/^HTTP \d+$/.test(err.message)) throw err; // only swallow the expected HTTP-status error
  logDegraded('ddos-target-locations', err.message);
  const targets = { items: [], degraded: true };
}

Prevention

When it happens

Trigger: Cloudflare Radar returns a non-2xx status for GET /radar/attacks/layer3/top/locations/target?dateRange=7d — e.g. expired/invalid CF API token (401/403), plan-level access restriction on the target-locations route, transient 5xx, or a rate-limit 429 — detected by the `if (!resp.ok) throw` check after a 15s-capped fetch.

Common situations: Rotated or unscoped CLOUDFLARE_API_TOKEN lacking Radar read permission; free-tier account hitting routes behind a paid plan; Radar API incident/degradation returning 500s; hitting per-minute quota during repeated seed runs.

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


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/69427b08a21cb20e. Report an issue: GitHub.

Appendix: source

Thrown at scripts/seed-internet-outages.mjs:217

 * logged, but must not withhold a confirmed protocol/vector summary. Keep this
 * distinction explicit: silently promoting the optional slice to required (or
 * demoting a required one) changes published coverage without changing counts.
 */
async function fetchDdosData(token) {
  const headers = {
    'User-Agent': CHROME_UA,
    ...(token ? { Authorization: `Bearer ${token}` } : {}),
  };

  // Reports `degraded` rather than just returning [] so the caller can stamp the
  // shortfall on seed-meta. An unconfirmed empty slice riding inside a CONFIRMED
  // payload is the exact conflation this issue is about: without the marker, a
  // permanently 4xx target endpoint would blank the DDoS map's target countries
  // forever while recordCount (protocol+vector) never moves and health stays OK.
  const fetchOptionalTargetLocations = async () => {
    try {
      const resp = await fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/top/locations/target?dateRange=7d`, { headers, signal: AbortSignal.timeout(15_000) });
      if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
      const result = requireRadarResult(await resp.json(), 'DDoS target locations');
      return {
        items: requireRadarArray(result, 'top_0', 'DDoS target locations')
          .filter((item) => item && typeof item === 'object' && !Array.isArray(item)),
        degraded: false,
      };
    } catch (err) {
      console.warn(`  CF Radar DDoS target locations unavailable (optional slice): ${err?.message || err}`);
      return { items: [], degraded: true };
    }
  };

  const [protocolResp, vectorResp, targetSlice] = await Promise.all([
    fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/summary/protocol?dateRange=7d`, { headers, signal: AbortSignal.timeout(15_000) }),
    fetch(`${CF_RADAR_BASE}/radar/attacks/layer3/summary/vector?dateRange=7d`, { headers, signal: AbortSignal.timeout(15_000) }),
    fetchOptionalTargetLocations(),
  ]);

View on GitHub (pinned to 7d06c8633d)