santifer/career-ops · error · Error

smartrecruiters: invalid URL

Error message

smartrecruiters: invalid URL: ${url}

What it means

assertSmartRecruitersUrl throws this when the URL passed to it cannot be parsed by the URL constructor at all (empty string, missing scheme, spaces, malformed characters). The provider validates every API URL it builds before fetching, so a syntactically invalid URL is rejected immediately rather than producing a confusing fetch error downstream.

Solutions

  1. Print the offending url in the error and fix its syntax — add the https:// scheme if missing
  2. Ensure entry.api or entry.careers_url is a complete, well-formed URL in portals.yml
  3. Prefer the built-in buildPostingsUrl(slug) helper over hand-assembling API URLs
  4. Trim whitespace and remove invisible characters from configured URLs

Example fix

// before
entry.api = 'api.smartrecruiters.com/v1/companies/acme/postings'; // no scheme
// after
entry.api = 'https://api.smartrecruiters.com/v1/companies/acme/postings';
Defensive patterns

Strategy: validation

Validate before calling

function isValidHttpUrl(url) {
  if (typeof url !== 'string' || !url.trim()) return false;
  try { new URL(url); return true; } catch { return false; }
}
if (!isValidHttpUrl(entry.api)) throw new Error(`bad smartrecruiters api url: ${entry.api}`);

Type guard

const isUrl = (v) => {
  if (typeof v !== 'string') return false;
  try { new URL(v); return true; } catch { return false; }
  return true;
};

Try / catch

try {
  await srProvider.fetch(entry, ctx);
} catch (e) {
  if (String(e.message).includes('invalid URL')) {
    console.error(`Entry ${entry.name}: fix the URL syntax in portals.yml — scheme and characters`);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling fetch with an entry whose derived slug produces a malformed URL, or invoking assertSmartRecruitersUrl directly with user/config-supplied text like 'smartrecruiters.com/v1/...' (missing https://) or an undefined/null coerced to 'undefined'.

Common situations: A portals.yml api or careers_url missing the scheme; slug containing characters that break the URL (spaces, control chars); template placeholder left unfilled so the URL reads like a sentence instead of a URL; passing a non-string that stringifies to garbage.

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


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/afbb8898bf94e4bc. Report an issue: GitHub.

Appendix: source

Thrown at providers/smartrecruiters.mjs:70

 */
export function extractDescription(detail) {
  const sections = detail?.jobAd?.sections;
  if (!sections || typeof sections !== 'object') return '';
  const parts = [];
  for (const key of ['companyDescription', 'jobDescription', 'qualifications', 'additionalInformation']) {
    const text = sections[key]?.text;
    if (typeof text === 'string' && text.trim()) parts.push(text);
  }
  if (parts.length === 0) return '';
  return htmlToText(parts.join('\n'));
}

function assertSmartRecruitersUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`smartrecruiters: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`smartrecruiters: URL must use HTTPS: ${url}`);
  if (!ALLOWED_SMARTRECRUITERS_HOSTS.has(parsed.hostname)) {
    throw new Error(`smartrecruiters: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_SMARTRECRUITERS_HOSTS].join(', ')}`);
  }
  return url;
}

function resolveSlug(entry) {
  // entry.api takes precedence over careers_url (mirrors greenhouse/ashby) so a
  // branded page (e.g. https://jobs.continental.com) can stay as careers_url
  // while the SmartRecruiters slug is pinned via
  // api: https://careers.smartrecruiters.com/<slug> in portals.yml.
  for (const raw of [entry.api, entry.careers_url]) {
    if (typeof raw !== 'string' || !raw) continue;
    let parsed;
    try {
      parsed = new URL(raw);

View on GitHub (pinned to aac998c7ed)