{"record":{"id":"45abf46eacd20d55","repo":"jackwener/OpenCLI","slug":"api-error-45abf4","errorCode":"API_ERROR","errorMessage":"51job search failed: ${data.message ?? 'unknown'}","messagePattern":"51job search failed: (.+?)","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/51job/search.js","lineNumber":72,"sourceCode":"        const companySize = resolveCode(kwargs.companySize, COMPANY_SIZE_CODES);\n        const sortType = resolveCode(kwargs.sort, SORT_CODES, '0');\n\n        // Establish WAF-clean origin. Reusing the same tab avoids the slider\n        // challenge fire every call.\n        const currentUrl = await page.evaluate(`(() => window.location.href)()`);\n        if (!String(currentUrl).startsWith(WE_ORIGIN)) {\n            await navigateTo(page, `${WE_ORIGIN}/pc/search?keyword=${encodeURIComponent(keyword)}&searchType=2`, 2);\n        }\n\n        const url = buildSearchUrl({\n            keyword, jobArea, salary, workYear, degree,\n            companyType, companySize, sortType,\n            pageNum, pageSize: Math.min(limit, 50),\n        });\n\n        const data = await pageFetchJson(page, url);\n        if (data.status !== '1' && data.status !== 1) {\n            throw new CliError('API_ERROR', `51job search failed: ${data.message ?? 'unknown'}`);\n        }\n        const items = data?.resultbody?.job?.items ?? [];\n        if (items.length === 0) {\n            throw new CliError('NO_DATA', `No jobs matched \"${keyword}\"`);\n        }\n        return items.slice(0, limit).map((it, i) => mapJobItem(it, (pageNum - 1) * limit + i + 1));\n    },\n});\n","sourceCodeStart":54,"sourceCodeEnd":81,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/51job/search.js#L54-L81","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait cli.search({ keyword: 'java', salary: '20k-30k', experience: '3 years' });\n// after\ntry {\n  return await cli.search({ keyword: 'java', salary: '27', experience: '4' }); // valid codes\n} catch (e) {\n  if (e.code === 'API_ERROR') { await sleep(5000); return cli.search({ keyword: 'java' }); }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"const KNOWN_CODES = { salary: ['01','02','03',...], experience: ['01','02',...] }; // pass mapped codes only\nif (salary && !KNOWN_CODES.salary.includes(salary)) throw new Error('invalid salary code');","typeGuard":"const isApiOk = (d) => d != null && (d.status === '1' || d.status === 1);","tryCatchPattern":"try {\n  return await cli.search({ keyword, area, salary, experience });\n} catch (e) {\n  if (e.code === 'API_ERROR') {\n    console.error('51job search API:', e.message);\n    await sleep(5000);\n    return cli.search({ keyword }); // retry with fewer filters\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["api","http-error","rate-limit"],"backgroundTag":"upstream-api-error","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}