santifer/career-ops · error · Error
jobicy: unexpected API response — expected
Error message
jobicy: unexpected API response — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}] What it means
The Jobicy provider fetches https://jobicy.com/api/v2/remote-jobs and expects a JSON object with a jobs array ({ jobs: [...] }). If the parsed JSON is null/undefined or lacks an array at .jobs, fetch() throws, including the actual top-level keys seen (or 'null') to expose what came back instead. This is a defensive shape check against API changes, error pages, and HTML-as-JSON responses.
Solutions
- Check the printed keys list in the message — it tells you what the API actually returned
- Verify the Jobicy v2 API is up and the contract still returns { jobs: [...] } (curl https://jobicy.com/api/v2/remote-jobs?count=50)
- If rate-limited, back off and retry later with a lower count parameter
- Check for proxy/captive-portal interference returning HTML instead of JSON
- If the API schema changed, update the provider's parseJobicyResponse to the new shape
Example fix
// before: assuming the shape blindly
const jobs = json.jobs.map(parseJob);
// after: guarding before use
if (!json || !Array.isArray(json.jobs)) {
throw new Error(`jobicy: unexpected API response, keys: ${json ? Object.keys(json) : 'null'}`);
}
const jobs = json.jobs.map(parseJob); Defensive patterns
Strategy: try-catch
Validate before calling
function looksLikeJobicyPayload(json) {
return !!json && typeof json === 'object' && Array.isArray(json.jobs);
} Type guard
function isJobicyResponse(v) {
return typeof v === 'object' && v !== null && Array.isArray(v.jobs);
} Try / catch
try {
const json = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
if (!isJobicyResponse(json)) {
console.warn('Jobicy returned unexpected payload; skipping board this run');
return [];
}
} catch (e) {
console.warn(`Jobicy fetch failed: ${e.message}`);
return [];
} Prevention
- Pin/monitor the Jobicy v2 API contract; subscribe to their changelog
- Back off on rate-limit responses before retrying
- Log the full payload (keys at minimum) when the shape check fails
- Check for proxies returning HTML error pages instead of JSON
When it happens
Trigger: ctx.fetchJson(FEED_URL, { redirect: 'error' }) resolves with something whose shape is wrong: Jobicy returned an error object ({error:...}), an empty body parsed to null, an HTML error page, an API version change renaming jobs, or a rate-limit JSON body.
Common situations: Jobicy API downtime or maintenance returning error payloads; a rate-limit response; the API contract changing (v2 schema update); a captive portal / proxy returning HTML; redirect:'error' surfaces upstream differently but a 200-with-error-body still reaches this check.
Related errors
- collage: unrecognized response envelope (expected a…
- garena: unexpected API response — expected jobs[], got keys
- manfred: unexpected API response — expected a JSON array of…
- 4dayweek: unexpected API response on page
- a16z-speedrun-talent: unexpected API response on page
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/cd592334128b8b9b.
Report an issue: GitHub.
Appendix: source
Thrown at providers/jobicy.mjs:29
/** @type {Provider} */
export default {
id: 'jobicy',
detect(entry) {
return entry?.provider === 'jobicy' ? { url: FEED_URL } : null;
},
/**
* Fetches and normalizes postings from the Jobicy public feed.
* @param {{ name?: 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) {
// redirect:'error' prevents SSRF via server-side redirects
const json = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
if (!json || !Array.isArray(json.jobs)) {
throw new Error(`jobicy: unexpected API response — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);
}
return parseJobicyResponse(json, entry.name || 'Jobicy');
},
};
/**
* Parse a Jobicy API response. Exported for unit tests.
*
* @param {any} json - Raw response payload.
* @param {string} defaultCompany - Fallback company name.
* @returns {Array<{title: string, url: string, company: string, location: string}>}
*/
export function parseJobicyResponse(json, defaultCompany = 'Jobicy') {
if (!json || !Array.isArray(json.jobs)) return [];
const toEpochMs = (value) => {
if (!value) return undefined;View on GitHub (pinned to aac998c7ed)