santifer/career-ops · error · Error
smartrecruiters: cannot derive API URL for
Error message
smartrecruiters: cannot derive API URL for ${entry.name} What it means
The smartrecruiters provider's fetch derives an API slug from entry.api or entry.careers_url via resolveSlug; if neither yields a slug (missing, non-HTTPS, wrong host like a branded domain, or empty path), fetch cannot construct the postings API URL and throws this error. It means the portals entry is not identifiable as a SmartRecruiters tenant.
Solutions
- Add api: 'https://careers.smartrecruiters.com/<slug>' to the portals entry so the slug is pinned explicitly (this is the documented fix for branded domains)
- Fix careers_url to match https://(careers|jobs).smartrecruiters.com/<slug> with a non-empty slug
- Ensure the scheme is https:// — resolveSlug silently skips http URLs
- Verify the entry actually is a SmartRecruiters tenant; if not, remove or change its provider
Example fix
// before - name: Continental careers_url: 'https://jobs.continental.com' // after - name: Continental careers_url: 'https://jobs.continental.com' api: 'https://careers.smartrecruiters.com/Continental'
Defensive patterns
Strategy: validation
Validate before calling
function canDeriveSrSlug(entry) {
for (const raw of [entry?.api, entry?.careers_url]) {
if (typeof raw !== 'string' || !raw) continue;
try {
const u = new URL(raw);
if (u.protocol === 'https:' &&
['careers.smartrecruiters.com','jobs.smartrecruiters.com'].includes(u.hostname) &&
u.pathname.split('/').filter(Boolean).length) return true;
} catch {}
}
return false;
}
if (!canDeriveSrSlug(entry)) console.error(`${entry.name}: needs api pin or (careers|jobs).smartrecruiters.com URL`); Type guard
const hasSrSlugSource = (entry) =>
[entry?.api, entry?.careers_url].some((raw) => {
if (typeof raw !== 'string') return false;
try {
const u = new URL(raw);
return u.protocol === 'https:' && /(^|\.)(careers|jobs)\.smartrecruiters\.com$/.test(u.hostname);
} catch { return false; }
}); Try / catch
try {
await srProvider.fetch(entry, ctx);
} catch (e) {
if (String(e.message).includes('cannot derive API URL')) {
console.error(`Entry ${entry.name}: add api: 'https://careers.smartrecruiters.com/<slug>' to pin the tenant slug`);
} else throw e;
} Prevention
- For branded career domains, always add the api key with the SmartRecruiters slug URL
- Keep careers_url on (careers|jobs).smartrecruiters.com when possible so detection works without api
- Validate provider entries at startup: provider: smartrecruiters must yield a slug
- Remember http:// sources are skipped by resolveSlug — use https
When it happens
Trigger: An entry with provider: smartrecruiters (explicit) but neither api nor a careers.smartrecruiters.com / jobs.smartrecruiters.com URL set; careers_url pointing at a branded custom domain (e.g. https://jobs.continental.com) with no api override; api/careers_url using http:// so resolveSlug skips it.
Common situations: Migrating a company entry whose careers page moved to a branded domain and forgetting to pin the slug; typo in the host so SR_CAREERS_HOSTS doesn't match; empty string or wrong type in api field; copy-pasting a tenant id into the wrong config key.
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
- smartrecruiters: invalid URL
- smartrecruiters: untrusted hostname
- smartrecruiters: URL must use HTTPS
- softgarden: cannot resolve widget URL for
- solidjobs: invalid URL
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/30a0a8ef6ebe9508.
Report an issue: GitHub.
Appendix: source
Thrown at providers/smartrecruiters.mjs:124
}
function resolveApiUrl(entry) {
const slug = resolveSlug(entry);
return slug ? buildPostingsUrl(slug, 0) : null;
}
/** @type {Provider} */
export default {
id: 'smartrecruiters',
detect(entry) {
const apiUrl = resolveApiUrl(entry);
return apiUrl ? { url: apiUrl } : null;
},
async fetch(entry, ctx) {
const slug = resolveSlug(entry);
if (!slug) throw new Error(`smartrecruiters: cannot derive API URL for ${entry.name}`);
// Honor the ctx.maxPages pagination hint (the portal health probe passes 1);
// a real sweep keeps the SR_MAX_PAGES safety cap.
const pageLimit = Math.min(
SR_MAX_PAGES,
Number.isInteger(ctx?.maxPages) && ctx.maxPages > 0 ? ctx.maxPages : Infinity,
);
const all = [];
for (let page = 0; page < pageLimit; page++) {
const apiUrl = buildPostingsUrl(slug, page * SR_PAGE_SIZE);
assertSmartRecruitersUrl(apiUrl);
const json = await ctx.fetchJson(apiUrl, { redirect: 'error' });
const parsed = parseSmartRecruitersResponse(json, entry.name);
if (parsed.length === 0) break;
all.push(...parsed);
if (parsed.length < SR_PAGE_SIZE) break; // last page (short)
}View on GitHub (pinned to aac998c7ed)