santifer/career-ops · error · Error
pinpoint: cannot derive API URL for ${entry.name}
Error message
pinpoint: cannot derive API URL for ${entry.name} What it means
fetch() calls resolveApiUrl(entry) and throws when it returns null. resolveApiUrl returns null if entry.careers_url is missing, not a string, unparseable by the URL constructor, not HTTPS, or its hostname fails PINPOINT_HOST_RE. Unlike the assert* guards (which throw on a URL you already have), this fires during fetch when the entry itself lacks the data needed to build the API URL at all.
Source
Thrown at providers/pinpoint.mjs:64
return null;
}
if (parsed.protocol !== 'https:') return null;
if (!PINPOINT_HOST_RE.test(parsed.hostname)) return null;
return `https://${parsed.hostname}/postings.json`;
}
/** @type {Provider} */
export default {
id: 'pinpoint',
detect(entry) {
const apiUrl = resolveApiUrl(entry);
return apiUrl ? { url: apiUrl } : null;
},
async fetch(entry, ctx) {
const apiUrl = resolveApiUrl(entry);
if (!apiUrl) throw new Error(`pinpoint: cannot derive API URL for ${entry.name}`);
assertPinpointUrl(apiUrl);
// redirect:'error' prevents SSRF via server-side redirects
const json = await ctx.fetchJson(apiUrl, { redirect: 'error' });
return parsePinpointResponse(json, entry.name);
},
};
/**
* Parse a Pinpoint /postings.json response. Exported for unit tests.
*
* Pinpoint returns:
* { data: [{ title, url, path, location: { name, city, province, ... }, ... }] }
*
* Field mapping → the normalized Job shape:
* - title: `title`, trimmed.
* - url: `url` — an absolute posting URL on the tenant's own
* `<slug>.pinpointhq.com` host. It is display-only (written to the
* pipeline and scan history, never server-fetched here), so it isView on GitHub (pinned to 9b17a8ac97)
Solutions
- Ensure the entry passed to fetch has a valid string careers_url that is https://<slug>.pinpointhq.com.
- If you are calling fetch() directly, call detect(entry) first and skip when it returns null — detect uses the same resolveApiUrl and returns null cleanly without throwing.
- Check the config source (e.g. portals.yml) for a typo'd or missing careers_url field for this entry name.
- If the board genuinely has no pinpointhq.com URL, do not use the pinpoint provider for this entry.
Example fix
// before — fetch called on an entry detect() would reject
await provider.fetch({ name: 'Acme' }, ctx);
// after — guard with detect()
if (provider.detect(entry)) {
await provider.fetch(entry, ctx);
} Defensive patterns
Strategy: validation
Validate before calling
// detect() uses the same resolveApiUrl and returns null instead of throwing
const detected = provider.detect(entry);
if (!detected) {
console.warn(`skip ${entry.name}: pinpoint cannot derive API URL`);
continue;
}
await provider.fetch(entry, ctx); 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}: no pinpointhq.com careers_url — fix config`);
} else throw e;
} Prevention
- Gate every fetch() call on detect() returning non-null.
- Validate at config-load time that pinpoint entries have a string careers_url matching the host regex.
- Distinguish 'cannot derive' (config/missing data) from assert failures (bad URL) in error handling.
When it happens
Trigger: entry.careers_url is undefined/null/empty; entry.careers_url is a non-HTTPS URL (http://acme.pinpointhq.com); entry.careers_url hostname is a branded domain that is not *.pinpointhq.com; entry.careers_url is a malformed string that throws inside new URL().
Common situations: A job_boards row was created without a careers_url (detect() returned null but fetch was still invoked directly); a YAML/JSON config field was misnamed (career_url vs careers_url); the entry was constructed programmatically and the URL field was dropped by a serializer.
Related errors
- 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}
- rippling: cannot derive API 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/d700a701729c7c16.
Report an issue: GitHub.