santifer/career-ops · error · Error

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

Error message

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

What it means

Thrown by oraclecloud's fetch() when resolveSite(entry) returns null — the entry has no api or careers_url that resolves to a valid Oracle FA cloud site. resolveSite() iterates [entry.api, entry.careers_url], parses each as a URL, checks protocol is https and hostname matches ORACLE_HOST_RE, then extracts siteNumber and lang from the path. If neither field yields a valid Oracle site, fetch() cannot proceed.

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 9b17a8ac97)

Solutions

  1. Check the portals.yml entry has a valid careers_url: https://<tenant>.fa.<region>.oraclecloud.com/hcmUI/CandidateExperience/<lang>/sites/<siteNumber>
  2. Verify entry.api (if set) also matches the Oracle FA cloud pattern — resolveSite checks api first.
  3. Ensure detect() is called and its result respected before calling fetch() — skip entries where detect() returns null.
  4. If the entry was programmatically generated, add the careers_url field with the correct Oracle HCM URL.

Example fix

// before — entry missing or with wrong URL
job_boards:
  oracle:
    provider: oraclecloud
    name: Acme
    # careers_url missing

// after
job_boards:
  oracle:
    provider: oraclecloud
    name: Acme
    careers_url: 'https://acme.fa.eu.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1'
    # optional overrides:
    # siteNumber: 'CX_1'
    # max_pages: 5
Defensive patterns

Strategy: validation

Validate before calling

const ORACLE_HOST_RE = /^[a-z0-9-]+\.fa\.(?:[a-z0-9-]+\.)?(?:ocs\.)?oraclecloud(?:[1-9][0-9]?)?\.com$/i;

/** Replicate resolveSite's logic to validate an entry before fetch. */
function canResolveOracleSite(entry) {
  for (const raw of [entry.api, entry.careers_url]) {
    if (typeof raw !== 'string' || !raw) continue;
    try {
      const p = new URL(raw);
      if (p.protocol === 'https:' && ORACLE_HOST_RE.test(p.hostname)) return true;
    } catch { /* skip */ }
  }
  return false;
}

if (!canResolveOracleSite(entry)) {
  console.warn(`oraclecloud entry ${entry.name} cannot resolve site — skipping`);
  continue;
}

Type guard

/** @param {import('./_types.js').PortalEntry} entry @returns {boolean} */
function hasValidOracleUrl(entry) {
  const url = entry.api || entry.careers_url;
  return typeof url === 'string'
    && /\.fa\..*\.oraclecloud\.com/i.test(url)
    && url.startsWith('https://');
}

Try / catch

try {
  await oracleProvider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('oraclecloud: cannot derive API URL')) {
    console.warn(`skipping ${entry.name}: no valid Oracle FA cloud URL`);
    continue;
  }
  throw err;
}

Prevention

When it happens

Trigger: resolveSite returns null when: (1) both entry.api and entry.careers_url are missing/empty/non-string; (2) they fail URL parsing; (3) protocol isn't https; (4) hostname fails ORACLE_HOST_RE. detect() catches the same resolveSite null and returns null, so this throw in fetch() means detect() was bypassed or the entry changed between detect() and fetch().

Common situations: The portals.yml entry for this provider has a missing or wrong-format careers_url. The entry was constructed by a batch script without the URL field. A config merge overwrote the correct URL with an empty or different value. The entry's careers_url points to a non-Oracle FA host (e.g. a legacy Taleo URL).

Related errors


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