santifer/career-ops · error · Error

remoteok: unexpected API response — expected a JSON array…

Error message

remoteok: unexpected API response — expected a JSON array, got ${data === null ? 'null' : typeof data}

What it means

RemoteOK's public feed (FEED_URL) is expected to return a top-level JSON array of job objects. fetch() throws this error when the parsed response is not an array (null, object, string, etc.), treating it as an unexpected API change or an error page that still parsed as JSON.

Solutions

  1. Retry after a delay — transient rate-limit or CDN responses are the usual cause
  2. Inspect the raw response from the RemoteOK feed URL to see the actual payload
  3. Check whether RemoteOK changed its API shape and update the provider accordingly
  4. Reduce scan frequency to avoid triggering rate limiting

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

function isArrayResponse(v) { return Array.isArray(v); }

Try / catch

try {
  const jobs = await remoteok.fetch(entry, ctx);
} catch (err) {
  if (err.message.startsWith('remoteok: unexpected API response')) {
    console.warn('RemoteOK feed returned a non-array payload — likely rate-limited or schema change; retrying later');
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: The RemoteOK API returns an HTML error/rate-limit page that fetchJson parses oddly, a JSON object wrapper, null body, or any non-array payload; API schema changes or Cloudflare interstitials.

Common situations: RemoteOK rate-limits or temporarily takes the feed down; a CDN returns a JSON error object; RemoteOK changes its response envelope from an array to an object.

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


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/288cd494e4fc9b1d. Report an issue: GitHub.

Appendix: 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 aac998c7ed)