santifer/career-ops · error · Error
API error
Error message
API error: ${json.errorMsg || json.errorCode || 'success=false'} What it means
Alibaba's job API reports failures in-band: it returns HTTP 200 with { success: false, errorMsg/errorCode }. Because the HTTP status looks fine, the provider explicitly checks success===false and throws so a dead board isn't mistaken for an empty-but-alive one. The thrown message carries the API's own errorMsg or errorCode.
Solutions
- Read the errorMsg/errorCode in the message: a parameter error means buildBody's payload no longer matches the API — update keyword/page fields.
- If it's a throttle code, slow the request pacing or reduce keywords per run and retry.
- Retry the run if transient — mid-run blips don't discard collected jobs, but a persistent error on every request means the board/API changed.
- Verify the endpoint is still live with a manual POST replicating buildBody's body; if the board is dead, remove or disable the portals.yml entry.
Example fix
// before: treats any failure as fatal for that keyword
throw new Error(`API error: ${json.errorMsg || json.errorCode || 'success=false'}`);
// after (caller side): tolerate transient in-band errors, keep prior pages
try { await fetchKeywordPage(page); } catch (err) {
if (seen.size > 0 && isTransient(err)) return; // keep collected jobs
throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
// probe the board before a full sweep
const json = await fetch(ALIBABA_ENDPOINT, { method: 'POST', body: probeBody }).then(r => r.json());
if (json?.success === false) throw new Error(`alibaba board unhealthy: ${json.errorMsg || json.errorCode}`); Type guard
function isAlibabaError(json) {
return !!json && typeof json === 'object' && json.success === false;
} Try / catch
try {
await fetchKeyword(kw, page);
} catch (err) {
if (String(err.message).startsWith('API error:') && collected > 0) {
console.warn(`alibaba: keyword "${kw}" failed mid-run, keeping ${collected} jobs: ${err.message}`);
return; // don't discard already-collected pages
}
throw err;
} Prevention
- Never assume HTTP 200 means success with this API — always check the success flag in the body.
- Pace requests to avoid in-band throttling errors during batch sweeps.
- Log errorMsg/errorCode verbatim; it names the exact parameter or limit that failed.
- Re-validate buildBody's field names whenever the provider hasn't run for a while.
When it happens
Trigger: Any keyword/page request where the API responds 200 with success:false — e.g. invalid or missing request parameters in buildBody, rate limiting expressed in-band, an internal API error, or a deprecated endpoint still answering 200 with an error envelope.
Common situations: Alibaba changed body parameters (keyword/page field names) so requests are rejected; IP-level throttling during a large sweep; the board was migrated or decommissioned; transient upstream errors mid-run (the catch block deliberately keeps already-collected jobs for blips).
Related errors
- 4dayweek: unexpected API response on page
- a16z-speedrun-talent: unexpected API response on page
- agentic-jobs: unexpected API response shape on page
- arbeitnow: unexpected API response on page
- arbeitsagentur: all keyword request(s) failed
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/3abad676a8fc1770.
Report an issue: GitHub.
Appendix: source
Thrown at providers/alibaba.mjs:162
for (let page = 1; page <= maxPages; page++) {
if (firstRequest) firstRequest = false;
else await sleep(INTER_PAGE_DELAY_MS, ctx);
let json;
try {
json = /** @type {any} */ (await ctx.fetchJson(API, {
method: 'POST',
headers: {
'content-type': 'application/json',
'cookie': `XSRF-TOKEN=${csrfToken}`,
'x-xsrf-token': csrfToken,
},
body: buildBody(keyword, page),
redirect: 'error',
}));
// The API reports failures in-band with HTTP 200; surface them so a
// dead board doesn't read as an empty-but-alive one.
if (json?.success === false) {
throw new Error(`API error: ${json.errorMsg || json.errorCode || 'success=false'}`);
}
} catch (err) {
// A dead board should still read as a failure, but a mid-run blip
// must not discard what's already collected (same idiom as
// workday/jobstreet/glints). Track successes directly — a keyword
// can legitimately match 0 jobs, so seen.size is not the signal.
if (!succeededOnce) throw err;
console.error(` ⚠ alibaba: keyword "${keyword}" page ${page} failed (${err.message}) — keeping the ${seen.size} jobs collected so far`);
return [...seen.values()];
}
succeededOnce = true;
const { jobs, total } = parseAlibabaResponse(json, entry.name || '阿里巴巴');
if (jobs.length === 0) break;
for (const job of jobs) {
if (!seen.has(job.url)) seen.set(job.url, job);
}
View on GitHub (pinned to aac998c7ed)