santifer/career-ops · error · Error

recruitee: invalid URL: ${url}

Error message

recruitee: invalid URL: ${url}

What it means

assertRecruiteeUrl throws this when new URL(url) itself raises — i.e. the string is not a parseable absolute URL at all (no scheme, invalid characters, unbalanced brackets). This is the first guard in the chain, before the HTTPS and hostname checks. It means the URL is structurally broken, not merely wrong-hosted.

Source

Thrown at providers/recruitee.mjs:17

// @ts-check
/** @typedef {import('./_types.js').Provider} Provider */

// Recruitee provider — hits the public per-tenant offers API.
// Auto-detects from careers_url pattern `https://<slug>.recruitee.com`.
// Per-tenant subdomains are the variable part — SSRF defence uses a
// regex match on `<safe-slug>.recruitee.com` rather than a static
// allowlist.

const RECRUITEE_HOST_RE = /^[a-z0-9][a-z0-9-]*\.recruitee\.com$/;

function assertRecruiteeUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`recruitee: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`recruitee: URL must use HTTPS: ${url}`);
  if (!RECRUITEE_HOST_RE.test(parsed.hostname)) {
    throw new Error(`recruitee: untrusted hostname "${parsed.hostname}" — must match <slug>.recruitee.com`);
  }
  return url;
}

function resolveApiUrl(entry) {
  const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
  if (!raw) return null;
  let parsed;
  try {
    parsed = new URL(raw);
  } catch {
    return null;
  }
  if (parsed.protocol !== 'https:') return null;

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Prepend 'https://' if the hostname is present but the scheme is missing.
  2. Validate the URL string with new URL() or a URL-pattern check before passing it into the provider.
  3. Check for stray characters, leading/trailing whitespace, or smart quotes in the source config.
  4. Ensure the value is actually a string and not null/undefined/number.

Example fix

// before
const url = entry.careers_url; // 'acme.recruitee.com'
assertRecruiteeUrl(url);
// after
const url = entry.careers_url.startsWith('http') ? entry.careers_url : `https://${entry.careers_url}`;
Defensive patterns

Strategy: validation

Validate before calling

function ensureAbsoluteHttpsUrl(raw) {
  if (typeof raw !== 'string' || !raw.trim()) return null;
  let s = raw.trim();
  if (!/^https?:\/\//i.test(s)) s = 'https://' + s;
  try { new URL(s); return s; } catch { return null; }
}
const safe = ensureAbsoluteHttpsUrl(entry.careers_url);
if (!safe) { console.warn(`skip ${entry.name}: unparseable URL`); }

Type guard

null

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (e) {
  if (/recruitee: invalid URL/.test(e.message)) {
    console.warn(`[skip] ${entry.name}: structurally invalid URL — ${entry.careers_url}`);
  } else throw e;
}

Prevention

When it happens

Trigger: A URL string like 'acme.recruitee.com' (no protocol), 'https://' (bare scheme), 'ht!tps://x.recruitee.com', or a non-string accidentally coerced. Any value that makes the URL constructor throw.

Common situations: The careers_url was stored without a protocol; a template/format string produced an empty or partial URL; user input was not trimmed or validated before reaching the provider.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/31072749a861b73b. Report an issue: GitHub.