santifer/career-ops · error · Error
agentic-jobs: unexpected API response shape on page ${page}
Error message
agentic-jobs: unexpected API response shape on page ${page} — "data" is missing or not an array What it means
The agentic-jobs API is expected to return `{ data: [...], meta: {...} }` per page. After fetchJson, the provider checks json.data is an array; if json is null or json.data is missing/non-array, it throws naming the page. The comment is explicit: an empty page legitimately returns `data: []` (which passes this guard), so a missing/non-array `data` is specifically a response-shape change, not an empty result — fail loudly rather than silently truncating the pages already collected.
Source
Thrown at providers/agentic-jobs.mjs:171
detect(entry) {
return entry?.provider === 'agentic-jobs' ? { url: SITE_ORIGIN } : null;
},
async fetch(_entry, ctx) {
const wait = (ms) => (ctx.sleep ? ctx.sleep(ms) : new Promise((r) => setTimeout(r, ms)));
const jobs = [];
const seen = new Set();
let total = null;
for (let page = 1; page <= MAX_PAGES; page++) {
if (page > 1) await wait(PAGE_DELAY_MS);
const url = assertAgenticUrl(`${API_BASE}/jobs?page=${page}`);
const json = await ctx.fetchJson(url, { redirect: 'error', headers: { accept: 'application/json' } });
// A missing/non-array `data` is a response-shape change, not a legitimate
// empty page (the API returns `data: []` for that) — fail loudly instead
// of silently truncating whatever pages were already collected.
if (!json || !Array.isArray(json.data)) {
throw new Error(`agentic-jobs: unexpected API response shape on page ${page} — "data" is missing or not an array`);
}
const records = json.data;
if (total === null) total = typeof json.meta?.total === 'number' ? json.meta.total : null;
// Trust the API's own reported page size over our constant, in case it
// ever differs from the documented default.
const effectivePageSize = typeof json.meta?.per_page === 'number' && json.meta.per_page > 0 ? json.meta.per_page : PAGE_SIZE;
for (const record of records) {
const job = normalizeAgenticJob(record);
if (job && !seen.has(job.url)) {
seen.add(job.url);
jobs.push(job);
}
}
if (jobs.length >= MAX_JOBS) break;
if (records.length < effectivePageSize) break; // short page — last one
if (total !== null && page * effectivePageSize >= total) break;View on GitHub (pinned to 9b17a8ac97)
Solutions
- Read the error — it states page N and that 'data' is missing/not-array. Reproduce with `curl -H 'accept: application/json' '<API_BASE>/jobs?page=1'` to see the actual body.
- If upstream renamed `data`, update the check and normalizeAgenticJob in providers/agentic-jobs.mjs to the new key.
- If it is an error envelope (429/5xx body), the provider does NOT retry agentic-jobs (unlike a16z) — retry manually after the rate window (the API allows 30 req/60s; PAGE_DELAY_MS=2100ms stays under it).
- If fetchJson returned null, investigate the network/status content-type.
Example fix
// If upstream renamed `data` → `results`:
// before
if (!json || !Array.isArray(json.data)) { throw ... }
const records = json.data;
// after
if (!json || !Array.isArray(json.results)) { throw ... }
const records = json.results; Defensive patterns
Strategy: try-catch
Validate before calling
// Probe page 1 and assert the documented shape before sweeping.
function isValidDataPage(json) {
return !!json && Array.isArray(json.data);
}
const probe = await ctx.fetchJson(`${API_BASE}/jobs?page=1`, { redirect: 'error', headers: { accept: 'application/json' } });
if (!isValidDataPage(probe)) throw new Error('agentic-jobs feed shape unexpected; not sweeping.'); Type guard
/** @param {unknown} j @returns {j is { data: unknown[], meta?: { total?: number, per_page?: number } }} */
function isDataPage(j) {
return !!j && typeof j === 'object' && Array.isArray((/** @type {any} */ (j)).data);
} Try / catch
try {
await provider.fetch(entry, ctx);
} catch (err) {
if (/unexpected API response shape/.test(String(err?.message))) {
console.error('agentic-jobs feed shape changed — curl the API and inspect `data`, then update the parser.');
}
throw err;
} Prevention
- Pin a feed fixture in CI; a diff catches upstream `data` renames.
- Remember this provider does NOT auto-retry transient errors (unlike a16z) — if 429s are likely, add a bounded retry around fetchJson.
- Distinguish 'empty page' (data:[] passes) from 'shape change' (this guard) in diagnostics.
When it happens
Trigger: The API returns a non-JSON body (null), an error envelope without data (e.g. {message:'...'}), renames `data` in a new API version, returns an HTML error page, or a CDN returns a wrapped object. An empty board page returns data:[] and does NOT trip this.
Common situations: Upstream API version bump renaming data; maintenance/error JSON envelope; rate-limit body that isn't the documented shape; the API endpoint moved and API_BASE points at a stale path.
Related errors
- agentic-jobs: parsed 0 jobs from the API — the response shap
- a16z-speedrun-talent: unexpected API response on page ${page
- agentic-jobs: invalid URL: ${url}
- USAGE
- Apify did not return a run id: ${JSON.stringify(body).slice(
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/c4161c4da38249ba.
Report an issue: GitHub.