santifer/career-ops · error · Error
oraclecloud: cannot derive API URL for
Error message
oraclecloud: cannot derive API URL for ${entry.name} What it means
The Oracle Recruiting provider could not derive an API base site from the entry configuration. fetch() calls resolveSite(entry), which extracts an allowlisted *.fa.*.oraclecloud*.com host from the entry (careers_url or explicit config); when it returns null the provider has nothing to call and throws instead of guessing. Unlike error 360 this is usually a missing/empty config value, not an untrusted one.
Solutions
- Add or correct careers_url in the portals.yml entry so it points to the tenant's *.fa.[region.][ocs.]oraclecloud[1-99].com host.
- Check that the entry's provider id is actually oraclecloud and not a mis-assigned provider for a different ATS.
- If the board is only reachable via a vanity domain, set the explicit Oracle tenant host in the entry config that resolveSite reads.
- Use audit-portals.mjs on the entry to confirm which provider actually claims that careers_url before keeping it under oraclecloud.
Example fix
// before (portals.yml) - name: Acme provider: oraclecloud // after - name: Acme provider: oraclecloud careers_url: https://acme.fa.ocs.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1
Defensive patterns
Strategy: validation
Validate before calling
function hasDerivableOracleSite(entry) {
const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
if (!raw) return false;
try {
const { hostname } = new URL(raw);
return /^[a-z0-9-]+\.fa\.(?:[a-z0-9-]+\.)?(?:ocs\.)?oraclecloud(?:[1-9][0-9]?)?\.com$/i.test(hostname);
} catch { return false; }
}
if (!hasDerivableOracleSite(entry)) throw new Error(`oraclecloud: entry ${entry.name} needs an allowlisted careers_url`); Type guard
function hasOracleCareersUrl(entry) {
return entry != null && typeof entry === 'object' &&
typeof entry.careers_url === 'string' && entry.careers_url.length > 0 &&
(() => { try { return isOracleUrl(entry.careers_url); } catch { return false; } })();
} Try / catch
try {
await oracleProvider.fetch(entry, ctx);
} catch (e) {
if (String(e.message).startsWith('oraclecloud: cannot derive API URL')) {
logger.warn({ entry: entry.name }, 'no usable Oracle host in entry config — skipping');
return null;
}
throw e;
} Prevention
- Make careers_url a required field for oraclecloud entries; validate portals.yml with a schema (e.g. zod or a CI script) before runs.
- Check provider assignment with audit-portals.mjs — wrong ATS id is the usual cause.
- Grep for misspelled keys (career_url, careersUrl) in portals.yml periodically.
- Keep tenant host and vanity domain documented in the entry comments so future edits don't drop the field.
When it happens
Trigger: Calling providers/oraclecloud.mjs fetch(entry, ctx) when entry has no careers_url, careers_url is not a string, the URL is malformed, or resolveSite's regex loop matches no allowlisted hostname — e.g. an oraclecloud entry whose URL points to a Greenhouse/Ashby domain.
Common situations: A portals.yml entry tagged with the wrong provider id (company actually uses another ATS); an entry added with only a name and no careers_url; or careers_url pointing at a redirect vanity domain that no provider claims.
Understand the failure class
Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.
Related errors
- personio: cannot derive feed URL for
- phenom: cannot resolve origin for
- collage: cannot derive API URL for
- jibeapply: cannot derive API URL for
- jobbankca: entry " " has no jobbankca.keywords[] and no…
AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-16).
Data as JSON: /api/errors/f28717a3bbcc230f.
Report an issue: GitHub.
Appendix: source
Thrown at providers/oraclecloud.mjs:233
return out;
}
/** @type {Provider} */
export default {
id: 'oraclecloud',
detect(entry) {
try {
const site = resolveSite(entry);
return site ? { url: buildApiUrl(site, 0, PAGE_SIZE) } : null;
} catch {
return null;
}
},
async fetch(entry, ctx) {
const site = resolveSite(entry);
if (!site) throw new Error(`oraclecloud: cannot derive API URL for ${entry.name}`);
const maxPages = Number.isInteger(entry.max_pages) && entry.max_pages > 0
? Math.min(entry.max_pages, MAX_PAGES)
: MAX_PAGES;
const all = [];
let total = null;
for (let page = 0; page < maxPages; page++) {
const offset = page * PAGE_SIZE;
const apiUrl = buildApiUrl(site, offset, PAGE_SIZE);
assertOracleUrl(apiUrl); // SSRF guard before every fetch
if (page > 0) await sleep(INTER_PAGE_DELAY_MS, ctx);
const json = await fetchJsonWithRetry(ctx, apiUrl, {
redirect: 'error',
headers: { 'User-Agent': BROWSER_LIKE_USER_AGENT, Accept: 'application/json' },
}, RETRY_POLICY);
View on GitHub (pinned to e7abd431fc)