{"record":{"id":"a5d25f84a0bea448","repo":"santifer/career-ops","slug":"comeet-cannot-derive-api-url-for-entry-name-s","errorCode":null,"errorMessage":"comeet: cannot derive API URL for ${entry.name} (set api: to the full careers-api positions URL)","messagePattern":"comeet: cannot derive API URL for (.+?) \\(set api: to the full careers-api positions URL\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/comeet.mjs","lineNumber":88,"sourceCode":"  const parsed = Date.parse(value);\n  return Number.isNaN(parsed) ? undefined : parsed;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'comeet',\n\n  detect(entry) {\n    const apiUrl = resolveApiUrl(entry);\n    // The DetectHit url is informational (the framework may log it), so strip\n    // the secret ?token= before returning it — fetch() re-resolves the real\n    // URL from the entry, so redaction here is safe.\n    return apiUrl ? { url: redactToken(apiUrl) } : null;\n  },\n\n  async fetch(entry, ctx) {\n    const apiUrl = resolveApiUrl(entry);\n    if (!apiUrl) throw new Error(`comeet: cannot derive API URL for ${entry.name} (set api: to the full careers-api positions URL)`);\n    assertComeetUrl(apiUrl);\n    // redirect:'error' prevents SSRF via server-side redirects; combined with\n    // assertComeetUrl above it guarantees the final hostname stays www.comeet.co.\n    const json = await ctx.fetchJson(apiUrl, { redirect: 'error' });\n    return parseComeetResponse(json, entry.name);\n  },\n};\n\n/**\n * Parse a Comeet careers-api positions response. Exported for unit tests.\n *\n * Comeet returns a top-level ARRAY of position objects:\n *   [{ name, location: { name, is_remote }, url_active_page,\n *      url_comeet_hosted_page, time_updated, ... }]\n *\n * - url: prefer `url_active_page` (the tenant's live careers page), fall back to\n *   `url_comeet_hosted_page` (the Comeet-hosted page). Both are public, display-\n *   only URLs (recorded in the pipeline/history, never server-fetched here), so","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/comeet.mjs#L70-L106","documentation":"The Comeet provider's fetch calls resolveApiUrl(entry) to derive the careers-api positions URL from the portals.yml entry. When no URL can be derived (the entry lacks an api: field and no fallback source yields one), fetch throws this error instead of silently skipping, telling the operator exactly what to add. It is a configuration-completeness check, not a network failure.","triggerScenarios":"Running a scan where a portals.yml entry is routed to the comeet provider but that entry has no api: value and resolveApiUrl returns null (no careers_url-derived API URL either).","commonSituations":"Adding a new company to portals.yml with provider: comeet but forgetting the api: key; typo-ing the key (e.g. url: instead of api:); an entry migrated from another ATS provider whose key shape does not match Comeet's expectations.","solutions":["Set api: in the portals.yml entry to the full Comeet careers-api positions URL (https://www.comeet.co/careers-api/...).","Verify the key name is exactly api: on the entry — compare against another working comeet entry in portals.yml.","If the company is not actually on Comeet, switch the entry to the correct provider instead of supplying a fabricated URL.","Re-run the scan for just that entry to confirm resolveApiUrl now succeeds (the DetectHit url helper should return a redacted URL, not null)."],"exampleFix":"// portals.yml before\n- name: mycompany\n  provider: comeet\n// after\n- name: mycompany\n  provider: comeet\n  api: https://www.comeet.co/careers-api/v2.1/company/mycompany/positions?token=XXXX","handlingStrategy":"validation","validationCode":"if (entry.provider === 'comeet' && !entry.api) { throw new Error(`comeet entry '${entry.name}' is missing api:`); }","typeGuard":"const hasComeetApi = (entry) => typeof entry.api === 'string' && entry.api.startsWith('https://www.comeet.co/careers-api/');","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (err) {\n  if (err.message.includes('cannot derive API URL')) {\n    console.warn(`Skipping ${entry.name}: set api: to the full comeet careers-api URL`);\n    return null;\n  }\n  throw err;\n}","preventionTips":["Include api: whenever you add a comeet provider entry to portals.yml","Run verify-pipeline / a config check before scans to catch incomplete entries","Copy the api URL template from an existing working comeet entry","Watch for key typos (url vs api) when migrating entries between providers"],"tags":["config","missing-config-field","comeet"],"backgroundTag":"missing-required-config-field","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"}