{"record":{"id":"6056ba645eb62c41","repo":"santifer/career-ops","slug":"eightfold-cannot-derive-api-url-for-entry-name","errorCode":null,"errorMessage":"eightfold: cannot derive API URL for ${entry.name}","messagePattern":"eightfold: cannot derive API URL for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/eightfold.mjs","lineNumber":277,"sourceCode":"  return Number.isFinite(hint) && hint > 0 ? Math.min(fromEntry, Math.floor(hint)) : fromEntry;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'eightfold',\n\n  detect(entry) {\n    try {\n      const tenant = resolveTenant(entry);\n      return tenant ? { url: buildApiUrl(tenant, 0, PAGE_SIZE) } : null;\n    } catch {\n      return null;\n    }\n  },\n\n  async fetch(entry, ctx) {\n    const tenant = resolveTenant(entry);\n    if (!tenant) throw new Error(`eightfold: cannot derive API URL for ${entry.name}`);\n\n    const maxPages = resolveMaxPages(entry, ctx);\n    const all = [];\n    /** @type {number|null} */\n    let total = null;\n\n    for (let page = 0; page < maxPages; page++) {\n      const start = page * PAGE_SIZE;\n      const apiUrl = buildApiUrl(tenant, start, PAGE_SIZE);\n      assertEightfoldUrl(apiUrl); // SSRF guard before every fetch\n      if (page > 0) await sleep(INTER_PAGE_DELAY_MS, ctx);\n\n      const json = /** @type {any} */ (await fetchJsonWithRetry(\n        /** @type {any} */ (ctx),\n        apiUrl,\n        {\n          // redirect:'error' prevents SSRF via a server-side redirect; with\n          // assertEightfoldUrl above it guarantees the final hostname stays","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/eightfold.mjs#L259-L295","documentation":"The primary eightfold misconfiguration error: resolveTenant(entry) returned null, meaning neither entry.api nor entry.careers_url is an https URL whose hostname matches /^[a-z0-9-]+\\.eightfold\\.ai$/i. Eightfold hosts branded career sites for enterprises, but the API is host-pinned to *.eightfold.ai, so a branded CNAME (careers.<company>.com) is deliberately NOT accepted — the entry must point at the canonical tenant host. An optional entry.domain overrides the ?domain= query param for multi-brand tenants.","triggerScenarios":"entry.api/entry.careers_url is missing, unparseable, non-https, or its hostname is not <tenant>.eightfold.ai (e.g. the branded careers.<company>.com CNAME). resolveTenant checks both entry.api then entry.careers_url and returns null only if neither matches.","commonSituations":"Configuring the branded careers.bayer.com CNAME instead of bayer.eightfold.ai; using an http URL; omitting both api: and careers_url; a multi-brand tenant where the canonical host is correct but a typo breaks the regex.","solutions":["Set api: (or careers_url:) to the canonical eightfold.ai tenant URL, e.g. https://bayer.eightfold.ai/careers. For multi-brand tenants, add domain: <brand> to scope the board.","If you only have the branded CNAME, resolve it to its eightfold.ai origin (the CNAME target) and configure that.","Gate with provider.detect(entry) (returns null when resolveTenant does) before fetch()."],"exampleFix":"# before — branded CNAME, rejected\n- name: Bayer\n  provider: eightfold\n  careers_url: https://careers.bayer.com\n\n# after — canonical eightfold.ai tenant host\n- name: Bayer\n  provider: eightfold\n  api: https://bayer.eightfold.ai/careers","handlingStrategy":"validation","validationCode":"import eightfold from './providers/eightfold.mjs';\nif (!eightfold.detect(entry)) {\n  // neither api: nor careers_url is https://<tenant>.eightfold.ai — set api: to the canonical host\n}","typeGuard":"/** True when entry resolves to an Eightfold tenant host. */\nfunction isEightfoldEntry(entry) {\n  for (const raw of [entry?.api, entry?.careers_url]) {\n    if (typeof raw !== 'string' || !raw) continue;\n    try {\n      const p = new URL(raw);\n      if (p.protocol === 'https:' && /^[a-z0-9-]+\\.eightfold\\.ai$/i.test(p.hostname)) return true;\n    } catch { /* try next */ }\n  }\n  return false;\n}","tryCatchPattern":"try { await eightfold.fetch(entry, ctx); }\ncatch (e) {\n  if (/^eightfold: cannot derive API URL/.test(e.message)) {\n    // config issue — set api: to https://<tenant>.eightfold.ai; do not retry\n  } else throw e;\n}","preventionTips":["Branded CNAMEs (careers.<company>.com) are deliberately rejected — use <tenant>.eightfold.ai.","For multi-brand tenants, add domain: <brand> to scope the board.","Run detect() before fetch()."],"tags":["config","eightfold","ats","url-resolution","hostname-pin","ssrf"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}