santifer/career-ops · error · Error
icims: cannot derive portal origin for
Error message
icims: cannot derive portal origin for ${entry.name} What it means
The icims provider derives the portal origin (the tenant's *.icims.com base URL) from the portals.yml entry via resolveOrigin. If it cannot derive an origin, fetch() throws this error naming the entry, because without a base origin there is no search endpoint to page through.
Solutions
- Set the entry's careers_url (or the field resolveOrigin reads) to the company's iCIMS tenant URL, e.g. https://<tenant>.icims.com/jobs
- Find the iCIMS portal link from the company's careers page and update portals.yml
- Remove or disable the entry if the company no longer uses iCIMS
- Extend resolveOrigin only if you control the entry shape — prefer fixing the config
Example fix
// before (portals.yml) careers_url: "https://acme.com/careers" // after careers_url: "https://acme.icims.com/jobs"
Defensive patterns
Strategy: validation
Validate before calling
function hasIcimsOrigin(entry) {
if (!entry || typeof entry.name !== 'string') return false;
const url = typeof entry.careers_url === 'string' ? entry.careers_url : '';
try {
const u = new URL(url);
return /(^|\.)icims\.com$/.test(u.hostname) || typeof entry.portal === 'string';
} catch { return false; }
}
// filter entries before fetch: entries.filter(hasIcimsOrigin) Type guard
const isIcimsEntry = (e) => !!e && typeof e.careers_url === 'string'
&& (() => { try { return new URL(e.careers_url).hostname.endsWith('.icims.com'); } catch { return false; } })(); Try / catch
try {
await icimsProvider.fetch(entry, ctx);
} catch (err) {
if (/cannot derive portal origin/.test(err.message)) {
console.warn(`fixing config for ${entry.name}: ${err.message}`);
return [];
}
throw err;
} Prevention
- Store the iCIMS tenant URL (https://<tenant>.icims.com/...) in careers_url
- Verify each new portals.yml entry resolves before committing it
- Re-audit entries after companies announce ATS migrations
- Use audit-portals.mjs to catch entries whose provider can no longer claim them
When it happens
Trigger: resolveOrigin(entry) returns falsy for an entry — the entry lacks the field icims derives the origin from (e.g. careers_url/portal host), or the URL does not match the expected iCIMS host pattern.
Common situations: A portals.yml entry was added with a plain careers page URL instead of the iCIMS tenant URL; the company migrated off iCIMS so the URL no longer matches the iCIMS pattern; a typo in the portal hostname.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- arbeitnow: invalid URL
- breezy: cannot derive API URL for
- collage: cannot derive API URL for
- comeet: invalid URL
- deutschebahn: cannot resolve db.jobs search id for
AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-22).
Data as JSON: /api/errors/6eb2798e1245084d.
Report an issue: GitHub.
Appendix: source
Thrown at providers/icims.mjs:158
id: 'icims',
detect(entry) {
const origin = resolveOrigin(entry);
return origin ? { url: searchUrl(origin, 0) } : null;
},
/**
* Walk a tenant's search pages. An entry may carry `fallback_urls`: other
* hosts the same tenant could be served from (scan-ats-full.mjs builds them,
* because the public dataset stores some tenants bare and some as a full
* portal subdomain). A fallback is tried only when the previous host answers
* 404 on its FIRST page, the one response that means "no board here". Any
* other failure (throttle, timeout, DNS, a later-page 404) is rethrown as-is,
* so dead-board tracking still reads it as "unknown", never "dead".
*/
async fetch(entry, ctx) {
const primary = resolveOrigin(entry);
if (!primary) throw new Error(`icims: cannot derive portal origin for ${entry.name}`);
const origins = [primary];
for (const raw of Array.isArray(entry.fallback_urls) ? entry.fallback_urls : []) {
// Same https + *.icims.com gate as the primary: a fallback can never
// point the scanner at another host.
const origin = resolveOrigin({ careers_url: raw });
if (origin && !origins.includes(origin)) origins.push(origin);
}
let notFound;
for (const origin of origins) {
try {
return await fetchPortal(origin, entry, ctx);
} catch (err) {
if (err?.status !== 404 || !err.firstPage) throw err;
notFound = err;
}
}
throw notFound;
},View on GitHub (pinned to e7abd431fc)