santifer/career-ops · error · Error
teamtailor: invalid URL
Error message
teamtailor: invalid URL: ${url} What it means
teamtailor's assertFeedUrl first requires the URL to parse as a valid URL. If new URL(url) throws (malformed, missing scheme, spaces, etc.), the provider throws this 'invalid URL' error naming the offending value.
Solutions
- Add https:// to the URL and remove whitespace/typos so it is a valid absolute URL
- Print or log the configured value to spot invisible characters or unexpanded variables
- Validate entries with node verify-pipeline.mjs before running scans
Example fix
// before careers_url: 'acme.teamtailor.com/jobs.rss' // after careers_url: 'https://acme.teamtailor.com/jobs.rss'
Defensive patterns
Strategy: validation
Validate before calling
let u;
try { u = new URL(entry.careers_url); } catch { throw new Error(`${entry.name}: careers_url is not a valid absolute URL`); }
if (u.protocol !== 'https:') throw new Error(`${entry.name}: careers_url must be https://`); Type guard
function isParseableHttpsUrl(u) {
try { return new URL(u).protocol === 'https:'; } catch { return false; }
} Try / catch
try {
offers = await provider.fetch(entry, ctx);
} catch (e) {
if (e.message.startsWith('teamtailor: invalid URL')) {
console.warn(`${entry.name}: fix careers_url — it does not parse as an absolute URL`);
return [];
}
throw e;
} Prevention
- Always include the https:// scheme in careers_url
- Paste URLs from the browser address bar, not from memory
- Check for unexpanded template variables and stray whitespace
- Lint portals.yml entries before committing
When it happens
Trigger: careers_url entered without a scheme (acme.teamtailor.com), with a typo (htps://), with embedded whitespace or a stray character; an env-substituted placeholder left unfilled (e.g. ${COMPANY}.teamtailor.com); an empty-ish string that slips past null checks.
Common situations: Hand-editing portals.yml with a missing https://; YAML folding a URL across lines; template variable not expanded; copy-paste introducing a trailing space or invisible character.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- comeet: URL path must be the careers-api endpoint
- consider: needs an https careers_url on a public host
- csod: cannot resolve careersite URL for
- flowxtra: untrusted hostname
- flowxtra: URL must use HTTPS
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/5722c6a123e4552b.
Report an issue: GitHub.
Appendix: source
Thrown at providers/teamtailor.mjs:39
// explicit `provider: teamtailor`. Either way the fetch is HTTPS-only with
// `redirect: 'error'`. Job `<link>`s (branded domains) are emitted as-is and
// never fetched.
const TEAMTAILOR_HOST_RE = /^([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\.teamtailor\.com$/i;
/**
* Validate a feed URL before fetching. Always HTTPS-only. The hostname is
* pinned to `*.teamtailor.com` for auto-detected entries; an explicit
* `provider: teamtailor` entry may use its configured branded host.
* @param {string} url
* @param {{ explicit?: boolean }} [opts]
*/
function assertFeedUrl(url, { explicit = false } = {}) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`teamtailor: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`teamtailor: URL must use HTTPS: ${url}`);
if (!explicit && !TEAMTAILOR_HOST_RE.test(parsed.hostname)) {
throw new Error(`teamtailor: untrusted hostname "${parsed.hostname}" — must be <slug>.teamtailor.com (or set "provider: teamtailor" to use a branded careers domain)`);
}
return url;
}
// Derive the RSS feed URL from a tracked_companies entry by normalizing any
// path on the configured host to /jobs.rss. Auto-detection (explicit=false)
// only claims *.teamtailor.com hosts; an explicit `provider: teamtailor` entry
// (explicit=true) may use a branded careers host. Returns null otherwise.
/**
* @param {import('./_types.js').PortalEntry} entry
* @param {{ explicit?: boolean }} [opts]
*/
function resolveFeedUrl(entry, { explicit = false } = {}) {
const raw = entry?.api || entry?.careers_url || '';View on GitHub (pinned to aac998c7ed)