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 theView on GitHub (pinned to 9b17a8ac97)
Solutions
- Set careers_url to the canonical https://ats.rippling.com/<slug>/jobs where <slug> is alphanumeric (plus interior hyphens), starting and ending alphanumeric.
- Call detect(entry) before fetch; it returns null cleanly when the slug cannot be resolved.
- Verify the first path segment is the tenant slug and is non-empty.
- 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
- Set careers_url to https://ats.rippling.com/<slug>/jobs with an alphanumeric slug.
- Gate fetch() on detect() returning non-null.
- Branded Rippling domains cannot be auto-derived — use the ats.rippling.com URL.
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
- pinpoint: cannot derive API URL for ${entry.name}
- radancy: cannot resolve search-jobs URL for ${entry.name}
- recruitee: cannot derive API URL for ${entry.name}
- rheinmetall: cannot resolve vacancies URL for ${entry.name}
- justjoin: careers_url or api must be a trusted justjoin.it U
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/554936554262f3e8.
Report an issue: GitHub.