{"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":"exception","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/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/justjoin.mjs#L92-L128","documentation":"Thrown by justjoin.fetch() at the very start of the fetch when detectUrl(entry) returns null — meaning the entry's api/careers_url is missing, empty, or failed every assertJustJoinUrl guard (invalid URL, non-HTTPS, wrong host, or wrong path). detect() uses the same detectUrl and would have returned null too, so reaching fetch() with a null detection implies the entry was forced into the justjoin provider without a valid justjoin.it URL.","triggerScenarios":"A portals.yml entry with provider: justjoin but no api and no careers_url; an api/careers_url that is a valid URL but not on justjoin.it (so detectUrl swallows the assertJustJoinUrl error and returns null); a non-string or whitespace-only url field.","commonSituations":"Forcing provider: justjoin on an entry that has no justjoin.it URL; an entry template where the url field was never filled in; an entry that points at a different board but was mislabeled justjoin.","solutions":["Set api to https://justjoin.it/api/candidate-api/offers or careers_url to a https://justjoin.it/job-offers/... URL.","If the company is not on justjoin.it, change provider to the correct ATS.","Remove the provider: justjoin assignment if the entry has no justjoin.it URL."],"exampleFix":"# before — provider set but no valid URL\nacme:\n  provider: justjoin\n  name: Acme\n\n# after — provide the trusted URL\nacme:\n  provider: justjoin\n  name: Acme\n  api: https://justjoin.it/api/candidate-api/offers","handlingStrategy":"validation","validationCode":"import { default as justjoin } from './providers/justjoin.mjs';\n// Use the provider's own detect as the source of truth:\nfor (const e of entries) {\n  if (e.provider === 'justjoin' && !justjoin.detect(e)) {\n    console.warn(`justjoin ${e.name}: no valid justjoin.it URL — set api or careers_url`);\n  }\n}","typeGuard":"/** @param {any} entry @returns {boolean} */\nfunction justjoinEntryHasValidUrl(entry) {\n  const url = entry?.api || entry?.careers_url;\n  if (typeof url !== 'string' || !url.trim()) return false;\n  try {\n    const p = new URL(url);\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":null,"preventionTips":["Never set provider: justjoin without a valid https://justjoin.it URL.","Run provider.detect() in a config dry-run to catch invalid entries pre-scan.","If a board isn't justjoin.it, select the correct provider."],"tags":["justjoin","config","missing-url","provider-mismatch"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}