santifer/career-ops · error · Error
solidjobs: unexpected API response — expected { jobs: [...]
Error message
solidjobs: unexpected API response — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}] What it means
After fetching, fetch verifies the JSON is an object with an Array field 'jobs'. If json is null/undefined/non-object or json.jobs is not an array, it throws and lists the actual top-level keys to aid diagnosis. This guards the .filter/.map pipeline downstream from a shape that does not match the documented { jobs: [...] } contract.
Source
Thrown at providers/solidjobs.mjs:67
return { url };
} catch {}
return null;
},
/**
* Fetches and normalizes job offers from the SolidJobs public API.
* * @param {{ careers_url?: string, name: string }} entry - The configuration 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}>>} Array of parsed job offers.
*/
async fetch(entry, ctx) {
const url = entry.careers_url;
if (!url) throw new Error('solidjobs: careers_url required');
assertUrl(url);
// redirect:'error' prevents SSRF via server-side redirects
const json = await ctx.fetchJson(url, { redirect: 'error' });
if (!json || !Array.isArray(json.jobs)) {
throw new Error(`solidjobs: unexpected API response — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);
}
/** @type {Array<{ title?: string, url?: string, company?: string, locations?: string | string[] }>} */
const jobs = json.jobs;
return jobs
.filter(j => j && typeof j === 'object' && typeof j.url === 'string' && j.url.trim() !== '')
.map(j => ({
title: j.title || '',
url: /** @type {string} */ (j.url || '').trim(),
company: j.company || entry.name,
location: Array.isArray(j.locations) ? j.locations.join(', ') : (typeof j.locations === 'string' ? j.locations : ''),
}));
},
};
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Open the careers_url in a browser and inspect the actual JSON — compare top-level keys to the error message
- Confirm the division in the URL is still valid (it/engineering/marketing/sales/hr/logistics/finances/other)
- Retry later if the API is in maintenance
- If the contract changed, update the parser in providers/solidjobs.mjs to the new shape
Defensive patterns
Strategy: try-catch
Type guard
/** @param {unknown} json @returns {json is { jobs: any[] }} */
function isSolidJobsResponse(json) {
return !!json && typeof json === 'object' && Array.isArray(json.jobs);
} Try / catch
try {
const jobs = await provider.fetch(entry, ctx);
} catch (err) {
if (/solidjobs: unexpected API response/.test(err.message)) {
console.warn(`${entry.name}: SolidJobs API shape changed or is down — skipping`);
return [];
}
throw err;
} Prevention
- Treat API-shape errors as transient: log and skip rather than aborting a batch scan.
- Monitor the SolidJobs API for contract changes after version bumps.
- Confirm redirect:'error' isn't turning a redirect into a null body.
When it happens
Trigger: The SolidJobs API returned an error envelope (e.g. { error: '...' }), an HTML maintenance page was parsed as non-JSON, the endpoint moved and now returns a different shape, or the network layer returned null. A redirect that redirect:'error' blocked surfaces here too if ctx.fetchJson resolves null on failure.
Common situations: API outage/maintenance window, a version bump that changed the response shape, an expired campaign query param, or hitting a rate-limit/CAPTCHA page.
Related errors
- echojobs: unexpected API response on page ${page} — expected
- thehub: unexpected API response on page ${page} — expected {
- themuse: unexpected API response on page ${page} — expected
- landingjobs: unexpected API response — expected a JSON array
- local parser JSON must be an array or contain jobs[]/results
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/f3a70f6a8c0ce409.
Report an issue: GitHub.