{"record":{"id":"fb78edddb0ec85b3","repo":"santifer/career-ops","slug":"api-error-decrypted-msg-decrypted-code-s","errorCode":null,"errorMessage":"API error: ${decrypted.msg || decrypted.code || 'success=false'}","messagePattern":"API error: (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/mokahr.mjs","lineNumber":258,"sourceCode":"              orgId: tenant.orgId,\n              locale: 'zh-CN',\n              limit: MAX_LIMIT,\n              offset,\n              ...(keyword ? { keyword } : {}),\n            }),\n            redirect: 'error',\n          }));\n        } catch (err) {\n          if (!succeededOnce) throw err;\n          console.error(`  ⚠ mokahr: keyword \"${keyword}\" page ${page} failed (${err.message}) — keeping the ${seen.size} jobs collected so far`);\n          return [...seen.values()];\n        }\n\n        let decrypted;\n        try {\n          decrypted = decryptMokaHrEnvelope(envelope);\n          if (decrypted?.success === false) {\n            throw new Error(`API error: ${decrypted.msg || decrypted.code || 'success=false'}`);\n          }\n        } catch (err) {\n          if (!succeededOnce) throw err;\n          console.error(`  ⚠ mokahr: keyword \"${keyword}\" page ${page} failed (${err.message}) — keeping the ${seen.size} jobs collected so far`);\n          return [...seen.values()];\n        }\n\n        succeededOnce = true;\n        const rawJobs = Array.isArray(decrypted?.data?.jobs) ? decrypted.data.jobs : [];\n        if (rawJobs.length === 0) break;\n        const jobs = parseMokaHrJobs(decrypted, entry.name || tenant.orgId, tenant.baseUrl);\n\n        for (const job of jobs) {\n          if (!seen.has(job.url)) seen.set(job.url, job);\n        }\n\n        // Stop on the raw API page length, not the normalized job count: the\n        // parser may drop one malformed row from an otherwise-full page, which","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/mokahr.mjs#L240-L276","documentation":"After successfully decrypting a MokaHR envelope, the provider checks decrypted.success; when the API reports success=false it throws this error carrying the API's own msg or code. It surfaces a logical API rejection (not a transport or crypto failure) such as a rejected query or throttling message. Note the caller catches it: if some keywords already succeeded, the partial result set is kept and the error is only logged as a warning.","triggerScenarios":"The MokaHR API returns {success:false, msg:'...'} (or a code with no msg) inside a correctly-decrypted envelope — e.g. an invalid keyword query, expired/invalid site session, or per-page limit rejection during a keyword/page iteration.","commonSituations":"A keyword that trips a server-side validation rule; site ID valid but tenant rejects the query shape after an API update; transient throttling on later pages of pagination; region/tenant requiring extra auth.","solutions":["Read decrypted.msg/code from the log line ('mokahr: keyword ... failed (...))' to see the API's actual reason.","If the message indicates throttling, reduce request rate / max_pages and retry the failed keyword later — partial results are preserved.","If a specific keyword is rejected, remove or rephrase that keyword in the entry config.","If the API contract changed (success semantics), update the provider's handling in providers/mokahr.mjs."],"exampleFix":"// before\nthrow new Error(`API error: ${decrypted.msg || decrypted.code || 'success=false'}`);\n// after (softer handling for known throttle codes)\nif (decrypted?.success === false) {\n  const e = new Error(`API error: ${decrypted.msg || decrypted.code || 'success=false'}`);\n  e.code = decrypted.code;\n  throw e; // caller keeps partial results via succeededOnce\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  jobs = await provider.fetch(entry, ctx);\n} catch (err) {\n  if (/API error:/.test(err.message)) {\n    console.warn(`MokaHR rejected a keyword/page (${err.message}); partial results retained`);\n    await sleep(backoff);\n    jobs = await provider.fetch(entry, ctx); // retry once\n  } else throw err;\n}","preventionTips":["Keep max_pages and keyword counts modest to avoid per-tenant throttling.","Rely on the provider's succeededOnce behavior: partial results survive single-keyword failures.","Surface decrypted.msg in your own logs — it names the API's actual rejection reason.","Rotate or drop keywords that consistently fail server-side validation."],"tags":["api","mokahr","partial-results"],"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"}