{"record":{"id":"74df7ae66299f8e4","repo":"santifer/career-ops","slug":"wttj-unexpected-algolia-response-for-query-que","errorCode":null,"errorMessage":"wttj: unexpected Algolia response for query \"${query}\" — expected { hits: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]","messagePattern":"wttj: unexpected Algolia response for query \"(.+?)\" — expected (.+?), got keys: \\[(.+?)\\]","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/wttj.mjs","lineNumber":238,"sourceCode":"      // faceted attributes; it never reaches a URL or host, so the assertHost\n      // guard above still covers every request target.\n      if (filters) params.set('filters', filters);\n      const json = /** @type {any} */ (\n        await ctx.fetchJson(url, {\n          method: 'POST',\n          redirect: 'error',\n          headers: {\n            'x-algolia-application-id': appId,\n            'x-algolia-api-key': apiKey,\n            // The client search key is referer-locked to the WTTJ site.\n            referer: `${SITE_ORIGIN}/`,\n            'content-type': 'application/json',\n          },\n          body: JSON.stringify({ params: params.toString() }),\n        })\n      );\n      if (!json || !Array.isArray(json.hits)) {\n        throw new Error(\n          `wttj: unexpected Algolia response for query \"${query}\" — expected { hits: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`,\n        );\n      }\n      for (const h of json.hits) {\n        const job = normalizeWttjHit(h);\n        if (job && !byUrl.has(job.url)) byUrl.set(job.url, job);\n      }\n    }\n    return [...byUrl.values()];\n  },\n};\n","sourceCodeStart":220,"sourceCodeEnd":250,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/wttj.mjs#L220-L250","documentation":"The wttj provider POSTs to Algolia and expects the JSON response to contain a hits array (Algolia's standard search result shape). If the parsed JSON is null, lacks hits, or hits is not an array, fetch throws this error listing the top-level keys actually received. This indicates the Algolia query failed at the API level (auth, invalid params, rate limit) or the response was intercepted by a proxy/challenge page.","triggerScenarios":"Algolia returns an error JSON body (e.g. {message, status} for 4xx), a Cloudflare/anti-bot HTML or JSON challenge replaces the response, the app id/api key from /api/env became invalid mid-run, or the request body/params are malformed so the response has no hits field.","commonSituations":"Rotated-out Algolia credentials producing 403 {\"message\":\"...\"} bodies; rate limiting (429) with a non-hits JSON; a corporate proxy returning an error page; WTTJ switching search backend so the endpoint no longer speaks Algolia.","solutions":["Log or print the full response body/status for the failing query to see which keys were returned (the error lists them)","Re-run the /api/env fetch and confirm the app id and api key are still valid for Algolia queries","Check whether the response is an anti-bot challenge or proxy error and add appropriate headers/cookies or run from a different network","If the keys look like an Algolia error object (message/status), read its message — usually invalid API key, invalid filters syntax, or quota exceeded"],"exampleFix":"// before: only checks shape, discards the error body\nif (!json || !Array.isArray(json.hits)) { throw new Error(...); }\n// after: surface the Algolia error message\nif (!json || !Array.isArray(json.hits)) {\n  if (json?.message && json?.status) {\n    throw new Error(`wttj: Algolia error ${json.status}: ${json.message}`);\n  }\n  throw new Error(`wttj: unexpected Algolia response for query \"${query}\"`);\n}","handlingStrategy":"try-catch","validationCode":"// preflight: verify Algolia credentials still work before a batch run\nconst res = await fetch(algoliaUrl, { method: 'POST', headers, body: JSON.stringify({ params: 'query=test' }) });\nif (!res.ok) throw new Error(`Algolia preflight failed: ${res.status} ${await res.text()}`);","typeGuard":"function isAlgoliaHitsResponse(json) {\n  return !!json && typeof json === 'object' && Array.isArray(json.hits);\n}","tryCatchPattern":"try {\n  await scanWttj(entry);\n} catch (e) {\n  if (e.message.startsWith('wttj: unexpected Algolia response')) {\n    console.warn('Algolia rejected the query — re-fetch /api/env creds, check for anti-bot pages or rate limits, then retry.');\n  } else throw e;\n}","preventionTips":["Log response status and body on non-hits responses to distinguish auth vs rate-limit vs challenge","Refresh Algolia app id/key from /api/env at the start of each run, not once per deploy","Send realistic browser headers and cookies to reduce anti-bot interception","Add a bounded retry with backoff for transient 5xx/rate-limit responses"],"tags":["network","algolia","wttj","response-shape"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}