santifer/career-ops · error · Error

workable: cannot derive feed URL for

Error message

workable: cannot derive feed URL for ${entry.name}

What it means

The Workable provider's fetch needs an account slug to build the widget feed URL. resolveWorkableSlug derives it from the entry (api or careers_url); when it returns null, fetch throws because it cannot construct a feed URL for that tracked company.

Solutions

  1. Add a valid Workable careers_url (https://apply.workable.com/<slug>/) or api field to the entry
  2. Verify the company actually uses Workable; if not, route it to the correct provider
  3. Remove or disable the entry if the company no longer has a Workable board

Example fix

// before
{ name: 'Acme', careers_url: 'https://acme.com/jobs' }
// after
{ name: 'Acme', careers_url: 'https://apply.workable.com/acme/' }
Defensive patterns

Strategy: validation

Validate before calling

function hasWorkableSlug(entry) {
  const src = entry.api || entry.careers_url || '';
  return /apply\.workable\.com\/([a-z0-9_-]+)/i.test(src);
}
if (!hasWorkableSlug(entry)) console.warn(`Skip ${entry.name}: no Workable slug derivable`);

Type guard

const hasWorkableSlug = (e) => /apply\.workable\.com\/([a-z0-9_-]+)/i.test(e.api || e.careers_url || '');

Try / catch

try { jobs = await workable.fetch(entry, ctx); } catch (e) { if (e.message.startsWith('workable: cannot derive feed URL')) { console.warn(`${entry.name}: add a Workable careers_url or route to another provider`); jobs = []; } else throw e; }

Prevention

When it happens

Trigger: fetch(entry, ctx) called with an entry that has neither a parseable api URL nor a careers_url containing a Workable slug — e.g. `{ name: 'Acme' }` with no api/careers_url, or a careers_url from a different ATS.

Common situations: A portals.yml entry for a company whose careers page is not Workable; an entry missing the api field; a careers_url format change after the company migrated ATS.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/90e7561b122ebf44. Report an issue: GitHub.

Appendix: source

Thrown at providers/workable.mjs:157

  if (!slug || !SLUG_RE.test(slug)) return null;
  return slug;
}

const widgetUrlFor = (slug) => `https://apply.workable.com/api/v1/widget/accounts/${slug}?details=true`;
const feedUrlFor = (slug) => `https://apply.workable.com/${slug}/jobs.md`;

/** @type {Provider} */
export default {
  id: 'workable',

  detect(entry) {
    const slug = resolveWorkableSlug(entry);
    return slug ? { url: widgetUrlFor(slug) } : null;
  },

  async fetch(entry, ctx) {
    const slug = resolveWorkableSlug(entry);
    if (!slug) throw new Error(`workable: cannot derive feed URL for ${entry.name}`);

    const referer = `https://apply.workable.com/${slug}/`;

    return serialized(async () => {
      // Primary: widget API. assertWorkableUrl + redirect:'error' together
      // guarantee the final hostname stays in the allowlist (no SSRF via
      // redirect). Retries transient failures; gives up early on a
      // long-lived Retry-After so a Cloudflare-level block on this path
      // falls through to the markdown feed instead of stalling the scan.
      const apiUrl = assertWorkableUrl(widgetUrlFor(slug));
      let payload = null;
      try {
        payload = await fetchWithRetry(ctx, () => ctx.fetchJson(apiUrl, {
          redirect: 'error',
          headers: { ...WORKABLE_HEADERS, referer },
        }));
      } catch {
        payload = null; // fall through to the markdown feed

View on GitHub (pinned to aac998c7ed)