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

  1. Read the errorMsg/errorCode in the message: a parameter error means buildBody's payload no longer matches the API — update keyword/page fields.
  2. If it's a throttle code, slow the request pacing or reduce keywords per run and retry.
  3. 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.
  4. 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

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


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)