santifer/career-ops · error · Error

justjoin: careers_url or api must be a trusted justjoin.it…

Error message

justjoin: careers_url or api must be a trusted justjoin.it URL

What it means

Thrown by the provider's fetch() when detectUrl(entry) returns null, meaning entry has no api or careers_url value that passes assertJustJoinUrl (trusted justjoin.it HTTPS URL with an accepted path). detect() also returns null in that case, so this error means the justjoin provider was selected for a portals.yml entry it cannot claim. It is the top-level guard replacing the more specific URL errors (315-317), which detect() swallows.

Solutions

  1. Add a valid careers_url (e.g. https://justjoin.it/job-offers) or api (https://justjoin.it/api/candidate-api/offers) to the portals.yml entry
  2. Confirm the entry should use the justjoin provider at all — if the company uses another ATS, fix the provider routing/detection
  3. Call provider.detect(entry) first in your own code and skip the provider when it returns null instead of calling fetch

Example fix

// before — calling fetch on an entry that may not be justjoin
const jobs = await justjoinProvider.fetch(entry, ctx);
// after — gate on detect
if (justjoinProvider.detect(entry)) {
  const jobs = await justjoinProvider.fetch(entry, ctx);
}
Defensive patterns

Strategy: type-guard

Validate before calling

const url = entry.api || entry.careers_url || '';
const usable = typeof url === 'string' && url.trim() && providerDetectsJustJoin(url);
if (!usable) throw new Error('entry is not a justjoin.it source');

Type guard

const isJustJoinEntry = (entry) => {
  const u = entry?.api || entry?.careers_url || '';
  if (typeof u !== 'string' || !u.trim()) return false;
  try {
    const p = new URL(u);
    return p.protocol === 'https:' && p.hostname === 'justjoin.it' &&
      (p.pathname.startsWith('/job-offers') || p.pathname === '/api/candidate-api/offers');
  } catch { return false; }
};

Try / catch

try {
  const jobs = await justjoinProvider.fetch(entry, ctx);
} catch (e) {
  if (e.message.includes('must be a trusted justjoin.it URL')) {
    console.error(`Skipping entry ${entry.company ?? '?'}: no valid justjoin.it api/careers_url configured`);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling provider.fetch(entry, ctx) where entry.api and entry.careers_url are: missing/empty, non-string, whitespace-only, or fail any assertJustJoinUrl check (non-HTTPS, wrong host, wrong path). Also hit when a routing layer dispatches an entry to the justjoin provider that belongs to another provider.

Common situations: A portals.yml company entry with no careers_url; a careers_url pointing at a non-justjoin ATS (Greenhouse/Lever) mis-routed to this provider; an empty string after YAML parsing; someone deleted the URL field during config cleanup.

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/26ae7be53d88c7eb. Report an issue: GitHub.

Appendix: source

Thrown at providers/justjoin.mjs:110

        url: `${JOB_BASE}${slug}`,
        company: String(offer.companyName || '').trim(),
        location: normalizeLocation(offer),
        postedAt: postedAtMillis(offer.publishedAt),
      };
    })
    .filter(Boolean);
}

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

  detect(entry) {
    return detectUrl(entry);
  },

  async fetch(entry, ctx) {
    if (!detectUrl(entry)) throw new Error('justjoin: careers_url or api must be a trusted justjoin.it URL');

    const jobs = [];
    let from = 0;
    const maxPages = Number(entry.max_pages || MAX_PAGES);
    for (let page = 0; page < maxPages; page++) {
      const url = buildApiUrl(entry, from);
      const json = await ctx.fetchJson(url, { redirect: 'error' });
      const parsed = parseJustJoinResponse(json);
      jobs.push(...parsed);

      const next = json?.meta?.next?.cursor;
      if (next == null || parsed.length === 0) break;
      from = Number(next);
      if (!Number.isFinite(from)) break;
    }
    return jobs;
  },
};

View on GitHub (pinned to aac998c7ed)