santifer/career-ops · error · Error

successfactors: cannot resolve origin for ${entry.name}

Error message

successfactors: cannot resolve origin for ${entry.name}

What it means

resolveConfig parses entry.api or entry.careers_url and derives the RMK/CSB endpoint origins; it returns null only if new URL throws or the protocol is not http/https. fetch throws when that happens. Note detect() matches on a loose regex (successfactors.(eu|com)|jobs2web.com) against the raw string, so a string can pass detection yet fail URL parsing — and a provider: successfactors override skips detection entirely.

Source

Thrown at providers/successfactors.mjs:417

    startrow += tiles.length;
  }
  // The cap is checked between pages, so the last page can overshoot it.
  return jobs.slice(0, MAX_JOBS);
}

/** @type {Provider} */
export default {
  id: 'successfactors',

  detect(entry) {
    const url = entry.api || entry.careers_url || '';
    if (/successfactors\.(eu|com)|jobs2web\.com/i.test(url)) return { url };
    return null;
  },

  async fetch(entry, ctx) {
    const cfg = resolveConfig(entry);
    if (!cfg) throw new Error(`successfactors: cannot resolve origin for ${entry.name}`);

    // Explicit opt-in goes straight to CSB. RMK tenants (the majority) run the
    // tile scraper; only when it comes back empty — the CSB empty-shell
    // signature — do we auto-fall back to the JSON API, so an unflagged CSB
    // tenant still works and a genuinely-empty RMK board costs one extra probe.
    if (String(entry.sfVariant || '').toLowerCase() === 'csb') {
      return fetchCsb(entry, cfg, ctx);
    }
    const rmkJobs = await fetchRmk(entry, cfg, ctx);
    if (rmkJobs.length > 0) return rmkJobs;
    // RMK answered (healthy, possibly legitimately empty) — the CSB call here
    // is only a probe for the empty-shell signature, so it must not turn a
    // failing/absent CSB endpoint into a dead-board throw.
    return fetchCsb(entry, cfg, ctx, { probe: true });
  },
};

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Provide a full absolute URL with scheme: https://careers.example.com or https://<tenant>.successfactors.com
  2. Ensure the protocol is http or https (https strongly preferred)
  3. If using provider: successfactors as an override, also set careers_url or api to the valid tenant URL
  4. Verify the URL still resolves in a browser — a decommissioned tenant origin can also yield odd responses upstream

Example fix

# before
- name: Acme
  provider: successfactors
  careers_url: successfactors.com/acme/careers
# after
- name: Acme
  provider: successfactors
  careers_url: https://acme.successfactors.com/careers
Defensive patterns

Strategy: validation

Validate before calling

// Validate the entry URL is parseable http(s) before successfactors.fetch.
function resolvableOrigin(v) {
  try {
    const u = new URL(v);
    return u.protocol === 'http:' || u.protocol === 'https:';
  } catch { return false; }
}
if (!resolvableOrigin(entry.api || entry.careers_url)) {
  console.warn(`${entry.name}: successfactors needs a valid http(s) URL`);
}

Prevention

When it happens

Trigger: The entry string contains 'successfactors.com' (so detect's regex matches) but is not a valid absolute URL, e.g. 'successfactors.com/careers' with no scheme. Or provider: successfactors is set but api/careers_url is missing or malformed. A protocol like ftp:// would also fail the http/https gate.

Common situations: A manually-added entry omits the https:// scheme; YAML mis-parsing produced a non-string; or provider: successfactors was force-set on an entry that has no usable URL.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/56f40d550a5a8a04. Report an issue: GitHub.