santifer/career-ops · error · Error
remoteok: unexpected API response — expected a JSON array, g
Error message
remoteok: unexpected API response — expected a JSON array, got ${data === null ? 'null' : typeof data} What it means
The remoteok provider fetches a single public feed (FEED_URL) and asserts the response is a JSON array. If ctx.fetchJson resolves to anything that is not an array — null, an object, a string, a number — it throws. This guards against partial outages, CDN error pages served as JSON, or upstream API contract changes that wrap the list in an envelope.
Source
Thrown at providers/remoteok.mjs:29
// private scanning, but don't redistribute this feed publicly without it.
const FEED_URL = 'https://remoteok.com/api';
/** @type {Provider} */
export default {
id: 'remoteok',
/**
* Fetches and normalizes postings from the RemoteOK 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(`remoteok: unexpected API response — expected a JSON array, got ${data === null ? 'null' : typeof data}`);
}
return data
.filter(j => j && typeof j === 'object'
&& typeof j.position === 'string' && j.position.trim() !== ''
&& typeof j.url === 'string' && /^https?:\/\//i.test(j.url.trim()))
.map(j => ({
title: j.position.trim(),
url: j.url.trim(),
company: typeof j.company === 'string' && j.company.trim() ? j.company.trim() : (entry.name || 'RemoteOK'),
location: typeof j.location === 'string' ? j.location.trim() : '',
}));
},
};
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Retry after a short delay — transient rate-limit or maintenance responses are often intermittent.
- Inspect the raw response: curl the FEED_URL directly to see the current shape.
- If RemoteOK permanently changed its format, update the parser to unwrap the new envelope before the Array.isArray check.
- Verify network egress is not being intercepted by a corporate proxy returning a JSON block page.
Example fix
// before
const data = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
if (!Array.isArray(data)) throw new Error(...);
// after — tolerate a wrapped envelope
const data = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
const list = Array.isArray(data) ? data : (Array.isArray(data?.jobs) ? data.jobs : null);
if (!list) throw new Error('remoteok: unexpected API response'); Defensive patterns
Strategy: type-guard
Validate before calling
// Probe the feed shape before relying on the provider's strict assertion
async function probeRemoteOk(ctx) {
const data = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
return Array.isArray(data);
}
if (!(await probeRemoteOk(ctx))) {
console.warn('remoteok: feed shape changed or endpoint down — skipping');
} Type guard
/** @param {unknown} d */
function isRemoteOkFeed(d) {
return Array.isArray(d) && d.every(j => j == null || (typeof j === 'object' && typeof j.position === 'string'));
} Try / catch
try {
await provider.fetch(entry, ctx);
} catch (e) {
if (/unexpected API response/.test(e.message)) {
// likely transient outage or rate limit — retry once after delay
await new Promise(r => setTimeout(r, 5000));
await provider.fetch(entry, ctx);
} else throw e;
} Prevention
- Treat shape errors as potentially transient — retry once before giving up.
- Monitor the feed endpoint for contract changes.
- Wrap provider calls so a single provider outage does not abort the whole scan.
When it happens
Trigger: The feed returns an error envelope like { error: '...' } or { data: [...] } instead of a bare array; the endpoint returns null; a rate-limit or maintenance page is parsed as a JSON object; the upstream changed its response shape.
Common situations: RemoteOK is temporarily rate-limiting and returns an error object; a proxy/gateway injected a wrapping object; the FEED_URL constant points at a stale or version-changed endpoint after an API revision.
Related errors
- remotive: unexpected API response — expected { jobs: [...] }
- remotli: unexpected API response — expected { jobs: [...] },
- echojobs: unexpected API response on page ${page} — expected
- Apify did not return a run id: ${JSON.stringify(body).slice(
- Gmail token refresh returned no access_token
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/288cd494e4fc9b1d.
Report an issue: GitHub.