santifer/career-ops · error
glints: unexpected API response
Error message
glints: unexpected API response — ${JSON.stringify(json).slice(0, 200)} What it means
The Glints provider pages through a GraphQL searchJobsV3 endpoint and expects each response to contain data.searchJobsV3.jobsInPage as an array. When page 1 already fails to have that shape, the provider throws this error with a 200-char dump of the JSON to aid diagnosis. It guards against silently scanning zero jobs when the API contract changes or a proxy/CDN returns non-GraphQL payloads.
Solutions
- Read the 200-char JSON snippet in the message to identify what the API actually returned (errors array? challenge page? empty object?).
- Re-run the scan later — transient upstream errors often clear; the fetch loop itself breaks (not throws) for page > 1.
- Update the extraction path in providers/glints.mjs if Glints changed the GraphQL schema (e.g. searchJobsV3 renamed).
- Check for network middleboxes/proxies that intercept the HTTPS request and return their own JSON (captive portal, corporate proxy).
Example fix
// before const jobsInPage = json?.data?.searchJobsV3?.jobsInPage; // after (tolerate a renamed/shifted payload) const jobsInPage = json?.data?.searchJobsV3?.jobsInPage ?? json?.data?.searchJobs?.jobsInPage;
Defensive patterns
Strategy: type-guard
Validate before calling
function looksLikeGlintsPayload(json) {
return Array.isArray(json?.data?.searchJobsV3?.jobsInPage);
}
if (!looksLikeGlintsPayload(await res.json())) throw new Error('glints: payload shape changed'); Type guard
const isGlintsPage = (v) => Array.isArray(v?.data?.searchJobsV3?.jobsInPage);
Try / catch
try {
await provider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).startsWith('glints: unexpected API response')) {
console.error(`glints schema drift for ${entry.name}:`, err.message);
return; // skip entry, keep scanning other boards
}
throw err;
} Prevention
- Log the full upstream body (not just 200 chars) when the shape guard trips so schema drift is diagnosable.
- Pin a small integration test that hits the Glints API weekly and asserts the jobsInPage path still exists.
- Treat page>1 shape breaks as a break, page 1 as a throw — keep that asymmetry so partial data still flows.
- Watch for GraphQL errors arrays in responses and surface them explicitly instead of as shape failures.
When it happens
Trigger: First page of a fetch() call where json.data.searchJobsV3.jobsInPage is not an array — e.g. the API returns an errors array, an HTML error page parsed as JSON, a rate-limit JSON body, or Glints renames/versions the field (searchJobsV3 -> v4).
Common situations: Glints schema/GraphQL version change, blocked or rate-limited requests returning a challenge JSON, misconfigured query variables producing an errors payload, or an intermittent upstream outage on the very first request.
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
- hackernews: unexpected item response for thread
- Apify did not return a run id
- gem: JobBoardList failed
- getonbrd: unexpected API response for category
- glints: HTTP
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/fd09629e69607f5b.
Report an issue: GitHub.
Appendix: source
Thrown at providers/glints.mjs:229
CountryCode: country,
includeExternalJobs: true,
pageSize: pageSize,
page: page,
},
};
let json;
try {
json = /** @type {any} */ (await graphqlPage(apiUrl, query, variables, ctx));
} catch (err) {
if (page === 1) throw err;
console.error(`glints: page ${page} fetch failed — ${err.message}`);
break;
}
const jobsInPage = json?.data?.searchJobsV3?.jobsInPage;
if (!Array.isArray(jobsInPage)) {
if (page === 1) throw new Error(`glints: unexpected API response — ${JSON.stringify(json).slice(0, 200)}`);
break;
}
if (jobsInPage.length === 0) break;
for (const item of jobsInPage) {
const job = parseGlintsItem(item, baseUrl, fallbackCompany);
if (job) allJobs.push(job);
}
// Stop if no more pages
if (json?.data?.searchJobsV3?.hasMore === false) break;
if (jobsInPage.length < pageSize) break;
// Rate-limit courtesy delay
await new Promise(resolve => setTimeout(resolve, 300));
}
View on GitHub (pinned to aac998c7ed)