santifer/career-ops · error · Error
himalayas: unexpected API response - expected
Error message
himalayas: unexpected API response - expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}] What it means
fetch() pulls the Himalayas jobs feed via ctx.fetchJson and requires the JSON body to be an object with a `jobs` array. When the parsed response is null or lacks a jobs array, it throws this error listing the top-level keys it actually received, so the developer can see what shape came back.
Solutions
- Fetch the FEED_URL manually and inspect the actual response shape to confirm the schema changed
- Update the check and parseHimalayasResponse to match the new feed envelope (e.g. { data: { jobs: [...] } })
- Check for rate limiting or auth errors in the returned keys and address the upstream cause
- Pin/report the provider breakage upstream and skip the himalayas board until fixed
Example fix
// before if (!json || !Array.isArray(json.jobs)) throw new Error(...); // after const jobs = json?.jobs ?? json?.data?.jobs; if (!Array.isArray(jobs)) throw new Error(...);
Defensive patterns
Strategy: type-guard
Validate before calling
function looksLikeHimalayasFeed(json) {
return !!json && typeof json === 'object' && !Array.isArray(json) && Array.isArray(json.jobs);
}
// guard the raw text before parse too: contentType includes 'application/json' Type guard
const isFeedShape = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && Array.isArray(v.jobs);
Try / catch
try {
const jobs = await himalayasProvider.fetch(entry, ctx);
} catch (err) {
if (/unexpected API response/.test(err.message)) {
console.error('himalayas feed schema changed or returned an error body:', err.message);
return []; // skip board, alert maintainer
}
throw err;
} Prevention
- Check fetchJson content-type/status before trusting the parsed body
- Pin a schema test against a saved feed snapshot in CI
- Log the received top-level keys on mismatch for quick diagnosis
- Watch for provider API changelog/deprecation notices
When it happens
Trigger: ctx.fetchJson returns null, an array, or an object without `jobs` (e.g. { error: '...' }, { message: 'rate limited' }), or the feed endpoint changed its envelope from { jobs: [...] } to something else.
Common situations: Himalayas changes or deprecates its public feed schema; a captive portal / HTML error page was parsed leniently; a proxy returns an auth-error JSON body; the feed URL was updated to a v2 API with a different envelope.
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
- ibm: unexpected API response — expected hits.hits[], got…
- All active models failed. Last error
- arbeitsagentur: all keyword request(s) failed
- archive render skipped — Greenhouse API unreachable
- HTTP
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/d5df9b34dca5ac38.
Report an issue: GitHub.
Appendix: source
Thrown at providers/himalayas.mjs:89
id: 'himalayas',
detect(entry) {
return entry?.provider === 'himalayas' ? { url: FEED_URL } : null;
},
/**
* Fetches and normalizes postings from the Himalayas public feed.
* @param {{ provider?: string }} entry - The job_boards entry being processed.
* @param {{ fetchJson: (url: string, opts?: { redirect?: 'error'|'follow'|'manual' }) => Promise<any> }} ctx - HTTP context.
* @returns {Promise<Array<{title: string, url: string, company: string, location: string, postedAt?: number}>>}
*/
async fetch(entry, ctx) {
const feedUrl = assertHimalayasUrl(FEED_URL);
// redirect:'error' prevents SSRF via server-side redirects; combined with
// assertHimalayasUrl above it keeps the request pinned to himalayas.app.
const json = await ctx.fetchJson(feedUrl, { redirect: 'error' });
if (!json || !Array.isArray(json.jobs)) {
throw new Error(`himalayas: unexpected API response - expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);
}
return parseHimalayasResponse(json);
},
};
/**
* Parse Himalayas' public jobs API response. Exported for unit tests.
*
* Shape: `{ jobs: [...] }`, where each job currently carries `title`,
* `companyName`, `locationRestrictions`, `applicationLink`, `guid`,
* `pubDate`, and `companySlug`. `applicationLink` is preferred over `guid`
* and used as the dedup key after HTTPS + host validation.
*
* @param {unknown} json - raw parsed API response
* @returns {Array<{title: string, url: string, company: string, location: string, postedAt?: number}>}
*/
export function parseHimalayasResponse(json) {
if (!json || typeof json !== 'object' || !Array.isArray(json.jobs)) return [];View on GitHub (pinned to aac998c7ed)