jackwener/OpenCLI · error · CliError
API_ERROR
API_ERROR
Error message
51job search failed: ${data.message ?? 'unknown'} What it means
The search command fetches 51job's search JSON API and verifies data.status === '1'/1; any other status throws CliError('API_ERROR') with the API-supplied message (or 'unknown'). This is an upstream API rejection (bad filter codes, throttling, endpoint/format change), thrown before parsing items at clis/51job/search.js:72.
Source
Thrown at clis/51job/search.js:72
const companySize = resolveCode(kwargs.companySize, COMPANY_SIZE_CODES);
const sortType = resolveCode(kwargs.sort, SORT_CODES, '0');
// Establish WAF-clean origin. Reusing the same tab avoids the slider
// challenge fire every call.
const currentUrl = await page.evaluate(`(() => window.location.href)()`);
if (!String(currentUrl).startsWith(WE_ORIGIN)) {
await navigateTo(page, `${WE_ORIGIN}/pc/search?keyword=${encodeURIComponent(keyword)}&searchType=2`, 2);
}
const url = buildSearchUrl({
keyword, jobArea, salary, workYear, degree,
companyType, companySize, sortType,
pageNum, pageSize: Math.min(limit, 50),
});
const data = await pageFetchJson(page, url);
if (data.status !== '1' && data.status !== 1) {
throw new CliError('API_ERROR', `51job search failed: ${data.message ?? 'unknown'}`);
}
const items = data?.resultbody?.job?.items ?? [];
if (items.length === 0) {
throw new CliError('NO_DATA', `No jobs matched "${keyword}"`);
}
return items.slice(0, limit).map((it, i) => mapJobItem(it, (pageNum - 1) * limit + i + 1));
},
});
View on GitHub (pinned to 49907e53dc)
Solutions
- Inspect the error message — it contains the API's data.message explaining the rejection
- Use resolveCode-mapped values: pass filters through the library's accepted code sets (SALARY_CODES, WORKYEAR_CODES, resolveCity) rather than raw strings
- Back off and retry with exponential delay if throttled
- Update the library if 51job changed the API; verify the status field the check expects
Example fix
// before
await cli.search({ keyword: 'java', salary: '20k-30k', experience: '3 years' });
// after
try {
return await cli.search({ keyword: 'java', salary: '27', experience: '4' }); // valid codes
} catch (e) {
if (e.code === 'API_ERROR') { await sleep(5000); return cli.search({ keyword: 'java' }); }
throw e;
} Defensive patterns
Strategy: retry
Validate before calling
const KNOWN_CODES = { salary: ['01','02','03',...], experience: ['01','02',...] }; // pass mapped codes only
if (salary && !KNOWN_CODES.salary.includes(salary)) throw new Error('invalid salary code'); Type guard
const isApiOk = (d) => d != null && (d.status === '1' || d.status === 1);
Try / catch
try {
return await cli.search({ keyword, area, salary, experience });
} catch (e) {
if (e.code === 'API_ERROR') {
console.error('51job search API:', e.message);
await sleep(5000);
return cli.search({ keyword }); // retry with fewer filters
}
throw e;
} Prevention
- Translate filters through the library's code maps, not raw display strings
- Rate-limit crawls to avoid blocks
- Log the upstream data.message carried in the error
- Update the library when 51job alters its API envelope
When it happens
Trigger: Calling the search subcommand when the API rejects the query — invalid salary/experience/workYear or area codes, rate limiting/bot detection, API envelope change, or transient server error.
Common situations: Passing human-readable filter values (e.g. '20k-30k', '3 years') instead of the library's mapped codes; aggressive crawling triggering blocks; 51job changing its API response format so status is no longer '1'; maintenance windows.
Related errors
- API_ERROR
- HTTP ${result.httpStatus} from /api/user
- ${batch.error}
- Suno generate failed (HTTP ${status}): ${detail}
- TikTok Studio item_list failed: ${detail}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/45abf46eacd20d55.
Report an issue: GitHub.