{"record":{"id":"a06a50c7ba0ead2e","repo":"santifer/career-ops","slug":"bamboohr-cannot-derive-api-url-for-entry-name","errorCode":null,"errorMessage":"bamboohr: cannot derive API URL for ${entry.name}","messagePattern":"bamboohr: cannot derive API URL for (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/bamboohr.mjs","lineNumber":67,"sourceCode":"    return null;\n  }\n  if (parsed.protocol !== 'https:') return null;\n  if (!BAMBOOHR_HOST_RE.test(parsed.hostname)) return null;\n  return `https://${parsed.hostname}`;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'bamboohr',\n\n  detect(entry) {\n    const origin = resolveOrigin(entry);\n    return origin ? { url: `${origin}/careers/list` } : null;\n  },\n\n  async fetch(entry, ctx) {\n    const origin = resolveOrigin(entry);\n    if (!origin) throw new Error(`bamboohr: cannot derive API URL for ${entry.name}`);\n    const apiUrl = `${origin}/careers/list`;\n    assertBambooHRUrl(apiUrl);\n    // redirect:'error' + the host check above keep the final hostname pinned to\n    // the tenant — a server-side redirect can't bounce us off-domain (SSRF).\n    const json = /** @type {any} */ (await ctx.fetchJson(apiUrl, { redirect: 'error' }));\n    return parseBambooHRResponse(json, entry.name, origin);\n  },\n};\n\n/**\n * Parse a BambooHR `/careers/list` response. Exported for unit tests.\n *\n * BambooHR returns:\n *   { meta: {...}, result: [{ id, jobOpeningName,\n *       location: { city?, state? }, isRemote?, employmentStatusLabel? }] }\n *\n * - url: built as `<origin>/careers/<id>` — matches the public\n *   `jobOpeningShareUrl`. Rows without a non-empty `id` are dropped (no stable","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/bamboohr.mjs#L49-L85","documentation":"BambooHR's fetch() calls resolveOrigin(entry) to get the tenant origin `https://<tenant>.bamboohr.com`; if it returns null the provider cannot build the `/careers/list` endpoint and throws. Like Ashby's equivalent, it refuses to request anything when config is insufficient.","triggerScenarios":"Calling fetch(entry) with an entry lacking a usable origin — no api/careers URL matching the BambooHR tenant pattern, or a blank tenant field — so resolveOrigin returns null.","commonSituations":"portals.yml entry where only the corporate site is listed, tenant subdomain typo making the host pattern fail, or entries migrated from another provider without a BambooHR origin field.","solutions":["Add/set the entry's origin or api field to `https://<tenant>.bamboohr.com`.","Fix the tenant subdomain so the hostname matches `<tenant>.bamboohr.com` (this also makes resolveOrigin succeed).","Verify the company actually hosts jobs on BambooHR; otherwise move the entry to the right provider.","Validate entries at load time: reject entries where resolveOrigin(entry) is null with a clear config warning."],"exampleFix":"// before\n{ name: 'ExampleCo', careers_url: 'https://exampleco.com/careers' }\n// fetch throws: cannot derive API URL\n\n// after\n{ name: 'ExampleCo', api: 'https://exampleco.bamboohr.com/careers/list' }","handlingStrategy":"fallback","validationCode":"function hasBambooOrigin(entry) {\n  return resolveOrigin(entry) != null;\n}","typeGuard":"function isBambooReadyEntry(entry) {\n  return typeof entry?.name === 'string' && typeof resolveOrigin(entry) === 'string';\n}","tryCatchPattern":"try {\n  return await bamboohrProvider.fetch(entry, ctx);\n} catch (err) {\n  if (/cannot derive API URL/.test(err.message)) {\n    console.warn(`Entry ${entry.name}: missing <tenant>.bamboohr.com origin in config`);\n    return null;\n  }\n  throw err;\n}","preventionTips":["Populate the api/origin field with `https://<tenant>.bamboohr.com` for every BambooHR entry.","Verify the tenant subdomain spelling — resolveOrigin fails on any host-pattern mismatch.","Confirm the company genuinely hosts jobs on BambooHR before assigning it to this provider.","Validate origins for all entries at config load and surface failures early."],"tags":["config","bamboohr","api-url","provider"],"backgroundTag":"missing-config-value","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}