santifer/career-ops · error · Error

landingjobs: unexpected API response — expected a JSON…

Error message

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

What it means

The landingjobs provider's fetch() calls ctx.fetchJson on the pinned FEED_URL and then asserts the payload is a JSON array of jobs. If the API returns a JSON object, a string, a number, or null instead, the provider throws this error rather than crash later inside .map. It signals an unexpected API contract change or a wrong/intercepted response.

Solutions

  1. Inspect the actual response body (curl the FEED_URL) to see what shape the API now returns.
  2. Update the provider (or pin the correct FEED_URL) if LandingJobs changed the response contract — e.g. unwrap the array from a new envelope field.
  3. Check for a proxy/firewall/DNS interception returning an error page with 200.
  4. Retry later if it's a transient upstream issue; otherwise file a bug with the response shape observed.

Example fix

// before
return json.map(j => normalizeLandingJob(j, fallbackCompany)).filter(Boolean);
// after
const jobs = Array.isArray(json) ? json : (json.jobs ?? []);
if (!Array.isArray(jobs)) throw new Error('landingjobs: no job array in response');
return jobs.map(j => normalizeLandingJob(j, fallbackCompany)).filter(Boolean);
Defensive patterns

Strategy: type-guard

Validate before calling

const json = await res.json();
if (!Array.isArray(json)) throw new Error(`Unexpected feed shape: ${JSON.stringify(json).slice(0, 200)}`);

Type guard

function isJobArray(v) { return Array.isArray(v) && v.every(j => j && typeof j === 'object'); }

Try / catch

try {
  return await provider.fetch(entry, ctx);
} catch (e) {
  if (e.message.startsWith('landingjobs: unexpected API response')) {
    console.warn('LandingJobs feed shape changed or returned an error envelope; skipping');
    return [];
  }
  throw e;
}

Prevention

When it happens

Trigger: The feed endpoint returns 200 with a JSON object (e.g. {error: ...}, an error envelope, or a paginated object instead of a bare array), or fetchJson was pointed at a non-API page that parses as non-array JSON.

Common situations: LandingJobs changes their API response shape (breaking change), a captive portal / proxy returns an HTML or JSON error body with a 200 status, or the FEED_URL constant is stale after an API migration.

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/986c998ff0290920. Report an issue: GitHub.

Appendix: source

Thrown at providers/landingjobs.mjs:125

  const location = [base, j.remote === true ? 'Remote' : ''].filter(Boolean).join(', ');

  /** @type {{ title: string, url: string, company: string, location: string, postedAt?: number }} */
  const job = { title, url, company, location };
  const postedAt = toEpochMs(j.published_at) ?? toEpochMs(j.created_at);
  if (postedAt !== undefined) job.postedAt = postedAt;
  return job;
}

/** @type {Provider} */
export default {
  id: 'landingjobs',

  async fetch(entry, ctx) {
    assertLandingUrl(FEED_URL);
    // redirect:'error' prevents SSRF via server-side redirects
    const json = await ctx.fetchJson(FEED_URL, { redirect: 'error' });
    if (!Array.isArray(json)) {
      throw new Error(
        `landingjobs: unexpected API response — expected a JSON array, got ${json === null ? 'null' : typeof json}`,
      );
    }
    const fallbackCompany = entry?.name;
    return json.map(j => normalizeLandingJob(j, fallbackCompany)).filter(Boolean);
  },
};

View on GitHub (pinned to aac998c7ed)