santifer/career-ops · error · Error

remotli: unexpected API response — expected { jobs: [...] },

Error message

remotli: unexpected API response — expected { jobs: [...] }, got ${data === null ? 'null' : typeof data}

What it means

During pagination, remotli fetches each page via ctx.fetchJson and asserts the response is an object with a jobs array. It throws on the first page (page 1) if the shape is wrong; on later pages, if succeededOnce is true, a shape failure breaks the loop (keeping already-collected rows) rather than throwing. So this error only surfaces when the very first request returns a non-conformant body.

Source

Thrown at providers/remotli.mjs:296

    // or a page-1 body that isn't `{ jobs: [...] }` — means we cannot tell a
    // live board from a broken one, so it must throw and surface as a dead
    // target. Once one page has parsed, the board is provably reachable and a
    // later transient failure must not discard what we already collected.
    let succeededOnce = false;

    for (let page = 1; page <= Math.min(cap, totalPages); page++) {
      const url = `${ORIGIN}${API_PATH}?page=${page}&limit=${PAGE_SIZE}&${ALL_WORK_MODES}`;
      assertRemotliUrl(url);

      /** @type {any[]} */
      let rows;
      try {
        // redirect:'error' prevents SSRF via server-side redirects; combined with
        // assertRemotliUrl this pins every hop to remotli.ch.
        const data = await ctx.fetchJson(url, { redirect: 'error' });

        if (!data || typeof data !== 'object' || !Array.isArray(/** @type {any} */ (data).jobs)) {
          throw new Error(
            `remotli: unexpected API response — expected { jobs: [...] }, got ${data === null ? 'null' : typeof data}`,
          );
        }

        rows = /** @type {any} */ (data).jobs;

        const reported = Number(/** @type {any} */ (data).pagination?.totalPages);
        if (Number.isInteger(reported) && reported > 0) totalPages = reported;
      } catch (err) {
        if (!succeededOnce) throw err;
        break; // keep the pages already collected — a mid-scan blip isn't a dead board
      }

      // Set only after the shape check passed, so a malformed body never counts
      // as proof of life.
      succeededOnce = true;

      for (const row of rows) {

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Retry the scan — transient first-page failures clear on the next run.
  2. curl the constructed first-page URL to inspect the actual response body and shape.
  3. Verify API_PATH, PAGE_SIZE, and ALL_WORK_MODES query parameters still match the live endpoint.
  4. If the API permanently changed, update the shape check to match the new envelope.

Example fix

// before — throws on first page if shape mismatches
if (!data || typeof data !== 'object' || !Array.isArray(data.jobs)) throw new Error(...);
// after — log and retry once before giving up
if (!data || typeof data !== 'object' || !Array.isArray(data.jobs)) {
  console.error('remotli: bad first-page shape', JSON.stringify(data).slice(0, 200));
  throw new Error('remotli: unexpected API response');
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight the first page shape before committing to the full scan
async function probeRemotliFirstPage(ctx) {
  const url = `${ORIGIN}${API_PATH}?page=1&limit=${PAGE_SIZE}&${ALL_WORK_MODES}`;
  const data = await ctx.fetchJson(url, { redirect: 'error' });
  return !!data && typeof data === 'object' && Array.isArray(data.jobs);
}
if (!(await probeRemotliFirstPage(ctx))) {
  console.warn('remotli: first-page shape invalid — API down or changed');
}

Type guard

/** @param {unknown} d */
function isRemotliPage(d) {
  return !!d && typeof d === 'object' && Array.isArray(/** @type{any}*/(d).jobs);
}

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (e) {
  if (/unexpected API response/.test(e.message)) {
    // only the first page throws; retry once after a delay
    await new Promise(r => setTimeout(r, 5000));
    await provider.fetch(entry, ctx);
  } else throw e;
}

Prevention

When it happens

Trigger: The first-page API response is null, a primitive, or an object lacking a jobs array (e.g. an error envelope { error, message }, a maintenance page, or an HTML-converted-to-JSON blob). Only fires when succeededOnce is still false.

Common situations: remotli.ch API is down or rate-limiting on the initial request; a proxy returned a JSON error block; the API_PATH constant changed and the first page 404'd into an error object; the response encoding changed.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/2d0ccdc53e6d6c45. Report an issue: GitHub.