santifer/career-ops · error · Error
csod: cannot resolve careersite URL for ${entry.name}
Error message
csod: cannot resolve careersite URL for ${entry.name} What it means
Thrown by the csod (Cornerstone OnDemand) provider's fetch() when resolveConfig(entry) returns null. resolveConfig needs an https URL whose host is exactly csod.com or ends in .csod.com, AND whose path carries the careersite shape /ux/ats/careersite/{digits}/ (a numeric siteId). Both the branded corporate page (careers_url) and the csod.com URL (api:) follow this convention. detect() applies the same check, so this fires only when fetch() is driven on a non-detecting entry.
Source
Thrown at providers/csod.mjs:174
if (Number.isInteger(v) && v > 0) return Math.min(v, MAX_PAGES);
return MAX_PAGES;
}
/** @type {Provider} */
export default {
id: 'csod',
detect(entry) {
const url = entry.api || entry.careers_url || '';
if (typeof url !== 'string') return null;
// Host check (not a path substring) so evil.com/x.csod.com can't spoof it,
// and the URL must carry the careersite path shape we know how to drive.
return resolveConfig({ api: url }) ? { url } : null;
},
async fetch(entry, ctx) {
const cfg = resolveConfig(entry);
if (!cfg) throw new Error(`csod: cannot resolve careersite URL for ${entry.name}`);
// The bootstrap page yields two things, not one: the anonymous bearer
// token, and — on some tenants — the session cookies the search API
// insists on. careers-kln rejects an otherwise valid token+body with
// "HTTP 401 CSOD Unauthorized" until those cookies come back with it, so
// the token alone is not a sufficient credential. Prefer ctx.fetchResponse
// to see Set-Cookie; fall back to fetchText when the caller's ctx predates
// it (older embedders and test mocks), which keeps the pre-cookie
// behaviour intact for tenants that never needed it.
//
// cfg.homeUrl and cfg.searchApi are both built from the same parsed
// origin, so replaying these cookies cannot reach a third-party host.
// redirect:'error' on the bootstrap keeps that true: origin validation
// covers the URL we ask for, not wherever a 3xx would send us.
let html;
let cookie = '';
if (typeof ctx.fetchResponse === 'function') {
const res = await ctx.fetchResponse(cfg.homeUrl, { redirect: 'error', headers: { accept: 'text/html' } });View on GitHub (pinned to 9b17a8ac97)
Solutions
- Set api: to the full csod.com careersite URL, e.g. https://career-ohb.csod.com/ux/ats/careersite/4/home?c=career-ohb.
- Ensure the path contains /ux/ats/careersite/<numeric siteId>/ — the regex extracts the siteId from there.
- Keep the branded page in careers_url and the csod.com URL in api: (same convention as workday/successfactors).
- Gate with provider.detect(entry) before fetch().
Example fix
# before — branded host only, no csod.com URL - name: OHB provider: csod careers_url: https://www.career-ohb.com # after — csod.com careersite URL with numeric siteId - name: OHB provider: csod careers_url: https://www.career-ohb.com api: https://career-ohb.csod.com/ux/ats/careersite/4/home?c=career-ohb
Defensive patterns
Strategy: validation
Validate before calling
import csod from './providers/csod.mjs';
if (!csod.detect(entry)) {
// entry.api/entry.careers_url is not https://*.csod.com/ux/ats/careersite/<id>/... — fix config
} Type guard
/** True when entry resolves to a CSOD careersite (https *.csod.com with /ux/ats/careersite/<digits>/). */
function isCsodEntry(entry) {
const raw = typeof entry?.api === 'string' ? entry.api : (typeof entry?.careers_url === 'string' ? entry.careers_url : '');
if (!raw) return false;
let u;
try { u = new URL(raw); } catch { return false; }
if (u.protocol !== 'https:') return false;
const host = u.host.toLowerCase();
if (host !== 'csod.com' && !host.endsWith('.csod.com')) return false;
return /\/ux\/ats\/careersite\/\d+/.test(u.pathname);
} Try / catch
try { await csod.fetch(entry, ctx); }
catch (e) {
if (/^csod: cannot resolve careersite URL/.test(e.message)) {
// config issue — set api: to the csod.com careersite URL; do not retry
} else throw e;
} Prevention
- Put the branded page in careers_url and the csod.com URL in api:.
- The path must carry /ux/ats/careersite/<numeric siteId>/.
- https is mandatory (session cookies are replayed — no cleartext).
When it happens
Trigger: entry.api/entry.careers_url is missing, unparseable, non-https, not on *.csod.com, or its path lacks a numeric /ux/ats/careersite/{siteId}/ segment.
Common situations: Putting the branded corporate page (not on csod.com) in api:; using an http URL (rejected because session cookies would travel in clear); a URL whose siteId path was copied without the /ux/ats/careersite/ prefix; omitting the csod.com URL entirely.
Related errors
- breezy: cannot derive API URL for ${entry.name}
- comeet: cannot derive API URL for ${entry.name} (set api: to
- consider: ${entry.name} needs an https careers_url on a publ
- eightfold: cannot derive API URL for ${entry.name}
- deutschebahn: cannot resolve db.jobs search id for ${entry.n
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/e40decd48c929e79.
Report an issue: GitHub.