{"record":{"id":"49db24cb5dfa2c5e","repo":"santifer/career-ops","slug":"api-error-code-json-code","errorCode":null,"errorMessage":"API error: code=${json?.code}","messagePattern":"API error: code=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/feishu-jobs.mjs","lineNumber":174,"sourceCode":"      for (let page = 1; page <= maxPages; page++) {\n        if (firstRequest) firstRequest = false;\n        else await sleep(INTER_PAGE_DELAY_MS);\n        const offset = (page - 1) * PAGE_SIZE;\n        let json;\n        try {\n          json = /** @type {any} */ (await ctx.fetchJson(api, {\n            method: 'POST',\n            headers: {\n              'content-type': 'application/json',\n              'accept': 'application/json',\n              'user-agent': MACOS_BROWSER_LIKE_USER_AGENT,\n              'referer': `${origin}/`,\n            },\n            body: JSON.stringify(keyword ? { limit: PAGE_SIZE, offset, keyword } : { limit: PAGE_SIZE, offset }),\n            redirect: 'error',\n          }));\n          if (json?.code !== 0) {\n            throw new Error(`API error: code=${json?.code}`);\n          }\n        } catch (err) {\n          if (seen.size === 0) throw err;\n          console.error(`  ⚠ feishu-jobs: keyword \"${keyword}\" page ${page} failed (${err.message}) — keeping the ${seen.size} jobs collected so far`);\n          return [...seen.values()];\n        }\n        const companyName = entry.name || origin;\n        const sourcePage = Array.isArray(json?.data?.job_post_list) ? json.data.job_post_list : [];\n        const { jobs, total } = parseFeishuJobsResponse(json, companyName, origin);\n        if (sourcePage.length === 0) break;\n\n        for (const job of jobs) {\n          if (!seen.has(job.url)) seen.set(job.url, job);\n        }\n\n        const covered = Math.min(offset + PAGE_SIZE, total);\n        if (covered >= total) break;\n        if (page === maxPages && probeMaxPages > entryMaxPages) {","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/feishu-jobs.mjs#L156-L192","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: custom fetch call dropping the required UA\njson = await ctx.fetchJson(api, { method: 'POST', headers: { 'content-type': 'application/json' }, body });\n// after: preserve the macOS Chrome UA and same-origin referer\njson = await ctx.fetchJson(api, { method: 'POST', headers: { 'content-type': 'application/json', 'user-agent': MACOS_BROWSER_LIKE_USER_AGENT, 'referer': `${origin}/` }, body });","handlingStrategy":"retry","validationCode":"// No pre-call validation can prevent server-side error codes; verify the\n// envelope after each response instead:\nfunction assertFeishuEnvelope(json) {\n  if (!json || typeof json !== 'object' || json.code !== 0) {\n    throw new Error(`feishu: bad envelope code=${json?.code}`);\n  }\n}","typeGuard":"const isFeishuSuccess = (json) => !!json && typeof json === 'object' && json.code === 0 && Array.isArray(json?.data?.job_post_list);","tryCatchPattern":"try {\n  json = await ctx.fetchJson(api, opts);\n  if (json?.code !== 0) throw new Error(`API error: code=${json?.code}`);\n} catch (err) {\n  if (seen.size === 0) throw err; // first page failed — surface it\n  console.warn(`Partial results kept: ${seen.size} jobs before failure (${err.message})`);\n  return [...seen.values()];\n}","preventionTips":["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"],"tags":["api","feishu","rate-limit","waf","http"],"backgroundTag":"api-error-response","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}