{"record":{"id":"4db79d896dce44e4","repo":"santifer/career-ops","slug":"collage-invalid-url-url","errorCode":null,"errorMessage":"collage: invalid URL: ${url}","messagePattern":"collage: invalid URL: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/collage.mjs","lineNumber":16,"sourceCode":"// @ts-check\n/** @typedef {import('./_types.js').Provider} Provider */\n\n// Collage HR public job-site API.  A job-site address is an explicit tenant\n// identifier, not a company-name slug we should guess.  Entries may provide\n// the exact API URL or a public Collage careers URL from which the final path\n// segment is read.\n\nconst API_ORIGIN = 'https://api.collage.co';\nconst COLLAGE_API_HOST = 'api.collage.co';\nconst COLLAGE_SITE_HOST_RE = /^secure\\.collage\\.co$/;\n\n/** @param {string} url */\nfunction assertCollageApiUrl(url) {\n  let parsed;\n  try { parsed = new URL(url); } catch { throw new Error(`collage: invalid URL: ${url}`); }\n  if (parsed.protocol !== 'https:') throw new Error(`collage: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== COLLAGE_API_HOST) {\n    throw new Error(`collage: untrusted hostname \"${parsed.hostname}\" — must be ${COLLAGE_API_HOST}`);\n  }\n  if (!/^\\/v1\\/positions\\/[^/?#]+$/.test(parsed.pathname)) {\n    throw new Error(`collage: API URL must be /v1/positions/<job-site-address>: ${url}`);\n  }\n  return url;\n}\n\n/** @param {import('./_types.js').PortalEntry} entry */\nfunction resolveApiUrl(entry) {\n  const explicit = typeof entry.api === 'string' ? entry.api.trim() : '';\n  if (explicit) return assertCollageApiUrl(explicit);\n\n  const raw = typeof entry.careers_url === 'string' ? entry.careers_url.trim() : '';\n  if (!raw) return null;\n  let parsed;","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/collage.mjs#L1-L34","documentation":"collage provider's assertCollageApiUrl validates an explicitly configured API URL and the first check is that `new URL(url)` parses at all. This error means the string supplied as the entry's `api` field is not a valid absolute URL (missing scheme, whitespace-only, garbage). It is an input-validation guard so a malformed portals.yml entry fails loudly instead of producing a nonsense fetch.","triggerScenarios":"A portals.yml Collage entry sets `api:` to a string that the URL constructor rejects — e.g. `api.collage.co/v1/positions/foo` without `https://`, a relative path like `/v1/positions/foo`, a value with stray spaces/newlines beyond trim, or a non-string coerced to an empty/mangled string.","commonSituations":"Hand-editing portals.yml and forgetting the scheme; copying a path from docs without the origin; YAML folding a long URL with an illegal character; typos like `https:/api.collage.co` (single slash).","solutions":["Check the `api:` value in portals.yml and add the full absolute URL including `https://`","Ensure there are no stray spaces, newlines, or invisible characters inside the quoted value","Use the canonical form: https://api.collage.co/v1/positions/<job-site-address>","If you only have a secure.collage.co careers page URL, put it in `careers_url:` instead and let the provider derive the API URL"],"exampleFix":"# before (portals.yml)\napi: api.collage.co/v1/positions/acme\n# after\napi: https://api.collage.co/v1/positions/acme","handlingStrategy":"validation","validationCode":"// Validate the portals.yml Collage api value before loading the provider\nfunction isValidCollageApiUrl(url) {\n  try { return new URL(url).href === url.trim() || new URL(url).protocol === 'https:'; }\n  catch { return false; }\n}\n// Pre-check: new URL(entry.api) must not throw; must be absolute with a scheme\n","typeGuard":"function isAbsoluteUrlString(v) {\n  if (typeof v !== 'string' || !v.trim()) return false;\n  try { new URL(v); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  const jobs = await collageProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).startsWith('collage: invalid URL')) {\n    console.error(`Fix portals.yml entry \"${entry.name}\": api must be an absolute https URL, got \"${entry.api}\"`);\n  } else { throw err; }\n}","preventionTips":["Always write full absolute URLs (scheme + host + path) in portals.yml api/careers_url fields","Quote URL values in YAML so special characters aren't folded or truncated","Lint portals.yml entries with a URL-format check before running scans","Prefer providing careers_url and letting the provider construct the API URL"],"tags":["url-validation","configuration","portals-config"],"backgroundTag":"invalid-url-format","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"}