{"record":{"id":"d2ae3fe030232a12","repo":"santifer/career-ops","slug":"lever-invalid-url-url","errorCode":null,"errorMessage":"lever: invalid URL: ${url}","messagePattern":"lever: invalid URL: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/lever.mjs","lineNumber":16,"sourceCode":"// @ts-check\n/** @typedef {import('./_types.js').Provider} Provider */\n\n// Lever provider — hits the public postings endpoint.\n// Auto-detects from careers_url via jobs.(eu.)?lever.co/<slug>.\n// Handles both explicit `api:` URLs and auto-detection from `careers_url`.\n\nconst ALLOWED_LEVER_HOSTS = new Set(['api.lever.co', 'api.eu.lever.co']);\n\n/** @param {string} url */\nfunction assertLeverUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`lever: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`lever: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_LEVER_HOSTS.has(parsed.hostname))\n    throw new Error(`lever: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_LEVER_HOSTS].join(', ')}`);\n  return url;\n}\n\n/** @param {import('./_types.js').PortalEntry} entry */\nfunction resolveApiUrl(entry) {\n  // Explicit api: wins — lets an entry keep a human-facing corporate\n  // careers_url (e.g. https://www.coalfire.com/careers) while still pinning\n  // the Lever postings board (mirrors greenhouse's api: precedence).\n  if (entry.api) {\n    assertLeverUrl(entry.api);\n    return entry.api;\n  }\n  let url;\n  try {","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/lever.mjs#L1-L34","documentation":"assertLeverUrl mirrors the Larajobs pattern: it first attempts new URL(url). If the constructor throws — the string is not an absolute parseable URL — this error fires. It guards the Lever postings fetch and the api: auto-detection path.","triggerScenarios":"entry.api or a careers_url-derived value passed to assertLeverUrl is empty, relative, missing a scheme, or otherwise rejected by the URL constructor. resolveApiUrl calls assertLeverUrl on entry.api when present, and on the derived api.lever.co URL.","commonSituations":"portals.yml api: field is blank or missing https://; a slug-based derivation produced an incomplete URL; whitespace/control characters were pasted into the config.","solutions":["Read the value in the message — it is the exact unparseable string.","Set api: to a full URL such as https://api.lever.co/v0/postings/<slug>.","Strip whitespace/newlines from the config value.","If relying on auto-detection from careers_url, ensure careers_url is a full https URL containing lever.co."],"exampleFix":"// before (portals.yml)\napi: lever.co/postings/acme\n\n// after\napi: https://api.lever.co/v0/postings/acme","handlingStrategy":"validation","validationCode":"import { URL } from 'node:url';\nexport function isValidAbsoluteUrl(value) {\n  if (typeof value !== 'string' || !value) return false;\n  try { new URL(value); return true; } catch { return false; }\n}\n// Pre-validate before the provider runs:\n// if (!isValidAbsoluteUrl(entry.api)) failConfig(`lever api invalid for ${entry.name}`);","typeGuard":"/** @param {string} url */\nfunction isParseableUrl(url) {\n  try { new URL(url); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  assertLeverUrl(entry.api);\n} catch (err) {\n  console.warn(`lever entry ${entry.name} has bad api URL: ${err.message}`);\n  entry.disabled = true;\n}","preventionTips":["Pre-flight every lever entry's api: field in the config loader.","Always specify the full https://api.lever.co/... URL rather than relying on partial strings.","For auto-detection, ensure careers_url is a full https URL containing lever.co."],"tags":["url-validation","config","lever","ssrf-guard"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}