santifer/career-ops · error · Error

jibeapply: careers_url required

Error message

jibeapply: careers_url required

What it means

The jibeapply provider reads the board URL directly from entry.careers_url. If the field is missing, not a string, or empty, fetch() throws this error because there is no URL to convert into a JibeApply API endpoint.

Solutions

  1. Add a valid careers_url to the entry in portals.yml
  2. Verify YAML indentation so careers_url belongs to the jibeapply entry
  3. If the site is iCIMS-hosted/branded, optionally also set entry.api with the explicit API URL
  4. Disable/remove the entry if it is a placeholder

Example fix

// before (portals.yml)
- name: acme
  provider: jibeapply
// after
- name: acme
  provider: jibeapply
  careers_url: "https://acme.jibeapply.com/jobs"
Defensive patterns

Strategy: validation

Validate before calling

function entryHasCareersUrl(entry) {
  return typeof entry?.careers_url === 'string' && entry.careers_url.trim() !== '';
}
// filter before fetch: jibeapplyEntries.filter(entryHasCareersUrl)

Type guard

const hasCareersUrl = (e) => typeof e?.careers_url === 'string' && e.careers_url.length > 0;

Try / catch

try {
  await jibeapplyProvider.fetch(entry, ctx);
} catch (err) {
  if (/careers_url required/.test(err.message)) {
    console.warn(`jibeapply entry ${entry?.name} missing careers_url; skipping`);
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: fetch(entry) is called with an entry whose careers_url is undefined, null, non-string, or "" — typically an incomplete portals.yml entry for a jibeapply-backed company.

Common situations: A new company was added to portals.yml with only a name and no careers_url; YAML indentation error put careers_url under the wrong entry; the field was renamed in a config refactor.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at providers/jibeapply.mjs:99

    })
    .filter(Boolean);
}

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

  detect(entry) {
    const url = entry.careers_url;
    if (typeof url !== 'string') return null;
    const apiUrl = toApiUrl(url);
    if (!apiUrl) return null;
    return { url: apiUrl };
  },

  async fetch(entry, ctx) {
    const url = entry.careers_url;
    if (typeof url !== 'string' || !url) throw new Error('jibeapply: careers_url required');

    // Prefer an explicit entry.api (allows iCIMS-hosted, branded JibeApply sites
    // that share the same JSON schema but aren't on jibeapply.com).
    const apiUrl = (typeof entry.api === 'string' && validateExplicitApi(entry.api))
      || toApiUrl(url);
    if (!apiUrl) throw new Error(`jibeapply: cannot derive API URL for ${entry.name}`);
    const first = await ctx.fetchJson(apiUrl, { redirect: 'error' });
    const total = first.totalCount ?? 0;
    // Use the actual number of items returned as page size — some implementations
    // set `count` to the total rather than the per-page count.
    const pageSize = first.jobs?.length || first.count || DEFAULT_PAGE_SIZE;
    const allJobs = [...(first.jobs ?? [])];

    if (total > pageSize && pageSize > 0) {
      const maxPages = resolveMaxPages(entry);
      const pages = Math.min(Math.ceil(total / pageSize), maxPages);
      // Sequential, not concurrent (mirrors providers/4dayweek.mjs, thehub.mjs,
      // arbeitnow.mjs, workday.mjs) — a single tenant's API has no reason to

View on GitHub (pinned to aac998c7ed)