santifer/career-ops · error · Error

rippling: cannot derive API URL for ${entry.name}

Error message

rippling: cannot derive API URL for ${entry.name}

What it means

fetch() throws when resolveSlug(entry) returns null. resolveSlug reads entry.careers_url, requires https:, requires hostname exactly 'ats.rippling.com', extracts the first path segment as the slug, and validates it against SLUG_RE (/^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/). Null is returned for a missing/non-HTTPS/wrong-host URL, an empty path segment, or a slug with illegal characters.

Source

Thrown at providers/rippling.mjs:74

  if (parsed.protocol !== 'https:') throw new Error(`rippling: URL must use HTTPS: ${url}`);
  if (parsed.hostname !== API_HOST) {
    throw new Error(`rippling: untrusted hostname "${parsed.hostname}" — must be ${API_HOST}`);
  }
  return url;
}

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

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

  async fetch(entry, ctx) {
    const slug = resolveSlug(entry);
    if (!slug) throw new Error(`rippling: cannot derive API URL for ${entry.name}`);
    const apiUrl = apiUrlForSlug(slug);
    assertRipplingApiUrl(apiUrl);
    // redirect:'error' prevents SSRF via server-side redirects
    const json = await ctx.fetchJson(apiUrl, { redirect: 'error' });
    return parseRipplingResponse(json, entry.name);
  },
};

/**
 * Parse a Rippling board API response. Exported for unit tests.
 *
 * The response is a top-level JSON ARRAY of postings. Field mapping → the
 * normalized Job shape:
 *   - title:    `name`, trimmed (postings without one are dropped).
 *   - url:      `url` — an absolute `https:` posting URL host-locked to
 *               `ats.rippling.com` (Rippling always serves postings there, so an
 *               off-host or non-https URL is untrusted and the posting is dropped).
 *               It is the dedup key and is display-only (written to the

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Set careers_url to the canonical https://ats.rippling.com/<slug>/jobs where <slug> is alphanumeric (plus interior hyphens), starting and ending alphanumeric.
  2. Call detect(entry) before fetch; it returns null cleanly when the slug cannot be resolved.
  3. Verify the first path segment is the tenant slug and is non-empty.
  4. If the board uses a branded domain, the rippling provider cannot auto-derive it — supply the ats.rippling.com URL or use the correct provider.

Example fix

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

Strategy: validation

Validate before calling

const CAREERS_HOST = 'ats.rippling.com';
const SLUG_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
function canResolveRipplingSlug(entry) {
  const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
  if (!raw) return false;
  try {
    const p = new URL(raw);
    if (p.protocol !== 'https:' || p.hostname !== CAREERS_HOST) return false;
    const seg = p.pathname.split('/').filter(Boolean)[0] || '';
    return SLUG_RE.test(seg);
  } catch { return false; }
}
if (!canResolveRipplingSlug(entry)) {
  console.warn(`skip ${entry.name}: no valid rippling slug in careers_url`);
}

Type guard

null

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (e) {
  if (/cannot derive API URL/.test(e.message)) {
    console.warn(`[skip] ${entry.name}: careers_url must be https://ats.rippling.com/<slug>/jobs`);
  } else throw e;
}

Prevention

When it happens

Trigger: careers_url is missing or not https; hostname is not ats.rippling.com (e.g. a branded Rippling domain); the first path segment is empty (bare https://ats.rippling.com); the slug contains underscores, dots, or special characters that fail SLUG_RE; the slug starts/ends with a hyphen.

Common situations: A Rippling board uses a custom domain instead of ats.rippling.com; the careers_url points at the jobs page with no slug in the path; the entry was constructed with a non-standard URL format.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/554936554262f3e8. Report an issue: GitHub.