santifer/career-ops · error · Error
a16z-speedrun-talent: unexpected API response on page
Error message
a16z-speedrun-talent: unexpected API response on page ${page} — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}] What it means
Thrown by the a16z-speedrun-talent provider's fetch when a page of its paginated feed returns JSON that lacks a `jobs` array. Each page is validated (json truthy and Array.isArray(json.jobs)) after a bounded retry via fetchJsonWithRetry — the retries cover transient blips mid-sweep because a single failure used to abort the whole provider and return nothing (#2506). Once retries are exhausted or the shape is still wrong, this error propagates: a silent partial board is considered worse than a loud failure. The message lists the actual top-level keys for diagnosis.
Solutions
- Fetch the page URL manually and compare the reported keys against the expected { jobs: [...] } shape.
- Check whether the feed API migrated/changed schema and update normalizeSpeedrunJob/parsing accordingly.
- Cap max_pages or stop at the first page whose job list is empty to avoid querying non-existent pages.
- If a proxy/WAF rewrites responses, bypass it so the real feed JSON arrives.
Defensive patterns
Strategy: retry
Validate before calling
const json = await fetchJsonWithRetry(ctx, url, { redirect: 'error' });
if (!json || !Array.isArray(json.jobs)) {
throw new Error(`speedrun feed shape changed on ${url}: keys=${Object.keys(json ?? {}).join(',')}`);
} Type guard
function isSpeedrunJobsPage(json) {
return typeof json === 'object' && json !== null && Array.isArray(json.jobs);
} Try / catch
try {
const jobs = await fetchSpeedrunFeedPage(page);
} catch (e) {
if (String(e.message).includes('unexpected API response')) {
console.error(`Speedrun feed shape unexpected at page ${page}; schema may have changed`);
return partialResults; // decide loudly, never silently truncate
} else throw e;
} Prevention
- Retry transient page failures with a bounded backoff before giving up.
- Stop pagination when a page comes back empty rather than querying past the end.
- Alert on schema changes: log the actual keys from the error message.
- Verify the feed endpoint manually after provider/API updates.
When it happens
Trigger: fetchJsonWithRetry on a feed page URL returns null, an object without `jobs`, or `jobs` not an array — API schema change, error payload with 200 status, or last-page/overflow page returning a different body; retries (redirect:'error') already exhausted.
Common situations: The speedrun talent feed API changed its response shape; requesting pages beyond the feed's end; a WAF/CDN serving JSON error bodies; an outage returning {message:'...'} objects that survive retries.
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
- 4dayweek: unexpected API response on page
- agentic-jobs: unexpected API response shape on page
- arbeitnow: unexpected API response on page
- collage: unrecognized response envelope (expected a…
- flowxtra: unexpected API response on page
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/916d448f0d28a56b.
Report an issue: GitHub.
Appendix: source
Thrown at providers/a16z-speedrun-talent.mjs:171
assertFeedUrl(FEED_BASE);
const maxPages = resolveMaxPages(entry);
const q = resolveQuery(entry);
const fallbackCompany = entry?.name;
const out = [];
for (let page = 0; page < maxPages; page++) {
const params = new URLSearchParams({ page: String(page), source: 'career-ops' });
if (q) params.set('q', q);
const url = `${FEED_BASE}?${params}`;
// redirect:'error' prevents SSRF via server-side redirects.
// Retried on transient upstream failures (429/5xx/timeout): this board
// paginates into the hundreds of pages, so a single blip mid-sweep used
// to abort the whole provider and return NOTHING. Retries are bounded
// and, once exhausted, the error still propagates — a silent partial
// board would be worse than an loud empty one (#2506).
const json = await fetchJsonWithRetry(ctx, url, { redirect: 'error' });
if (!json || !Array.isArray(json.jobs)) {
throw new Error(
`a16z-speedrun-talent: unexpected API response on page ${page} — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`,
);
}
for (const j of json.jobs) {
const normalized = normalizeSpeedrunJob(j, fallbackCompany);
if (normalized) out.push(normalized);
}
// Stop at the last page. Termination priority (#2547):
// 1. An empty page always ends iteration — nothing left to read, and
// it bounds a feed that over-reports total_pages instead of burning
// requests up to max_pages.
// 2. The feed's own total_pages when present. A page that comes back
// 49/50 because a listing was deleted between requests on a board
// that rotates mid-sweep is NOT the last page; treating it as one
// ended the sweep silently, at a clean exit code — the cap warning
// below is gated on max_pages and is never reached from here.
// A non-positive total_pages is not a statement about board size,
// so it counts as absent: a feed reporting 0 alongside 50 realView on GitHub (pinned to aac998c7ed)