santifer/career-ops · error · Error

landingjobs: invalid URL: ${url}

Error message

landingjobs: invalid URL: ${url}

What it means

Thrown by assertLandingUrl() when new URL(url) throws — the input is not a parseable URL. This is the first guard in the landing.jobs URL validator (protocol and hostname checks follow). The validator is the chokepoint for any landing.jobs URL the provider builds or accepts.

Source

Thrown at providers/landingjobs.mjs:26

//     created_at, type, tags, gross_salary_low, gross_salary_high, ... }
//
// NOTE: the v1 feed carries no company-name field — the employer slug only
// appears in the posting URL path (`https://landing.jobs/at/<slug>/<job>`), so
// `company` is derived best-effort from that slug (humanized) and falls back to
// the portal entry name. The whole active set is returned in one call.
//
// Wire in via a `job_boards:` entry with `provider: landingjobs`.

const FEED_URL = 'https://landing.jobs/api/v1/jobs';
const TRUSTED_HOST = 'landing.jobs';

/** @param {string} url */
function assertLandingUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`landingjobs: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`landingjobs: URL must use HTTPS: ${url}`);
  if (parsed.hostname !== TRUSTED_HOST) {
    throw new Error(`landingjobs: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST}`);
  }
  return url;
}

// NaN-safe Date.parse.
function toEpochMs(value) {
  if (typeof value !== 'string' || !value) return undefined;
  const parsed = Date.parse(value);
  return Number.isNaN(parsed) ? undefined : parsed;
}

/**
 * Derive a best-effort company name from a Landing.jobs posting URL.
 * Posting URLs are `https://landing.jobs/at/<slug>/<job>`; the `<slug>` is

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Set the url field to a full https://landing.jobs/... URL.
  2. Verify the YAML entry is quoted correctly and is a non-empty string.
  3. Confirm the value is a string before it reaches the validator.

Example fix

# before
acme:
  provider: landingjobs
  api: landing.jobs/api/v1/jobs

# after
acme:
  provider: landingjobs
  api: https://landing.jobs/api/v1/jobs
Defensive patterns

Strategy: validation

Validate before calling

function isParseableUrl(url) {
  try { new URL(url); return true; } catch { return false; }
}
for (const e of landingjobsEntries) {
  const u = e.api || e.careers_url;
  if (typeof u !== 'string' || !u.trim() || !isParseableUrl(u)) {
    console.warn(`landingjobs ${e.name}: url missing or unparseable`);
  }
}

Type guard

/** @param {string} url @returns {boolean} */
function isParseableUrl(url) {
  try { new URL(url); return true; } catch { return false; }
}

Prevention

When it happens

Trigger: A careers_url or api field that is empty, whitespace-only, lacks a scheme, or contains characters that break the URL parser (spaces, unbalanced brackets); a non-string value passed where a URL string is expected.

Common situations: A portals.yml entry with a blank or unquoted url; a copy-paste that dropped the https:// prefix; a YAML parsing quirk that produced a non-string.

Related errors


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