santifer/career-ops · error · Error
solidjobs: careers_url required
Error message
solidjobs: careers_url required
What it means
The first statement in fetch reads entry.careers_url and throws if it is falsy. This fires before assertUrl, so the value is missing entirely (undefined, null, empty string) rather than malformed. The provider requires a careers_url — there is no api: fallback for solidjobs.
Source
Thrown at providers/solidjobs.mjs:62
detect(entry) {
const url = entry.careers_url || '';
try {
const parsed = new URL(url);
if (parsed.hostname === 'solid.jobs' && parsed.pathname.startsWith('/public-api/offers/'))
return { url };
} catch {}
return null;
},
/**
* Fetches and normalizes job offers from the SolidJobs public API.
* * @param {{ careers_url?: string, name: string }} entry - The configuration entry being processed.
* @param {{ fetchJson: (url: string, opts?: { redirect?: 'error'|'follow'|'manual' }) => Promise<any> }} ctx - HTTP context.
* @returns {Promise<Array<{title: string, url: string, company: string, location: string}>>} Array of parsed job offers.
*/
async fetch(entry, ctx) {
const url = entry.careers_url;
if (!url) throw new Error('solidjobs: careers_url required');
assertUrl(url);
// redirect:'error' prevents SSRF via server-side redirects
const json = await ctx.fetchJson(url, { redirect: 'error' });
if (!json || !Array.isArray(json.jobs)) {
throw new Error(`solidjobs: unexpected API response — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`);
}
/** @type {Array<{ title?: string, url?: string, company?: string, locations?: string | string[] }>} */
const jobs = json.jobs;
return jobs
.filter(j => j && typeof j === 'object' && typeof j.url === 'string' && j.url.trim() !== '')
.map(j => ({
title: j.title || '',
url: /** @type {string} */ (j.url || '').trim(),
company: j.company || entry.name,
location: Array.isArray(j.locations) ? j.locations.join(', ') : (typeof j.locations === 'string' ? j.locations : ''),
}));View on GitHub (pinned to 9b17a8ac97)
Solutions
- Add careers_url: https://solid.jobs/public-api/offers/<division> to the entry
- Check YAML indentation — careers_url must be a sibling key under the entry, not nested under another field
- Confirm the key is spelled exactly careers_url (not career_url or url)
Example fix
# before - name: SolidJobs IT provider: solidjobs # after - name: SolidJobs IT provider: solidjobs careers_url: https://solid.jobs/public-api/offers/it
Defensive patterns
Strategy: validation
Validate before calling
// Guard the missing-field case before fetch.
if (typeof entry.careers_url !== 'string' || !entry.careers_url.trim()) {
console.warn(`${entry.name}: solidjobs entry missing careers_url`);
} Type guard
/** @param {unknown} e @returns {e is {careers_url: string}} */
function hasCareersUrl(e) {
return typeof e?.careers_url === 'string' && e.careers_url.trim() !== '';
} Prevention
- Lint provider: solidjobs entries for a non-empty careers_url.
- Check YAML indentation so careers_url is a sibling key.
When it happens
Trigger: A portals.yml entry was added with provider: solidjobs (or was auto-detected) but the careers_url field was omitted, left blank, or mis-indented in YAML so it parsed as null.
Common situations: YAML indentation error dropped the key; the entry was templated from another provider and the URL line was forgotten; an empty string was copied in.
Related errors
- solidjobs: invalid URL: ${url}
- solidjobs: URL must use HTTPS: ${url}
- solidjobs: untrusted hostname "${parsed.hostname}" — must be
- solidjobs: URL path must start with /public-api/offers/: ${u
- teamtailor: cannot derive jobs.rss URL for ${fallbackCompany
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/70097fb5cb190531.
Report an issue: GitHub.