santifer/career-ops · error · Error
API error
Error message
API error: ${decrypted.msg || decrypted.code || 'success=false'} What it means
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.
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.
Example fix
// before
throw new Error(`API error: ${decrypted.msg || decrypted.code || 'success=false'}`);
// after (softer handling for known throttle codes)
if (decrypted?.success === false) {
const e = new Error(`API error: ${decrypted.msg || decrypted.code || 'success=false'}`);
e.code = decrypted.code;
throw e; // caller keeps partial results via succeededOnce
} Defensive patterns
Strategy: retry
Try / catch
try {
jobs = await provider.fetch(entry, ctx);
} catch (err) {
if (/API error:/.test(err.message)) {
console.warn(`MokaHR rejected a keyword/page (${err.message}); partial results retained`);
await sleep(backoff);
jobs = await provider.fetch(entry, ctx); // retry once
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- mokahr: response missing data/necromancer — not the…
- themuse: unexpected API response on page
- 4dayweek: unexpected API response on page
- a16z-speedrun-talent: unexpected API response on page
- agentic-jobs: parsed 0 jobs from the API — the response…
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/fb78edddb0ec85b3.
Report an issue: GitHub.
Appendix: source
Thrown at providers/mokahr.mjs:258
orgId: tenant.orgId,
locale: 'zh-CN',
limit: MAX_LIMIT,
offset,
...(keyword ? { keyword } : {}),
}),
redirect: 'error',
}));
} catch (err) {
if (!succeededOnce) throw err;
console.error(` ⚠ mokahr: keyword "${keyword}" page ${page} failed (${err.message}) — keeping the ${seen.size} jobs collected so far`);
return [...seen.values()];
}
let decrypted;
try {
decrypted = decryptMokaHrEnvelope(envelope);
if (decrypted?.success === false) {
throw new Error(`API error: ${decrypted.msg || decrypted.code || 'success=false'}`);
}
} catch (err) {
if (!succeededOnce) throw err;
console.error(` ⚠ mokahr: keyword "${keyword}" page ${page} failed (${err.message}) — keeping the ${seen.size} jobs collected so far`);
return [...seen.values()];
}
succeededOnce = true;
const rawJobs = Array.isArray(decrypted?.data?.jobs) ? decrypted.data.jobs : [];
if (rawJobs.length === 0) break;
const jobs = parseMokaHrJobs(decrypted, entry.name || tenant.orgId, tenant.baseUrl);
for (const job of jobs) {
if (!seen.has(job.url)) seen.set(job.url, job);
}
// Stop on the raw API page length, not the normalized job count: the
// parser may drop one malformed row from an otherwise-full page, whichView on GitHub (pinned to aac998c7ed)