{"record":{"id":"26ae7be53d88c7eb","repo":"santifer/career-ops","slug":"justjoin-careers-url-or-api-must-be-a-trusted-jus","errorCode":null,"errorMessage":"justjoin: careers_url or api must be a trusted justjoin.it URL","messagePattern":"justjoin: careers_url or api must be a trusted justjoin\\.it URL","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/justjoin.mjs","lineNumber":110,"sourceCode":"        url: `${JOB_BASE}${slug}`,\n        company: String(offer.companyName || '').trim(),\n        location: normalizeLocation(offer),\n        postedAt: postedAtMillis(offer.publishedAt),\n      };\n    })\n    .filter(Boolean);\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'justjoin',\n\n  detect(entry) {\n    return detectUrl(entry);\n  },\n\n  async fetch(entry, ctx) {\n    if (!detectUrl(entry)) throw new Error('justjoin: careers_url or api must be a trusted justjoin.it URL');\n\n    const jobs = [];\n    let from = 0;\n    const maxPages = Number(entry.max_pages || MAX_PAGES);\n    for (let page = 0; page < maxPages; page++) {\n      const url = buildApiUrl(entry, from);\n      const json = await ctx.fetchJson(url, { redirect: 'error' });\n      const parsed = parseJustJoinResponse(json);\n      jobs.push(...parsed);\n\n      const next = json?.meta?.next?.cursor;\n      if (next == null || parsed.length === 0) break;\n      from = Number(next);\n      if (!Number.isFinite(from)) break;\n    }\n    return jobs;\n  },\n};","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/justjoin.mjs#L92-L128","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Confirm the entry should use the justjoin provider at all — if the company uses another ATS, fix the provider routing/detection","Call provider.detect(entry) first in your own code and skip the provider when it returns null instead of calling fetch"],"exampleFix":"// before — calling fetch on an entry that may not be justjoin\nconst jobs = await justjoinProvider.fetch(entry, ctx);\n// after — gate on detect\nif (justjoinProvider.detect(entry)) {\n  const jobs = await justjoinProvider.fetch(entry, ctx);\n}","handlingStrategy":"type-guard","validationCode":"const url = entry.api || entry.careers_url || '';\nconst usable = typeof url === 'string' && url.trim() && providerDetectsJustJoin(url);\nif (!usable) throw new Error('entry is not a justjoin.it source');","typeGuard":"const isJustJoinEntry = (entry) => {\n  const u = entry?.api || entry?.careers_url || '';\n  if (typeof u !== 'string' || !u.trim()) return false;\n  try {\n    const p = new URL(u);\n    return p.protocol === 'https:' && p.hostname === 'justjoin.it' &&\n      (p.pathname.startsWith('/job-offers') || p.pathname === '/api/candidate-api/offers');\n  } catch { return false; }\n};","tryCatchPattern":"try {\n  const jobs = await justjoinProvider.fetch(entry, ctx);\n} catch (e) {\n  if (e.message.includes('must be a trusted justjoin.it URL')) {\n    console.error(`Skipping entry ${entry.company ?? '?'}: no valid justjoin.it api/careers_url configured`);\n  } else throw e;\n}","preventionTips":["Always check provider.detect(entry) !== null before calling fetch","Ensure every portals.yml entry has a non-empty, correct careers_url for its provider","Keep provider dispatch driven by detect() rather than a hardcoded provider choice per entry"],"tags":["config","validation","provider-routing"],"backgroundTag":"invalid-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"}