{"record":{"id":"0687c18bf9826a18","repo":"santifer/career-ops","slug":"manfred-unexpected-api-response-expected-a-json","errorCode":null,"errorMessage":"manfred: unexpected API response — expected a JSON array of offers, got ${json === null ? 'null' : typeof json}","messagePattern":"manfred: unexpected API response — expected a JSON array of offers, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/manfred.mjs","lineNumber":210,"sourceCode":"\n/** @type {Provider} */\nexport default {\n  id: 'manfred',\n\n  detect(entry) {\n    return entry?.provider === 'manfred' ? { url: buildFeedUrl(entry) } : null;\n  },\n\n  async fetch(entry, ctx) {\n    // Validate the URL actually fetched (not just a constant) so the host pin\n    // is meaningful, then redirect:'error' blocks SSRF via server-side\n    // redirects — together they keep the request on getmanfred.com.\n    const url = assertManfredUrl(buildFeedUrl(entry));\n    const json = /** @type {any} */ (\n      await fetchJsonWithRetry(ctx, url, { redirect: 'error', timeoutMs: FETCH_TIMEOUT_MS })\n    );\n    if (!Array.isArray(json)) {\n      throw new Error(\n        `manfred: unexpected API response — expected a JSON array of offers, got ${json === null ? 'null' : typeof json}`,\n      );\n    }\n    const fallbackCompany = entry?.name;\n    const out = [];\n    for (const offer of json) {\n      const normalized = normalizeManfredOffer(offer, fallbackCompany);\n      if (normalized) out.push(normalized);\n    }\n    return out;\n  },\n};\n","sourceCodeStart":192,"sourceCodeEnd":223,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/providers/manfred.mjs#L192-L223","documentation":"The Manfred fetch() calls the feed API and expects the JSON body to be an array of offer objects. If the parsed response is not an array (an object, string, number, or null), fetch() throws this error describing what type actually arrived. It guards against the API contract changing or an error/proxy page being returned with 200.","triggerScenarios":"fetchJson resolves with valid JSON that is not an array: the API returned an error envelope like {\"error\":...}, a WAF/HTML-to-JSON gateway response, an empty object, or a literal null body.","commonSituations":"Manfred changes its feed response shape (wraps offers in {offers:[...]}); a captive portal or bot-protection layer intercepts the request and returns JSON that isn't the feed; the feed URL is valid but points at a non-feed endpoint.","solutions":["Log the actual response body at the failing URL to see what shape came back.","Check whether the Manfred feed API changed shape and update the provider to unwrap the array (e.g. json.offers).","Re-verify the feed URL is the offers-feed endpoint and not an error/docs endpoint.","If a bot-protection layer is intercepting, retry from a normal network or adjust request headers via the fetch context."],"exampleFix":"// before\nif (!Array.isArray(json)) throw new Error(...);\nconst out = [];\nfor (const offer of json) {...}\n// after (if API now wraps the array)\nconst offers = Array.isArray(json) ? json : Array.isArray(json?.offers) ? json.offers : null;\nif (!offers) throw new Error('manfred: unexpected API response shape');","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const isOfferArray = (v) => Array.isArray(v) && v.every((o) => o && typeof o === 'object');","tryCatchPattern":"try {\n  const jobs = await provider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).startsWith('manfred: unexpected API response')) {\n    const body = await fetch(feedUrl).then((r) => r.text());\n    console.error('Feed returned non-array body:', body.slice(0, 300));\n    return [];\n  }\n  throw err;\n}","preventionTips":["Log the raw response body on shape mismatch to detect API contract changes early.","Pin an integration test that asserts the live feed parses as an array.","Handle wrapped envelope shapes ({offers:[...]}) defensively in downstream code.","Treat a persistent shape error as an API-change signal, not a transient one — investigate the endpoint."],"tags":["api","json","response-shape"],"backgroundTag":"unexpected-response-shape","analyzedSha":"e7abd431fce9348a95261acac9e0c14779c35df8","analyzedAt":"2026-09-22T13:19:01.448Z","contentChangedAt":"2026-09-22T13:19:01.448Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}