{"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":61,"sourceCode":"  return `https://api.${host[1]}/v0/postings/${slug}`;\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' });\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: j.categories?.location || '',\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":43,"sourceCodeEnd":77,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/lever.mjs#L43-L77","documentation":"In fetch(), resolveApiUrl returns null/empty when it cannot produce an API URL — i.e. entry has neither an explicit api: field nor a careers_url it can derive a Lever slug from. Because detect() already swallows resolveApiUrl's exceptions and returns null, reaching fetch() with no URL means the entry was selected despite yielding no API endpoint.","triggerScenarios":"An entry reaches fetch() (the orchestrator called it directly, or detect returned a truthy {url} that later became empty) but entry.api is unset AND entry.careers_url does not match the jobs.(eu.)?lever.co/<slug> pattern. entry.name is interpolated so you know which portal entry is at fault.","commonSituations":"A portals.yml entry has provider: lever but neither api: nor a lever.co careers_url; the careers_url host pattern changed; detect() and fetch() were called out of order; the entry name has trailing characters that broke slug extraction.","solutions":["Add an explicit api: https://api.lever.co/v0/postings/<slug> to the entry in portals.yml.","Or set careers_url: https://jobs.lever.co/<slug> so the slug can be auto-derived.","Confirm the entry should actually use provider: lever (it may belong to greenhouse/ashby).","Ensure the orchestrator only calls fetch() on entries for which detect() returned non-null."],"exampleFix":"# before (portals.yml)\n- name: Acme\n  provider: lever\n  careers_url: https://www.acme.com/careers\n\n# after\n- name: Acme\n  provider: lever\n  api: https://api.lever.co/v0/postings/acme\n  careers_url: https://www.acme.com/careers","handlingStrategy":"validation","validationCode":"// Verify detect() yields an API URL before calling fetch().\n// Provider.detect() returns { url } or null — never call fetch on a null detect.\nconst detected = provider.detect(entry);\nif (!detected || !detected.url) {\n  throw new Error(`config: lever entry ${entry.name} has no api URL (set api: or a lever.co careers_url)`);\n}","typeGuard":"/** @param {import('./providers/_types.js').Provider} provider @param {any} entry */\nfunction canFetchLever(provider, entry) {\n  return Boolean(provider.detect?.(entry)?.url);\n}","tryCatchPattern":"try {\n  const detected = provider.detect(entry);\n  if (!detected?.url) throw new Error(`lever: cannot derive API URL for ${entry.name}`);\n  const jobs = await provider.fetch(entry, ctx);\n} catch (err) {\n  console.warn(`skipping ${entry.name}: ${err.message}`);\n}","preventionTips":["Every provider: lever entry should carry either an explicit api: URL or a careers_url on jobs.(eu.)?lever.co.","Gate fetch() behind a truthy detect() in the orchestrator.","Validate the lever entry config at load time so missing URLs fail fast with a clear message."],"tags":["config","lever","missing-url","provider-config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}