{"record":{"id":"e8971aaae5887763","repo":"santifer/career-ops","slug":"softgarden-cannot-resolve-widget-url-for-entry","errorCode":null,"errorMessage":"softgarden: cannot resolve widget URL for ${entry.name}","messagePattern":"softgarden: cannot resolve widget URL for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/softgarden.mjs","lineNumber":133,"sourceCode":"      postedAt: parseSoftgardenDate(dateM ? clean(dateM[1]) : undefined),\n    });\n  }\n  return out;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'softgarden',\n\n  detect(entry) {\n    const url = entry.api || entry.careers_url || '';\n    if (typeof url !== 'string') return null;\n    return resolveWidgetUrl({ api: url }) ? { url } : null;\n  },\n\n  async fetch(entry, ctx) {\n    const widgetUrl = resolveWidgetUrl(entry);\n    if (!widgetUrl) throw new Error(`softgarden: cannot resolve widget URL for ${entry.name}`);\n\n    const html = await ctx.fetchText(widgetUrl, { headers: { accept: 'text/html' } });\n    const rows = parseWidget(html, widgetUrl);\n    const jobs = [];\n    for (const row of rows) {\n      const job = { title: row.title, url: row.url, company: entry.name, location: row.location };\n      if (typeof row.postedAt === 'number') job.postedAt = row.postedAt;\n      jobs.push(job);\n      if (jobs.length >= MAX_JOBS) break;\n    }\n    return jobs;\n  },\n};\n","sourceCodeStart":115,"sourceCodeEnd":147,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/softgarden.mjs#L115-L147","documentation":"The softgarden provider's fetch calls resolveWidgetUrl(entry), which must produce a {tenant}.softgarden.io widget URL from entry.api or entry.careers_url. If the configured URL is unparseable, non-HTTP(S), or not on a *.softgarden.io host, fetch cannot proceed and throws this error — the entry is not recognizable as a softgarden tenant.","triggerScenarios":"A portals entry with provider: softgarden whose api/careers_url is missing, empty, on a branded domain (e.g. careers.renk-group.com) with no softgarden.io URL, or uses another scheme (ftp:, about:); a typo like renk-group.softgarden.com (wrong TLD).","commonSituations":"Company moved its careers page to a custom domain while the widget still lives on softgarden.io; misconfigured host TLD; leaving the api field blank expecting detection from another field; pasting the iframe's outer page URL instead of the widget URL (the resolver actually tolerates that, but only if the host is *.softgarden.io).","solutions":["Set api (or careers_url) to the tenant widget URL, e.g. https://renk-group.softgarden.io/de/widgets/jobs — an explicit /widgets/jobs path passes through unchanged","Ensure the host is exactly softgarden.io or ends with .softgarden.io; fix typos/TLD","Use https:// or http:// only — other schemes are rejected by resolveWidgetUrl","If the careers site is branded, keep careers_url for humans and put the softgarden.io widget URL in api"],"exampleFix":"// before\n- name: RENK\ncareers_url: 'https://careers.renk-group.com'  # not a softgarden.io host\n// after\n- name: RENK\napi: 'https://renk-group.softgarden.io/de/widgets/jobs'","handlingStrategy":"validation","validationCode":"function canResolveSoftgardenWidget(entry) {\n  const raw = entry?.api || entry?.careers_url || '';\n  try {\n    const u = new URL(raw);\n    if (u.protocol !== 'https:' && u.protocol !== 'http:') return false;\n    const host = u.host.toLowerCase();\n    return host === 'softgarden.io' || host.endsWith('.softgarden.io');\n  } catch { return false; }\n}\nif (!canResolveSoftgardenWidget(entry)) console.error(`${entry.name}: api/careers_url must be a *.softgarden.io URL`);","typeGuard":"const isSoftgardenUrl = (raw) => {\n  try {\n    const u = new URL(String(raw));\n    const host = u.host.toLowerCase();\n    return (u.protocol === 'https:' || u.protocol === 'http:') &&\n      (host === 'softgarden.io' || host.endsWith('.softgarden.io'));\n  } catch { return false; }\n};","tryCatchPattern":"try {\n  await softgardenProvider.fetch(entry, ctx);\n} catch (e) {\n  if (String(e.message).includes('cannot resolve widget URL')) {\n    console.error(`Entry ${entry.name}: set api to https://<tenant>.softgarden.io/<lang>/widgets/jobs`);\n  } else throw e;\n}","preventionTips":["Point api at the tenant widget URL (https://{tenant}.softgarden.io/{lang}/widgets/jobs); any *.softgarden.io path is normalized to the widget automatically","Check the TLD carefully — .softgarden.com will not resolve","Keep careers_url on a softgarden.io host or use api for the widget while careers_url stays branded","Run config validation before sweeps so unresolvable entries are caught early"],"tags":["config-error","url-validation","scraper","widget"],"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"}