santifer/career-ops · error · Error
softgarden: cannot resolve widget URL for
Error message
softgarden: cannot resolve widget URL for ${entry.name} What it means
The softgarden provider's fetch calls resolveWidgetUrl(entry), which must produce a {tenant}.softgarden.io widget URL from entry.api or entry.careers_url. If the configured URL is unparseable, non-HTTP(S), or not on a *.softgarden.io host, fetch cannot proceed and throws this error — the entry is not recognizable as a softgarden tenant.
Solutions
- Set api (or careers_url) to the tenant widget URL, e.g. https://renk-group.softgarden.io/de/widgets/jobs — an explicit /widgets/jobs path passes through unchanged
- Ensure the host is exactly softgarden.io or ends with .softgarden.io; fix typos/TLD
- Use https:// or http:// only — other schemes are rejected by resolveWidgetUrl
- If the careers site is branded, keep careers_url for humans and put the softgarden.io widget URL in api
Example fix
// before - name: RENK careers_url: 'https://careers.renk-group.com' # not a softgarden.io host // after - name: RENK api: 'https://renk-group.softgarden.io/de/widgets/jobs'
Defensive patterns
Strategy: validation
Validate before calling
function canResolveSoftgardenWidget(entry) {
const raw = entry?.api || entry?.careers_url || '';
try {
const u = new URL(raw);
if (u.protocol !== 'https:' && u.protocol !== 'http:') return false;
const host = u.host.toLowerCase();
return host === 'softgarden.io' || host.endsWith('.softgarden.io');
} catch { return false; }
}
if (!canResolveSoftgardenWidget(entry)) console.error(`${entry.name}: api/careers_url must be a *.softgarden.io URL`); Type guard
const isSoftgardenUrl = (raw) => {
try {
const u = new URL(String(raw));
const host = u.host.toLowerCase();
return (u.protocol === 'https:' || u.protocol === 'http:') &&
(host === 'softgarden.io' || host.endsWith('.softgarden.io'));
} catch { return false; }
}; Try / catch
try {
await softgardenProvider.fetch(entry, ctx);
} catch (e) {
if (String(e.message).includes('cannot resolve widget URL')) {
console.error(`Entry ${entry.name}: set api to https://<tenant>.softgarden.io/<lang>/widgets/jobs`);
} else throw e;
} Prevention
- Point api at the tenant widget URL (https://{tenant}.softgarden.io/{lang}/widgets/jobs); any *.softgarden.io path is normalized to the widget automatically
- Check the TLD carefully — .softgarden.com will not resolve
- Keep careers_url on a softgarden.io host or use api for the widget while careers_url stays branded
- Run config validation before sweeps so unresolvable entries are caught early
When it happens
Trigger: A portals entry with provider: softgarden whose api/careers_url is missing, empty, on a branded domain (e.g. careers.renk-group.com) with no softgarden.io URL, or uses another scheme (ftp:, about:); a typo like renk-group.softgarden.com (wrong TLD).
Common situations: Company moved its careers page to a custom domain while the widget still lives on softgarden.io; misconfigured host TLD; leaving the api field blank expecting detection from another field; pasting the iframe's outer page URL instead of the widget URL (the resolver actually tolerates that, but only if the host is *.softgarden.io).
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- senjob: untrusted hostname
- smartrecruiters: invalid URL
- smartrecruiters: untrusted hostname
- smartrecruiters: URL must use HTTPS
- solidjobs: invalid URL
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/e8971aaae5887763.
Report an issue: GitHub.
Appendix: source
Thrown at providers/softgarden.mjs:133
postedAt: parseSoftgardenDate(dateM ? clean(dateM[1]) : undefined),
});
}
return out;
}
/** @type {Provider} */
export default {
id: 'softgarden',
detect(entry) {
const url = entry.api || entry.careers_url || '';
if (typeof url !== 'string') return null;
return resolveWidgetUrl({ api: url }) ? { url } : null;
},
async fetch(entry, ctx) {
const widgetUrl = resolveWidgetUrl(entry);
if (!widgetUrl) throw new Error(`softgarden: cannot resolve widget URL for ${entry.name}`);
const html = await ctx.fetchText(widgetUrl, { headers: { accept: 'text/html' } });
const rows = parseWidget(html, widgetUrl);
const jobs = [];
for (const row of rows) {
const job = { title: row.title, url: row.url, company: entry.name, location: row.location };
if (typeof row.postedAt === 'number') job.postedAt = row.postedAt;
jobs.push(job);
if (jobs.length >= MAX_JOBS) break;
}
return jobs;
},
};
View on GitHub (pinned to aac998c7ed)