santifer/career-ops · error · Error

ashby: invalid URL: ${url}

Error message

ashby: invalid URL: ${url}

What it means

ashby validates every URL through `assertAshbyUrl` before fetching. The 'invalid URL' variant fires when `new URL(url)` throws — the input is not parseable as an absolute URL. This is the first of three sequential guards (parse → https → host allowlist).

Source

Thrown at providers/ashby.mjs:86

  // Ensure correct ordering (min <= max)
  const resolvedMin = /** @type {number} */ (min ?? max);
  const resolvedMax = /** @type {number} */ (max ?? min);
  return {
    min: Math.min(resolvedMin, resolvedMax),
    max: Math.max(resolvedMin, resolvedMax),
    currency: currency.toUpperCase(),
  };
}

const ALLOWED_ASHBY_HOSTS = new Set(['api.ashbyhq.com']);

/** @param {string} url */
function assertAshbyUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`ashby: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`ashby: URL must use HTTPS: ${url}`);
  if (!ALLOWED_ASHBY_HOSTS.has(parsed.hostname))
    throw new Error(`ashby: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_ASHBY_HOSTS].join(', ')}`);
  return url;
}

/** @param {import('./_types.js').PortalEntry} entry */
function resolveApiUrl(entry) {
  // Explicit api: wins — lets an entry keep a human-facing corporate
  // careers_url (e.g. https://openai.com/careers) while still pinning the
  // Ashby posting-api board (mirrors greenhouse's api: precedence).
  if (entry.api) {
    assertAshbyUrl(entry.api);
    return entry.api;
  }
  const url = entry.careers_url || '';
  const match = url.match(/jobs\.ashbyhq\.com\/([^/?#]+)/);

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Set `api:` to a fully-qualified `https://api.ashbyhq.com/...` URL.
  2. Validate entry URLs at config load time so the error surfaces next to the offending row.
  3. Check for whitespace or template interpolation producing an empty/mangled scheme.

Example fix

# before
- name: Acme
  api: api.ashbyhq.com/posting-api?compId=acme

# after
- name: Acme
  api: https://api.ashbyhq.com/posting-api?compId=acme
Defensive patterns

Strategy: validation

Validate before calling

// Validate entry.api is an absolute URL before passing to the provider
function isValidAbsoluteUrl(u) {
  try { new URL(u); return true; } catch { return false; }
}
if (entry.api && !isValidAbsoluteUrl(entry.api)) {
  throw new Error(`ashby: entry ${entry.name} has a malformed api URL: ${entry.api}`);
}

Prevention

When it happens

Trigger: `assertAshbyUrl(url)` receives a string `new URL()` cannot parse: a schemeless host (`api.ashbyhq.com/...`), a protocol-relative URL, an empty string, or a value with illegal characters. Typically called on `entry.api` or a derived URL in `resolveApiUrl`.

Common situations: An entry's `api:` field is set without a scheme, a careers_url that gets parsed drops characters, or a templated/config-injected value is malformed.

Related errors


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