{"record":{"id":"664b6904acaa70e6","repo":"koala73/worldmonitor","slug":"imagery-search-failed-resp-status","errorCode":null,"errorMessage":"Imagery search failed: ${resp.status}","messagePattern":"Imagery search failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/imagery.ts","lineNumber":31,"sourceCode":"let retryAfterUntil = 0;\n\nexport async function fetchImageryScenes(params: ImagerySearchParams): Promise<ImageryScene[]> {\n  if (Date.now() < retryAfterUntil) throw new Error('Imagery search rate limited');\n  const url = new URL(toApiUrl('/api/imagery/v1/search-imagery'), window.location.origin);\n  url.searchParams.set('bbox', params.bbox);\n  if (params.datetime) url.searchParams.set('datetime', params.datetime);\n  if (params.source) url.searchParams.set('source', params.source);\n  if (params.limit) url.searchParams.set('limit', String(params.limit));\n\n  const resp = await fetch(url.toString(), { signal: AbortSignal.timeout(15_000) });\n  if (!resp.ok) {\n    if (resp.status === 429) {\n      const retryAfter = resp.headers.get('Retry-After');\n      const seconds = retryAfter === null ? NaN : Number(retryAfter);\n      const deadline = Number.isFinite(seconds) ? Date.now() + Math.max(0, seconds) * 1000 : Date.parse(retryAfter ?? '');\n      retryAfterUntil = Number.isFinite(deadline) ? deadline : Date.now() + 60_000;\n    }\n    throw new Error(`Imagery search failed: ${resp.status}`);\n  }\n  const data = await resp.json();\n  return data.scenes ?? [];\n}\n","sourceCodeStart":13,"sourceCodeEnd":36,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/src/services/imagery.ts#L13-L36","documentation":"fetchImageryScenes() throws this when the imagery search API returns any non-OK response. The status code is embedded in the message; a 429 additionally arms the module-level cooldown before throwing. It surfaces upstream/API failures (bad request, auth, 5xx) rather than parsing errors.","triggerScenarios":"Calling fetchImageryScenes() and receiving a non-2xx from /api/imagery/v1/search-imagery — e.g. 400 for a malformed bbox/datetime, 401/403 for auth, 429 for rate limiting, or 5xx from the upstream provider.","commonSituations":"Passing an invalid bbox (inverted or out-of-range coordinates); datetime strings the API rejects; server-side outage or upstream imagery provider error; hitting the API rate limit (429).","solutions":["Read the status in the message: fix a 400 by validating bbox (minLon<maxLon, lat within ±90) and datetime format before calling","For 429, back off and retry after the cooldown (see the paired 'rate limited' error)","For 5xx, retry with exponential backoff; check the API/server health if it persists","Capture the response body for detail — the thrown message only carries the numeric status"],"exampleFix":"// before\nif (!Number.isFinite(bbox[0]) || bbox[0] > bbox[2]) return;\n// after\nconst [minLon, minLat, maxLon, maxLat] = bbox;\nif ([minLon, minLat, maxLon, maxLat].some(Number.isNaN)) throw new Error('bbox must be 4 numbers');\nif (minLon >= maxLon || minLat >= maxLat) throw new Error('bbox min must be < max');","handlingStrategy":"validation","validationCode":"function isValidBbox(b: number[]): boolean { return b.length === 4 && b.every(Number.isFinite) && b[0] < b[2] && b[1] < b[3]; }","typeGuard":"const isImageryParams = (p: unknown): p is ImagerySearchParams => typeof p === 'object' && p !== null && Array.isArray((p as any).bbox) && isValidBbox((p as any).bbox);","tryCatchPattern":"try { scenes = await fetchImageryScenes(p); } catch (e) { const m = /Imagery search failed: (\\d+)/.exec(e.message); if (m) logImageryHttpFailure(Number(m[1])); throw e; }","preventionTips":["Validate bbox and datetime (ISO 8601) before calling","Back off on 429 and 5xx with exponential retry","Monitor the imagery endpoint's health and statuses"],"tags":["http","imagery","api-error","status-code"],"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"}