santifer/career-ops · error · Error
remotli: unexpected API response — expected
Error message
remotli: unexpected API response — expected { jobs: [...] }, got ${data === null ? 'null' : typeof data} What it means
After fetching, the remotli provider verifies the response is an object containing a jobs array. If the parsed JSON is null, a primitive, or an object without a jobs array, it throws this error. It guards against the API's contract changing or an HTML/error page being returned in place of JSON.
Solutions
- Log the actual response body to confirm the new shape, then update the provider's parsing to match the current API contract.
- Check whether the endpoint URL is still the correct API route (an HTML 404 page can end up as non-conforming data).
- Add a fallback parse (e.g. accept {results:[...]}) or bump/reinstall the provider package if a newer version handles the new schema.
Example fix
// before
if (!data || typeof data !== 'object' || !Array.isArray(data.jobs)) throw new Error('remotli: unexpected API response');
// after
const rows = Array.isArray(data?.jobs) ? data.jobs : Array.isArray(data?.results) ? data.results : null;
if (!rows) throw new Error('remotli: unexpected API response'); Defensive patterns
Strategy: try-catch
Validate before calling
const res = await fetch(url); const data = await res.json();
const looksValid = data && typeof data === 'object' && Array.isArray(data.jobs);
if (!looksValid) console.warn('Unexpected remotli payload:', JSON.stringify(data).slice(0, 200)); Type guard
function isJobsPayload(d) { return !!d && typeof d === 'object' && Array.isArray(d.jobs); } Try / catch
try { rows = await provider.fetch(entry, ctx); } catch (e) { if (e.message.startsWith('remotli: unexpected API response')) { logRawResponseForDiff(); return null; } throw e; } Prevention
- Pin/monitor the upstream API version and watch for schema changes.
- Log raw response bodies on failure for fast diagnosis.
- Wrap fetchJson with a schema check before consuming fields.
When it happens
Trigger: ctx.fetchJson returns data that is null, a non-object (string/number), or an object whose .jobs is not an Array — e.g. the API returned {error:...}, an empty HTML page serialized oddly, or the endpoint version changed shape.
Common situations: remotli.ch API redesigned its response schema; hitting an HTML error/captcha page that got mis-parsed; querying a deprecated endpoint; or a proxy returning a JSON error body.
Related errors
- Existing candidates file at
- himalayas: unexpected API response - expected
- invalid golden case : need string id/jd and label.
- jobicy: unexpected API response — expected
- local parser JSON must be an array or contain…
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/2d0ccdc53e6d6c45.
Report an issue: GitHub.
Appendix: source
Thrown at providers/remotli.mjs:297
// or a page-1 body that isn't `{ jobs: [...] }` — means we cannot tell a
// live board from a broken one, so it must throw and surface as a dead
// target. Once one page has parsed, the board is provably reachable and a
// later transient failure must not discard what we already collected.
let succeededOnce = false;
for (let page = 1; page <= Math.min(cap, totalPages); page++) {
const url = `${ORIGIN}${API_PATH}?page=${page}&limit=${PAGE_SIZE}&${ALL_WORK_MODES}`;
assertRemotliUrl(url);
/** @type {any[]} */
let rows;
try {
// redirect:'error' prevents SSRF via server-side redirects; combined with
// assertRemotliUrl this pins every hop to remotli.ch.
const data = await ctx.fetchJson(url, { redirect: 'error' });
if (!data || typeof data !== 'object' || !Array.isArray(/** @type {any} */ (data).jobs)) {
throw new Error(
`remotli: unexpected API response — expected { jobs: [...] }, got ${data === null ? 'null' : typeof data}`,
);
}
rows = /** @type {any} */ (data).jobs;
const reported = Number(/** @type {any} */ (data).pagination?.totalPages);
if (Number.isInteger(reported) && reported > 0) totalPages = reported;
} catch (err) {
if (!succeededOnce) throw err;
break; // keep the pages already collected — a mid-scan blip isn't a dead board
}
// Set only after the shape check passed, so a malformed body never counts
// as proof of life.
succeededOnce = true;
for (const row of rows) {View on GitHub (pinned to aac998c7ed)