{"record":{"id":"138c9463cd637090","repo":"santifer/career-ops","slug":"lever-cannot-derive-api-url-for-entry-name","errorCode":null,"errorMessage":"lever: cannot derive API URL for ${entry.name}","messagePattern":"lever: cannot derive API URL for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/lever.mjs","lineNumber":86,"sourceCode":"  return merged.join('; ');\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'lever',\n\n  detect(entry) {\n    try {\n      const apiUrl = resolveApiUrl(entry);\n      return apiUrl ? { url: apiUrl } : null;\n    } catch {\n      return null;\n    }\n  },\n\n  async fetch(entry, ctx) {\n    const apiUrl = resolveApiUrl(entry);\n    if (!apiUrl) throw new Error(`lever: cannot derive API URL for ${entry.name}`);\n    assertLeverUrl(apiUrl);\n    const json = await ctx.fetchJson(apiUrl, { redirect: 'error', timeoutMs: LEVER_TIMEOUT_MS });\n    if (!Array.isArray(json)) return [];\n    return json.map(j => ({\n      title: j.text || '',\n      url: j.hostedUrl || '',\n      company: entry.name,\n      location: resolveLocation(j.categories),\n      // Lever's v0 postings list ships the full description for free (same\n      // payload, no per-job request) — enables scan.mjs content_filter.\n      description: typeof j.descriptionPlain === 'string' ? j.descriptionPlain : '',\n      postedAt: typeof j.createdAt === 'number' ? j.createdAt : undefined,\n    }));\n  },\n};\n","sourceCodeStart":68,"sourceCodeEnd":102,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/providers/lever.mjs#L68-L102","documentation":"The Lever provider can only scrape a company if it can map the portal entry to a Lever postings API URL. It uses an explicit `api:` field if present, otherwise it auto-detects from a careers_url matching jobs.(eu.)lever.co/<slug>. If neither yields a URL, fetch() throws this error naming the entry.","triggerScenarios":"Calling lever.fetch(entry) where entry has no `api` field and its careers_url is (a) not parseable as a URL, (b) not on a jobs.lever.co / jobs.eu.lever.co host, or (c) on a Lever board host but with an empty path (no slug).","commonSituations":"portals.yml entry pointing at the company's corporate careers page (e.g. https://www.coalfire.com/careers) without an `api:` override; a typo'd Lever host like jobs.lever.io; a board URL like https://jobs.lever.co/co with a missing or empty slug segment; a company that migrated off Lever.","solutions":["Add an explicit `api:` field to the entry, e.g. api: https://api.lever.co/v0/postings/<slug>, alongside a human-facing careers_url.","Fix careers_url to a valid Lever board URL of the form https://jobs.lever.co/<slug> (or jobs.eu.lever.co), with a non-empty slug.","Verify the company actually hosts its board on Lever (check the careers page for a jobs.lever.co link); if it uses Greenhouse/Ashby/etc., switch the entry to that provider.","Confirm detect() returned non-null before calling fetch(): detect() returns null for the same condition instead of throwing."],"exampleFix":"// before (portals.yml)\n- name: Coalfire\n  careers_url: https://www.coalfire.com/careers\n// after\n- name: Coalfire\n  careers_url: https://www.coalfire.com/careers\n  api: https://api.lever.co/v0/postings/coalfire","handlingStrategy":"validation","validationCode":"const u = new URL(entry.careers_url || '');\nconst isLever = /^jobs\\.(?:eu\\.)?lever\\.co$/.test(u.hostname) && u.pathname.split('/').filter(Boolean).length > 0;\nif (!entry.api && !isLever) throw new Error(`entry ${entry.name} is not a Lever board; set api: https://api.lever.co/v0/postings/<slug>`);","typeGuard":"function hasLeverApiUrl(entry) {\n  if (entry.api) return true;\n  try {\n    const u = new URL(entry.careers_url || '');\n    return /^jobs\\.(?:eu\\.)?lever\\.co$/.test(u.hostname) && !!u.pathname.split('/').filter(Boolean)[0];\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  await leverProvider.fetch(entry, ctx);\n} catch (e) {\n  if (String(e.message).startsWith('lever: cannot derive API URL')) {\n    console.warn(`Skipping ${entry.name}: not a Lever board — add api: or fix careers_url`);\n    return [];\n  }\n  throw e;\n}","preventionTips":["Always pair a corporate careers_url with an explicit api: field when the board is Lever.","Validate portals.yml entries with provider.detect() before running scans.","Check the careers page for a jobs.lever.co link to confirm the ATS vendor before adding the entry.","Watch for companies migrating ATS vendors — detect() returning null is the early signal."],"tags":["config","url","provider"],"backgroundTag":"invalid-url","analyzedSha":"e7abd431fce9348a95261acac9e0c14779c35df8","analyzedAt":"2026-09-22T13:19:01.448Z","contentChangedAt":"2026-09-22T13:19:01.448Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}