{"record":{"id":"695c7bfd620a9fc4","repo":"santifer/career-ops","slug":"getonbrd-unexpected-api-response-on-page-page","errorCode":null,"errorMessage":"getonbrd: unexpected API response on page ${page} — expected { data: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]","messagePattern":"getonbrd: unexpected API response on page (.+?) — expected (.+?), got keys: \\[(.+?)\\]","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/getonbrd.mjs","lineNumber":122,"sourceCode":"  return job;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'getonbrd',\n\n  async fetch(entry, ctx) {\n    assertGetonbrdUrl(FEED_BASE);\n    const maxPages = resolveMaxPages(entry);\n    const fallbackCompany = entry?.name;\n    const out = [];\n\n    for (let page = 1; page <= maxPages; page++) {\n      const url = `${FEED_BASE}?per_page=${PER_PAGE}&expand[]=company&page=${page}`;\n      // redirect:'error' prevents SSRF via server-side redirects\n      const json = await ctx.fetchJson(url, { redirect: 'error' });\n      if (!json || !Array.isArray(json.data)) {\n        throw new Error(\n          `getonbrd: unexpected API response on page ${page} — expected { data: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`,\n        );\n      }\n      for (const j of json.data) {\n        const normalized = normalizeGetonbrdJob(j, fallbackCompany);\n        if (normalized) out.push(normalized);\n      }\n      if (json.data.length < PER_PAGE) break; // short page → last page reached\n    }\n    return out;\n  },\n};\n","sourceCodeStart":104,"sourceCodeEnd":135,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/getonbrd.mjs#L104-L135","documentation":"getonbrd.mjs throws this in fetch() when the JSON body for a page is null/falsy or json.data is not an Array. The Get on Board JSON:API contract is { data: [...resources], meta: {...} }, so a missing or reshaped data array means the response is not a valid job feed page. The message includes the actual top-level keys (or 'null') to aid diagnosis.","triggerScenarios":"The API returned an error envelope ({ message, errors }) with no data; a maintenance/HTML page was parsed into a non-standard object; the endpoint was versioned and moved jobs under a different key; a rate-limit response returned JSON without data; the body was empty (null json).","commonSituations":"Get on Board ships an API version bump that reshapes the envelope; the board is temporarily unavailable and returns an error object; a WAF/Cloudflare challenge returns interstitial JSON; invalid query params (e.g. an unknown expand) triggered an error response.","solutions":["curl the failing page URL (https://www.getonbrd.com/api/v0/categories/programming/jobs?per_page=100&expand[]=company&page=N) and inspect the keys named in the message.","If it is an error/maintenance envelope, retry shortly — usually transient.","If the shape changed permanently, update the json.data extraction and the short-page break logic in getonbrd.mjs.","Confirm expand[]=company and per_page=100 still match the documented API; an API tightening may reject the current query string."],"exampleFix":"// before\nif (!json || !Array.isArray(json.data)) { throw new Error(`getonbrd: unexpected API response ...`); }\n\n// after (tolerate a renamed key, e.g. 'results')\nconst rows = Array.isArray(json?.data) ? json.data : Array.isArray(json?.results) ? json.results : null;\nif (!rows) { throw new Error(`getonbrd: unexpected API response ...`); }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"// Narrow a Get on Board response to the expected { data: [] } shape.\nfunction isGetonbrdPage(json) {\n  return !!json && typeof json === 'object' && Array.isArray(json.data);\n}","tryCatchPattern":"// On non-first pages, treat a shape anomaly as end-of-feed instead of fatal.\ntry {\n  const json = await ctx.fetchJson(url, { redirect: 'error' });\n  if (!isGetonbrdPage(json)) {\n    if (page === 1) throw new Error('getonbrd: unexpected API response on page 1');\n    break;\n  }\n} catch (err) {\n  if (page === 1) throw err;\n  console.error(`getonbrd: page ${page} failed — ${err.message}`);\n  break;\n}","preventionTips":["Log the top-level keys on shape failures for fast diagnosis.","Retry once on transient maintenance/error envelopes before surfacing.","Track Get on Board API announcements for envelope changes."],"tags":["api-response-shape","runtime","getonbrd","third-party-api"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}