santifer/career-ops · error · Error

getonbrd: unexpected API response for category "${category}"

Error message

getonbrd: unexpected API response for category "${category}" on page ${page} — expected { data: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]

What it means

The getonbrd provider fetch loop calls fetchJson on the Getonbrd API per category/page and expects a JSON envelope of shape { data: [...] }. If the response is null, not an object, or lacks an array `data`, the provider throws this error listing the keys actually received. It signals the API contract changed, the endpoint returned an error payload, or a non-JSON/empty body was parsed.

Source

Thrown at providers/getonbrd.mjs:182

  async fetch(entry, ctx) {
    const categories = resolveCategories(entry);
    const maxPages = resolveMaxPages(entry);
    const fallbackCompany = entry?.name;
    const out = [];
    // A posting can appear under several categories; first sighting wins so the
    // scanner never sees the same URL twice from one entry.
    const seen = new Set();

    for (const category of categories) {
      const base = assertGetonbrdUrl(feedBase(category));

      for (let page = 1; page <= maxPages; page++) {
        const url = `${base}?per_page=${PER_PAGE}&expand[]=company&page=${page}`;
        // redirect:'error' prevents SSRF via server-side redirects
        const json = await ctx.fetchJson(url, { redirect: 'error' });
        if (!json || !Array.isArray(json.data)) {
          throw new Error(
            `getonbrd: unexpected API response for category "${category}" on page ${page} — expected { data: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`,
          );
        }
        for (const j of json.data) {
          const normalized = normalizeGetonbrdJob(j, fallbackCompany);
          if (!normalized || seen.has(normalized.url)) continue;
          seen.add(normalized.url);
          out.push(normalized);
        }
        if (json.data.length < PER_PAGE) break; // short page → last page reached
      }
    }
    return out;
  },
};

View on GitHub (pinned to 1696bec4d0)

Solutions

  1. Read the 'got keys' list in the message — it tells you the actual response shape and usually identifies the new envelope key.
  2. Verify the category slug is valid by opening the API URL manually in a browser/curl.
  3. Check whether Getonbrd changed its API response format and update normalize/fetch code accordingly.
  4. Retry after a delay if a transient server issue is suspected; add rate-limit backoff if calls are too aggressive.
  5. Confirm network middleware isn't returning an HTML error page or empty body (proxy, VPN, corporate firewall).

Example fix

// before
const json = await ctx.fetchJson(url, { redirect: 'error' });
if (!json || !Array.isArray(json.data)) throw new Error(`...`);
// after
const json = await ctx.fetchJson(url, { redirect: 'error' });
const items = json?.data ?? json?.jobs ?? json?.results;
if (!Array.isArray(items)) throw new Error(`getonbrd: unexpected response keys: ${Object.keys(json || {}).join(',')}`);
Defensive patterns

Strategy: type-guard

Validate before calling

const res = await fetch(url);
const json = await res.json();
if (res.ok && json && typeof json === 'object' && Array.isArray(json.data)) {
  // safe to proceed
}

Type guard

function isGetonbrdPage(json) {
  return typeof json === 'object' && json !== null && Array.isArray(json.data);
}

Try / catch

try {
  const json = await ctx.fetchJson(url, { redirect: 'error' });
  if (!isGetonbrdPage(json)) {
    console.warn(`getonbrd: non-standard response for ${category} page ${page}:`, Object.keys(json || {}));
    break; // stop paging, keep earlier pages' results
  }
} catch (e) {
  if (e.message.includes('unexpected API response')) {
    await sleep(retryDelayMs);
    continue; // retry once for transient issues
  }
  throw e;
}

Prevention

When it happens

Trigger: fetchJson returns null (empty/204 body), an error object like { error: '...' }, a paginated envelope with a different key (e.g. { jobs: [...] }), or an HTML error page that fails to parse upstream — for any page within 1..maxPages of a category fetch.

Common situations: Getonbrd API version change relocating the array; rate-limit or auth responses shaped differently; category slug no longer exists so the API returns an error payload; transient 5xx rendered as a non-standard body; `expand[]=company` parameter change.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01). Data as JSON: /api/errors/d4a22be8ece7f88c. Report an issue: GitHub.