santifer/career-ops · error · Error

lever: URL must use HTTPS

Error message

lever: URL must use HTTPS: ${url}

What it means

assertLeverUrl rejects any parsed URL whose protocol is not 'https:'. This enforces TLS for all Lever API traffic, protecting postings data and request metadata from plaintext interception or downgrade.

Solutions

  1. Use https:// in the URL/config entry.
  2. Search portals.yml and caller code for 'http://' and upgrade each to 'https://'.
  3. For local testing, use a mock that speaks https or inject a test double for fetch rather than bypassing the assertion.
  4. Confirm with new URL(u).protocol === 'https:' before the call.

Example fix

// before
assertLeverUrl('http://api.lever.co/v0/postings/acme');
// after
assertLeverUrl('https://api.lever.co/v0/postings/acme');
Defensive patterns

Strategy: validation

Validate before calling

function isHttpsUrl(u) { try { return new URL(u).protocol === 'https:'; } catch { return false; } }
if (!isHttpsUrl(url)) throw new Error(`lever endpoint must be https: ${url}`);

Type guard

function isHttpsUrlString(v) { if (typeof v !== 'string') return false; try { return new URL(v).protocol === 'https:'; } catch { return false; } }

Try / catch

try {
  provider.fetch(entry, ctx);
} catch (e) {
  if (e.message.startsWith('lever: URL must use HTTPS')) {
    console.warn(`Upgrading to https: ${e.message}`);
    return provider.fetch({ ...entry, url: entry.url.replace(/^http:/, 'https:') }, ctx);
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a http:// (or ftp:, file:, etc.) URL to assertLeverUrl or the lever provider fetch, e.g. 'http://api.lever.co/v0/postings/acme'.

Common situations: Older config with http:// endpoints, copied example snippets using http, or a local test harness URL written with http://localhost that reaches the production validator.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at providers/lever.mjs:24

// Handles both explicit `api:` URLs and auto-detection from `careers_url`.

const ALLOWED_LEVER_HOSTS = new Set(['api.lever.co', 'api.eu.lever.co']);

// The v0 postings endpoint returns the whole board in one response, with every
// description inlined, so a large board outgrows _http.mjs's 10s default:
// jobgether is 42.8 MB and aborted at 10s on its own (#4177). Same value and
// reasoning as ASHBY_TIMEOUT_MS, the other one-response board-wide ATS feed.
const LEVER_TIMEOUT_MS = 30_000;

/** @param {string} url */
function assertLeverUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`lever: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`lever: URL must use HTTPS: ${url}`);
  if (!ALLOWED_LEVER_HOSTS.has(parsed.hostname))
    throw new Error(`lever: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_LEVER_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://www.coalfire.com/careers) while still pinning
  // the Lever postings board (mirrors greenhouse's api: precedence).
  if (entry.api) {
    assertLeverUrl(entry.api);
    return entry.api;
  }
  let url;
  try {
    url = new URL(entry.careers_url || '');
  } catch {

View on GitHub (pinned to e7abd431fc)