santifer/career-ops · error · Error
workingnomads: unexpected API response — expected a JSON…
Error message
workingnomads: unexpected API response — expected a JSON array, got ${data === null ? 'null' : typeof data} What it means
The workingnomads provider fetches its fixed FEED_URL and requires the response to be a JSON array of jobs. When ctx.fetchJson returns null or any non-array value, fetch throws, treating the response as an unexpected API shape rather than silently returning zero results.
Solutions
- Retry the fetch; transient API errors often resolve on the next run
- Open FEED_URL directly and inspect the JSON shape for an API format change
- If the payload is now wrapped (e.g. {jobs:[...]}), update the provider to unwrap the array
- Check for rate limiting or auth requirements introduced by the API
Example fix
// before
const data = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
// after (unwrap if API changed)
let data = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
if (data && !Array.isArray(data) && Array.isArray(data.jobs)) data = data.jobs; Defensive patterns
Strategy: try-catch
Validate before calling
const data = await ctx.fetchJson(FEED_URL, { redirect: 'error' }).catch(() => null);
if (!Array.isArray(data)) throw new Error('workingnomads feed not an array; skipping run'); Type guard
const isJobArray = (d) => Array.isArray(d) && d.every(j => j && typeof j === 'object' && typeof j.title === 'string' && typeof j.url === 'string');
Try / catch
try { jobs = await provider.fetch(entry, ctx); } catch (e) { if (e.message.includes('unexpected API response')) { console.warn('Working Nomads API shape changed or is down; retrying next run'); jobs = []; } else throw e; } Prevention
- Check the API is reachable and returns an array before large batch runs
- Pin/monitor the upstream API for shape changes
- Treat empty/garbage responses as transient and schedule a retry rather than failing the whole pipeline
When it happens
Trigger: fetch(entry, ctx) where the Working Nomads API returns null (fetch failure surfaced as null), an error object, or a wrapped payload like { jobs: [...] } instead of a top-level array.
Common situations: API downtime returning an HTML/JSON error page parsed to null; an API version change that wraps the array in an object; a proxy or rate-limiter returning a non-array body.
Related errors
- remotli: unexpected API response — expected
- wttj: /api/env payload has no JSON object
- collage: unrecognized response envelope (expected a…
- Could not parse existing candidates file at
- each entry needs a string "input" and "output"
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/f8bebe6a6a1ce381.
Report an issue: GitHub.
Appendix: source
Thrown at providers/workingnomads.mjs:26
// Wire in via a `job_boards:` entry with `provider: workingnomads`.
const FEED_URL = 'https://www.workingnomads.com/api/exposed_jobs/';
/** @type {Provider} */
export default {
id: 'workingnomads',
/**
* Fetches and normalizes postings from the Working Nomads 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}>>}
*/
async fetch(entry, ctx) {
// redirect:'error' prevents SSRF via server-side redirects
const data = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
if (!Array.isArray(data)) {
throw new Error(`workingnomads: unexpected API response — expected a JSON array, got ${data === null ? 'null' : typeof data}`);
}
return data
.filter(j => j && typeof j === 'object'
&& typeof j.title === 'string' && j.title.trim() !== ''
&& typeof j.url === 'string' && /^https?:\/\//i.test(j.url.trim()))
.map(j => ({
title: j.title.trim(),
url: j.url.trim(),
company: typeof j.company_name === 'string' && j.company_name.trim() ? j.company_name.trim() : (entry.name || 'Working Nomads'),
location: typeof j.location === 'string' ? j.location.trim() : '',
}));
},
};
View on GitHub (pinned to aac998c7ed)