santifer/career-ops · error · Error
landingjobs: unexpected API response — expected a JSON…
Error message
landingjobs: unexpected API response — expected a JSON array, got ${json === null ? 'null' : typeof json} What it means
The landingjobs provider's fetch() calls ctx.fetchJson on the pinned FEED_URL and then asserts the payload is a JSON array of jobs. If the API returns a JSON object, a string, a number, or null instead, the provider throws this error rather than crash later inside .map. It signals an unexpected API contract change or a wrong/intercepted response.
Solutions
- Inspect the actual response body (curl the FEED_URL) to see what shape the API now returns.
- Update the provider (or pin the correct FEED_URL) if LandingJobs changed the response contract — e.g. unwrap the array from a new envelope field.
- Check for a proxy/firewall/DNS interception returning an error page with 200.
- Retry later if it's a transient upstream issue; otherwise file a bug with the response shape observed.
Example fix
// before
return json.map(j => normalizeLandingJob(j, fallbackCompany)).filter(Boolean);
// after
const jobs = Array.isArray(json) ? json : (json.jobs ?? []);
if (!Array.isArray(jobs)) throw new Error('landingjobs: no job array in response');
return jobs.map(j => normalizeLandingJob(j, fallbackCompany)).filter(Boolean); Defensive patterns
Strategy: type-guard
Validate before calling
const json = await res.json();
if (!Array.isArray(json)) throw new Error(`Unexpected feed shape: ${JSON.stringify(json).slice(0, 200)}`); Type guard
function isJobArray(v) { return Array.isArray(v) && v.every(j => j && typeof j === 'object'); } Try / catch
try {
return await provider.fetch(entry, ctx);
} catch (e) {
if (e.message.startsWith('landingjobs: unexpected API response')) {
console.warn('LandingJobs feed shape changed or returned an error envelope; skipping');
return [];
}
throw e;
} Prevention
- Snapshot the feed schema in tests to catch upstream contract changes
- Check status code and content-type before trusting parsed JSON
- Log a truncated body when the shape check fails for debugging
- Degrade per-provider so a broken feed doesn't fail the whole scan
When it happens
Trigger: The feed endpoint returns 200 with a JSON object (e.g. {error: ...}, an error envelope, or a paginated object instead of a bare array), or fetchJson was pointed at a non-API page that parses as non-array JSON.
Common situations: LandingJobs changes their API response shape (breaking change), a captive portal / proxy returns an HTML or JSON error body with a 200 status, or the FEED_URL constant is stale after an API migration.
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
- API error
- collage: unrecognized response envelope (expected a…
- garena: unexpected API response — expected jobs[], got keys
- gem: JobBoardList failed
- himalayas: unexpected API response - expected
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/986c998ff0290920.
Report an issue: GitHub.
Appendix: source
Thrown at providers/landingjobs.mjs:125
const location = [base, j.remote === true ? 'Remote' : ''].filter(Boolean).join(', ');
/** @type {{ title: string, url: string, company: string, location: string, postedAt?: number }} */
const job = { title, url, company, location };
const postedAt = toEpochMs(j.published_at) ?? toEpochMs(j.created_at);
if (postedAt !== undefined) job.postedAt = postedAt;
return job;
}
/** @type {Provider} */
export default {
id: 'landingjobs',
async fetch(entry, ctx) {
assertLandingUrl(FEED_URL);
// redirect:'error' prevents SSRF via server-side redirects
const json = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
if (!Array.isArray(json)) {
throw new Error(
`landingjobs: unexpected API response — expected a JSON array, got ${json === null ? 'null' : typeof json}`,
);
}
const fallbackCompany = entry?.name;
return json.map(j => normalizeLandingJob(j, fallbackCompany)).filter(Boolean);
},
};
View on GitHub (pinned to aac998c7ed)