santifer/career-ops · error · Error
flowxtra: unexpected API response on page ${page} — expected
Error message
flowxtra: unexpected API response on page ${page} — expected { data: { data: [...] } }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}] What it means
flowxtra.mjs throws this in fetch() after the JSON body comes back, when json.data.data is not an Array. The Flowxtra API contract is { success, data: { data: [...jobs], next_page_url, ... }, message }, so a missing/reshaped data.data means the response is not a live job listing. The message echoes Object.keys(json) (or 'null' when the body was null) so you can see exactly what shape arrived.
Source
Thrown at providers/flowxtra.mjs:130
}
/** @type {Provider} */
export default {
id: 'flowxtra',
async fetch(entry, ctx) {
const maxPages = resolveMaxPages(entry);
const fallbackCompany = entry?.name;
const out = [];
for (let page = 1; page <= maxPages; page++) {
const url = `${JOBS_ENDPOINT}?status=Live&per_page=${PER_PAGE}&page=${page}`;
assertFlowxtraEndpointUrl(url);
// redirect:'error' prevents SSRF via server-side redirects
const json = /** @type {any} */ (await ctx.fetchJson(url, { redirect: 'error' }));
const rows = json?.data?.data;
if (!Array.isArray(rows)) {
throw new Error(
`flowxtra: unexpected API response on page ${page} — expected { data: { data: [...] } }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`,
);
}
for (const j of rows) {
const normalized = normalizeFlowxtraJob(j, fallbackCompany);
if (normalized) out.push(normalized);
}
if (!json.data.next_page_url || rows.length < PER_PAGE) break; // last page reached
}
return out;
},
};
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Re-run the fetch in isolation (curl https://app.flowxtra.com/api/central/jobs?status=Live&per_page=100&page=1) and inspect the top-level keys reported in the message to see the new/intermediate shape.
- If the server is returning an error/maintenance envelope, wait and retry — this is usually transient and not a code defect.
- If the shape changed permanently, update the rows extraction (json.data.data) and the loop's next_page_url/length logic in flowxtra.mjs to match the new contract.
- Verify ctx.fetchJson is not silently returning a parsed HTML/interstitial object; check that the request still uses status=Live and the documented per_page.
Example fix
// before
const rows = json?.data?.data;
if (!Array.isArray(rows)) { throw new Error(`flowxtra: unexpected API response on page ${page} ...`); }
// after (adapt to a renamed key, e.g. data.items)
const rows = json?.data?.data ?? json?.data?.items;
if (!Array.isArray(rows)) { throw new Error(`flowxtra: unexpected API response on page ${page} ...`); } Defensive patterns
Strategy: try-catch
Type guard
// Narrow a Flowxtra response to the expected { data: { data: [] } } shape.
function isFlowxtraPage(json) {
return !!json
&& typeof json === 'object'
&& json.data && typeof json.data === 'object'
&& Array.isArray(json.data.data);
} Try / catch
// Treat a shape failure on a non-first page as 'end of feed' rather than fatal.
try {
const json = await ctx.fetchJson(url, { redirect: 'error' });
if (!isFlowxtraPage(json)) {
if (page === 1) throw new Error(`flowxtra: unexpected API response on page ${page}`);
break; // later-page anomaly: stop paginating, keep what we have
}
} catch (err) {
if (page === 1) throw err;
console.error(`flowxtra: page ${page} failed — ${err.message}`);
break;
} Prevention
- Pin scan runs to a known-good Flowxtra API version and watch for deprecation announcements.
- Log Object.keys(json) on shape failures (the message already does) so regressions are diagnosed from one run.
- Consider a retry-with-backoff for transient maintenance envelopes before surfacing the error.
When it happens
Trigger: The Flowxtra endpoint returns an error envelope ({ success:false, message:'...' }) where data is absent or data.data is an object/string; the endpoint is behind a maintenance page returning HTML that ctx.fetchJson parsed into a non-standard object; the API was versioned and renamed the nested data key; a Cloudflare/WAF challenge returned an interstitial JSON; json itself was null (empty 200 body).
Common situations: Flowxtra ships an API change that wraps jobs under a new key; the board is temporarily down and returns { message:'maintenance' } with no data; a transient proxy/gateway returns a JSON error object; the per_page/page query params were rejected and the server replied with an error envelope.
Related errors
- getonbrd: unexpected API response on page ${page} — expected
- glints: unexpected API response — ${JSON.stringify(json).sli
- gem: JobBoardList failed: ${listResult.errors[0]?.message ||
- glints: HTTP ${err.status} — ${detail}
- ${msg}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/6f691a0c8f5b13b2.
Report an issue: GitHub.