santifer/career-ops · error · Error
wttj: unexpected Algolia response for query
Error message
wttj: unexpected Algolia response for query "${query}" — expected { hits: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}] What it means
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.
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
Example fix
// before: only checks shape, discards the error body
if (!json || !Array.isArray(json.hits)) { throw new Error(...); }
// after: surface the Algolia error message
if (!json || !Array.isArray(json.hits)) {
if (json?.message && json?.status) {
throw new Error(`wttj: Algolia error ${json.status}: ${json.message}`);
}
throw new Error(`wttj: unexpected Algolia response for query "${query}"`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: verify Algolia credentials still work before a batch run
const res = await fetch(algoliaUrl, { method: 'POST', headers, body: JSON.stringify({ params: 'query=test' }) });
if (!res.ok) throw new Error(`Algolia preflight failed: ${res.status} ${await res.text()}`); Type guard
function isAlgoliaHitsResponse(json) {
return !!json && typeof json === 'object' && Array.isArray(json.hits);
} Try / catch
try {
await scanWttj(entry);
} catch (e) {
if (e.message.startsWith('wttj: unexpected Algolia response')) {
console.warn('Algolia rejected the query — re-fetch /api/env creds, check for anti-bot pages or rate limits, then retry.');
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- justjoin: unexpected API response — expected
- wttj: unexpected Algolia api key shape
- wttj: unexpected Algolia app id
- yourator: unexpected API response on page
- 4dayweek: unexpected API response on page
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/74df7ae66299f8e4.
Report an issue: GitHub.
Appendix: source
Thrown at providers/wttj.mjs:238
// faceted attributes; it never reaches a URL or host, so the assertHost
// guard above still covers every request target.
if (filters) params.set('filters', filters);
const json = /** @type {any} */ (
await ctx.fetchJson(url, {
method: 'POST',
redirect: 'error',
headers: {
'x-algolia-application-id': appId,
'x-algolia-api-key': apiKey,
// The client search key is referer-locked to the WTTJ site.
referer: `${SITE_ORIGIN}/`,
'content-type': 'application/json',
},
body: JSON.stringify({ params: params.toString() }),
})
);
if (!json || !Array.isArray(json.hits)) {
throw new Error(
`wttj: unexpected Algolia response for query "${query}" — expected { hits: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`,
);
}
for (const h of json.hits) {
const job = normalizeWttjHit(h);
if (job && !byUrl.has(job.url)) byUrl.set(job.url, job);
}
}
return [...byUrl.values()];
},
};
View on GitHub (pinned to aac998c7ed)