{"record":{"id":"288cd494e4fc9b1d","repo":"santifer/career-ops","slug":"remoteok-unexpected-api-response-expected-a-jso","errorCode":null,"errorMessage":"remoteok: unexpected API response — expected a JSON array, got ${data === null ? 'null' : typeof data}","messagePattern":"remoteok: unexpected API response — expected a JSON array, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/remoteok.mjs","lineNumber":29,"sourceCode":"// private scanning, but don't redistribute this feed publicly without it.\n\nconst FEED_URL = 'https://remoteok.com/api';\n\n/** @type {Provider} */\nexport default {\n  id: 'remoteok',\n\n  /**\n   * Fetches and normalizes postings from the RemoteOK public feed.\n   * @param {{ name?: string }} entry - The job_boards entry being processed.\n   * @param {{ fetchJson: (url: string, opts?: { redirect?: 'error'|'follow'|'manual' }) => Promise<any> }} ctx - HTTP context.\n   * @returns {Promise<Array<{title: string, url: string, company: string, location: string}>>}\n   */\n  async fetch(entry, ctx) {\n    // redirect:'error' prevents SSRF via server-side redirects\n    const data = await ctx.fetchJson(FEED_URL, { redirect: 'error' });\n    if (!Array.isArray(data)) {\n      throw new Error(`remoteok: unexpected API response — expected a JSON array, got ${data === null ? 'null' : typeof data}`);\n    }\n\n    return data\n      .filter(j => j && typeof j === 'object'\n        && typeof j.position === 'string' && j.position.trim() !== ''\n        && typeof j.url === 'string' && /^https?:\\/\\//i.test(j.url.trim()))\n      .map(j => ({\n        title: j.position.trim(),\n        url: j.url.trim(),\n        company: typeof j.company === 'string' && j.company.trim() ? j.company.trim() : (entry.name || 'RemoteOK'),\n        location: typeof j.location === 'string' ? j.location.trim() : '',\n      }));\n  },\n};\n","sourceCodeStart":11,"sourceCodeEnd":44,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/remoteok.mjs#L11-L44","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry after a delay — transient rate-limit or CDN responses are the usual cause","Inspect the raw response from the RemoteOK feed URL to see the actual payload","Check whether RemoteOK changed its API shape and update the provider accordingly","Reduce scan frequency to avoid triggering rate limiting"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"function isArrayResponse(v) { return Array.isArray(v); }","tryCatchPattern":"try {\n  const jobs = await remoteok.fetch(entry, ctx);\n} catch (err) {\n  if (err.message.startsWith('remoteok: unexpected API response')) {\n    console.warn('RemoteOK feed returned a non-array payload — likely rate-limited or schema change; retrying later');\n    return [];\n  }\n  throw err;\n}","preventionTips":["Space out scans to avoid RemoteOK rate limiting","Pin/monitor the feed shape; RemoteOK occasionally changes its response envelope","Fallback to another aggregator provider when RemoteOK is down"],"tags":["network","api","schema-validation"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}