santifer/career-ops · error

eightfold: cannot derive API URL for

Error message

eightfold: cannot derive API URL for ${entry.name}

What it means

provider.fetch() starts by resolving the tenant (host + optional domain) from the portal entry via resolveTenant(), which accepts only entry.api or entry.careers_url values that parse as HTTPS URLs on a *.eightfold.ai host. If neither field qualifies, fetch throws this error naming the entry — it cannot construct the /api/apply/v2/jobs endpoint, so it refuses to guess.

Solutions

  1. Set careers_url to https://<tenant>.eightfold.ai/careers (e.g. https://bayer.eightfold.ai/careers), or set entry.api directly to the eightfold.ai host — api takes precedence over careers_url.
  2. Check the URL parses and uses https:// — a bare hostname like bayer.eightfold.ai is skipped silently by resolveTenant and surfaces as this error.
  3. Confirm the hostname is a single-label subdomain of eightfold.ai (matches /^[a-z0-9-]+\.eightfold\.ai$/i); branded CNAMEs like careers.company.com are rejected by design.
  4. If the entry's branded careers page must stay, keep it in careers_url and add an explicit api: https://<tenant>.eightfold.ai field to pin the tenant.

Example fix

// before
- name: Bayer
  careers_url: https://careers.bayer.com
// after
- name: Bayer
  careers_url: https://careers.bayer.com   # display only
  api: https://bayer.eightfold.ai/careers  # tenant host pinned via api
Defensive patterns

Strategy: validation

Validate before calling

function canResolveEightfoldTenant(entry) {
  for (const raw of [entry?.api, entry?.careers_url]) {
    if (typeof raw !== 'string' || !raw) continue;
    try {
      const u = new URL(raw);
      if (u.protocol === 'https:' && /^[a-z0-9-]+\.eightfold\.ai$/i.test(u.hostname)) return true;
    } catch { /* skip */ }
  }
  return false;
}

Type guard

const hasEightfoldTenant = (entry) => canResolveEightfoldTenant(entry);

Try / catch

if (!canResolveEightfoldTenant(entry)) {
  console.warn(`Skipping ${entry.name}: no https://*.eightfold.ai api/careers_url`);
} else {
  const jobs = await provider.fetch(entry, ctx);
}

Prevention

When it happens

Trigger: fetch(entry) with entry.api missing/unset and entry.careers_url either absent, not a string, not parseable by new URL(), not https:, or on a host that fails EIGHTFOLD_HOST_RE (e.g. a branded CNAME like careers.company.com, an http: URL, or a malformed URL like 'bayer.eightfold.ai' with no scheme).

Common situations: A portals.yml entry points at the company's branded careers site instead of the canonical eightfold.ai tenant host; the careers_url lost its https:// scheme during hand-editing; the entry uses a custom field name (e.g. url: instead of careers_url:) that resolveTenant never reads; detect() returned null for this entry but an explicit provider selection forced fetch anyway.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/6056ba645eb62c41. Report an issue: GitHub.

Appendix: source

Thrown at providers/eightfold.mjs:277

  return Number.isFinite(hint) && hint > 0 ? Math.min(fromEntry, Math.floor(hint)) : fromEntry;
}

/** @type {Provider} */
export default {
  id: 'eightfold',

  detect(entry) {
    try {
      const tenant = resolveTenant(entry);
      return tenant ? { url: buildApiUrl(tenant, 0, PAGE_SIZE) } : null;
    } catch {
      return null;
    }
  },

  async fetch(entry, ctx) {
    const tenant = resolveTenant(entry);
    if (!tenant) throw new Error(`eightfold: cannot derive API URL for ${entry.name}`);

    const maxPages = resolveMaxPages(entry, ctx);
    const all = [];
    /** @type {number|null} */
    let total = null;

    for (let page = 0; page < maxPages; page++) {
      const start = page * PAGE_SIZE;
      const apiUrl = buildApiUrl(tenant, start, PAGE_SIZE);
      assertEightfoldUrl(apiUrl); // SSRF guard before every fetch
      if (page > 0) await sleep(INTER_PAGE_DELAY_MS, ctx);

      const json = /** @type {any} */ (await fetchJsonWithRetry(
        /** @type {any} */ (ctx),
        apiUrl,
        {
          // redirect:'error' prevents SSRF via a server-side redirect; with
          // assertEightfoldUrl above it guarantees the final hostname stays

View on GitHub (pinned to aac998c7ed)