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

  1. Read decrypted.msg/code from the log line ('mokahr: keyword ... failed (...))' to see the API's actual reason.
  2. If the message indicates throttling, reduce request rate / max_pages and retry the failed keyword later — partial results are preserved.
  3. If a specific keyword is rejected, remove or rephrase that keyword in the entry config.
  4. 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

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


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, which

View on GitHub (pinned to aac998c7ed)