santifer/career-ops · error
API error: code=
Error message
API error: code=${json?.code} What it means
The Feishu Jobs search API wraps every response in an envelope with a code field; code === 0 means success. After each POST to /api/v1/search/job/posts the provider checks json?.code and throws this error when the code is nonzero or missing — covering rate limits, WAF rejections, bad payloads, and malformed responses alike, with only the raw code reported. If jobs were already collected, the error is caught and treated as a soft stop (partial results kept); it propagates only when the very first request of a keyword fails.
Solutions
- Read the code value in the message and map it against the tenant's documented API error codes; a 405-like code on jobs.bytedance.com almost always means the User-Agent was changed — keep the provider's MACOS_BROWSER_LIKE_USER_AGENT intact.
- Retry later or slow down: reduce max_pages/keywords or honor the built-in inter-page pacing; transient rate-limit codes clear on backoff.
- If code=undefined, dump the raw response — you are likely getting an HTML WAF page or empty body instead of the JSON envelope; check network path/proxy and that the URL/origin is correct.
- For batch jobs, tolerate partial results: the provider already keeps jobs collected before the failure — structure your caller to accept the partial array instead of treating the throw as total loss.
Example fix
// before: custom fetch call dropping the required UA
json = await ctx.fetchJson(api, { method: 'POST', headers: { 'content-type': 'application/json' }, body });
// after: preserve the macOS Chrome UA and same-origin referer
json = await ctx.fetchJson(api, { method: 'POST', headers: { 'content-type': 'application/json', 'user-agent': MACOS_BROWSER_LIKE_USER_AGENT, 'referer': `${origin}/` }, body }); Defensive patterns
Strategy: retry
Validate before calling
// No pre-call validation can prevent server-side error codes; verify the
// envelope after each response instead:
function assertFeishuEnvelope(json) {
if (!json || typeof json !== 'object' || json.code !== 0) {
throw new Error(`feishu: bad envelope code=${json?.code}`);
}
} Type guard
const isFeishuSuccess = (json) => !!json && typeof json === 'object' && json.code === 0 && Array.isArray(json?.data?.job_post_list);
Try / catch
try {
json = await ctx.fetchJson(api, opts);
if (json?.code !== 0) throw new Error(`API error: code=${json?.code}`);
} catch (err) {
if (seen.size === 0) throw err; // first page failed — surface it
console.warn(`Partial results kept: ${seen.size} jobs before failure (${err.message})`);
return [...seen.values()];
} Prevention
- Never override the provider's macOS Chrome User-Agent — jobs.bytedance.com's WAF rejects other UA strings
- Keep the built-in inter-page delay; do not parallelize requests to the same tenant
- Log json.code on failure and check it against the tenant's API docs before retrying
- Treat code=undefined as a malformed/HTML response and inspect the raw body, not just retries
When it happens
Trigger: The tenant API returns a nonzero code: the UA-sniffing WAF on jobs.bytedance.com rejects a non-macOS-Chrome User-Agent (405), a burst hits rate limiting, the POST body/keyword is rejected, or the endpoint returns HTML/empty JSON so json?.code is undefined (code=undefined).
Common situations: Custom HTTP clients overriding the provider's macOS Chrome UA header (the Windows Chrome UA is verifiably rejected by jobs.bytedance.com); scanning too many keywords/pages without the built-in 300ms inter-page delay; the tenant endpoint changed or is geo-blocked; a proxy returns an error page parsed as JSON without a code field.
Related errors
- vc-portfolios: YC API fetch failed
- Apify did not return a run id
- arbeitnow: unexpected API response on page
- glints: HTTP
- gmail: failed to fetch message
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/49db24cb5dfa2c5e.
Report an issue: GitHub.
Appendix: source
Thrown at providers/feishu-jobs.mjs:174
for (let page = 1; page <= maxPages; page++) {
if (firstRequest) firstRequest = false;
else await sleep(INTER_PAGE_DELAY_MS);
const offset = (page - 1) * PAGE_SIZE;
let json;
try {
json = /** @type {any} */ (await ctx.fetchJson(api, {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
'user-agent': MACOS_BROWSER_LIKE_USER_AGENT,
'referer': `${origin}/`,
},
body: JSON.stringify(keyword ? { limit: PAGE_SIZE, offset, keyword } : { limit: PAGE_SIZE, offset }),
redirect: 'error',
}));
if (json?.code !== 0) {
throw new Error(`API error: code=${json?.code}`);
}
} catch (err) {
if (seen.size === 0) throw err;
console.error(` ⚠ feishu-jobs: keyword "${keyword}" page ${page} failed (${err.message}) — keeping the ${seen.size} jobs collected so far`);
return [...seen.values()];
}
const companyName = entry.name || origin;
const sourcePage = Array.isArray(json?.data?.job_post_list) ? json.data.job_post_list : [];
const { jobs, total } = parseFeishuJobsResponse(json, companyName, origin);
if (sourcePage.length === 0) break;
for (const job of jobs) {
if (!seen.has(job.url)) seen.set(job.url, job);
}
const covered = Math.min(offset + PAGE_SIZE, total);
if (covered >= total) break;
if (page === maxPages && probeMaxPages > entryMaxPages) {View on GitHub (pinned to aac998c7ed)