{"record":{"id":"25f313deadfad365","repo":"santifer/career-ops","slug":"workday-cannot-derive-cxs-endpoint-for-entry-na","errorCode":null,"errorMessage":"workday: cannot derive CXS endpoint for ${entry.name}","messagePattern":"workday: cannot derive CXS endpoint for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/workday.mjs","lineNumber":176,"sourceCode":"\n  /**\n   * Fetch all job postings for a Workday-backed entry, paginating through\n   * the tenant's CXS API.\n   *\n   * Some tenants front their CXS API with Cloudflare bot management (seen\n   * live: geico) that 500s requests missing ordinary browser headers — the\n   * default UA/accept-language-less request trips it even over plain HTTPS\n   * with no other red flags. A real Chrome UA + accept-language + matching\n   * origin/referer clears it without needing per-tenant config (same fix\n   * as providers/glints.mjs's firewall).\n   *\n   * @param {{ name?: string, api?: string, careers_url?: string, max_pages?: number }} entry\n   * @param {{ fetchJson: (url: string, opts?: object) => Promise<any>, sinceMs?: number, maxPages?: number, syntheticEntries?: boolean }} ctx\n   * @returns {Promise<Array<{title: string, url: string, company: string, location: string, postedAt?: number}>>}\n   */\n  async fetch(entry, ctx) {\n    const ep = resolveEndpoint(entry);\n    if (!ep) throw new Error(`workday: cannot derive CXS endpoint for ${entry.name}`);\n\n    const postOpts = {\n      method: 'POST',\n      redirect: 'error',\n      headers: {\n        'content-type': 'application/json',\n        accept: 'application/json',\n        'user-agent': BROWSER_LIKE_USER_AGENT,\n        'accept-language': 'en-US,en;q=0.9',\n        origin: ep.origin,\n        referer: `${ep.jobBase}/`,\n      },\n    };\n    const makeBody = (offset) => JSON.stringify({ limit: PAGE_SIZE, offset, searchText: '', appliedFacets: {} });\n    const sinceMs = typeof ctx?.sinceMs === 'number' ? ctx.sinceMs : null;\n\n    const first = await fetchJsonWithRetry(ctx, ep.api, { ...postOpts, body: makeBody(0) }, RETRY_POLICY);\n    const jobs = parseWorkdayResponse(first, entry);","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/workday.mjs#L158-L194","documentation":"Workday auto-detects a tenant by matching entry.api then entry.careers_url against ^https://([\\w-]+)\\.(wd[\\w-]*)\\.myworkdayjobs\\.com/(?:[a-z]{2}-[A-Z]{2}/)?([^/?#]+). resolveEndpoint returns null when neither matches, and fetch() throws because it cannot build the CXS POST URL (/wday/cxs/<tenant>/<site>/jobs).","triggerScenarios":"An entry marked provider: workday whose api and careers_url are both missing, both branded domains (e.g. https://www.ptc.com/en/careers), or whose tenant URL has an unusual locale/path segment the regex rejects (e.g. lowercase-only locale, missing site segment, non-wd instance subdomain).","commonSituations":"Company has a branded careers page backed by Workday but the entry does not also set api: to the raw myworkdayjobs.com tenant URL; the instance segment is not a wd… subdomain; the locale path is malformed.","solutions":["Set api: https://<tenant>.<instance>.myworkdayjobs.com/<site> on the entry (e.g. https://23andme.wd5.myworkdayjobs.com/23).","Or set careers_url to that same tenant URL.","Verify the instance segment matches the wd… pattern (wd1, wd5, …) and the site segment is present.","For branded pages, keep careers_url as the branded page AND add api: with the raw tenant URL — the resolver tries api: first."],"exampleFix":"# before\n- name: PTC\n  provider: workday\n  careers_url: https://www.ptc.com/en/careers\n# after\n- name: PTC\n  provider: workday\n  careers_url: https://www.ptc.com/en/careers\n  api: https://ptc.wd1.myworkdayjobs.com/PTC","handlingStrategy":"validation","validationCode":"const WD_RE = /^https:\\/\\/([\\w-]+)\\.(wd[\\w-]*)\\.myworkdayjobs\\.com\\/(?:[a-z]{2}-[A-Z]{2}\\/)?([^\\/?#]+)/;\nfunction workdayEndpointResolvable(entry) {\n  for (const u of [entry.api, entry.careers_url]) {\n    if (typeof u === \"string\" && WD_RE.test(u)) return true;\n  }\n  return false;\n}\n// before scan\nif (entry.provider === \"workday\" && !workdayEndpointResolvable(entry)) {\n  console.warn(`skip ${entry.name}: set api: to the myworkdayjobs.com tenant URL`);\n  continue;\n}","typeGuard":"const isWorkdayTenantUrl = (u) => typeof u === \"string\" &&\n  /^https:\\/\\/([\\w-]+)\\.(wd[\\w-]*)\\.myworkdayjobs\\.com\\/(?:[a-z]{2}-[A-Z]{2}\\/)?([^\\/?#]+)/.test(u);","tryCatchPattern":null,"preventionTips":["For branded careers pages, always pair careers_url with an explicit api: tenant URL.","Confirm the instance segment is the wd… subdomain Workday assigned the tenant.","Lint portals.yml provider: workday entries for a resolvable tenant regex."],"tags":["config","workday","detection","url-pattern","portals-yml"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}