santifer/career-ops · error · Error

nofluffjobs: careers_url or api must be a trusted nofluffjob

Error message

nofluffjobs: careers_url or api must be a trusted nofluffjobs.com URL

What it means

Thrown by nofluffjobs' fetch() when detectUrl(entry) returns null — meaning the entry has no usable api or careers_url that passes the full SSRF guard chain. This is a config-completeness check: fetch() re-validates detect()'s preconditions and refuses to proceed if the entry cannot resolve to a trusted URL. It prevents fetch() from silently doing nothing or hitting an unvalidated URL.

Source

Thrown at providers/nofluffjobs.mjs:138

        url: `${JOB_BASE}${slug}`,
        company,
        location: normalizeLocation(posting),
        postedAt: postedAtMillis(posting.posted),
      };
    })
    .filter(Boolean);
}

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

  detect(entry) {
    return detectUrl(entry);
  },

  async fetch(entry, ctx) {
    if (!detectUrl(entry)) throw new Error('nofluffjobs: careers_url or api must be a trusted nofluffjobs.com URL');

    const maxPages = Number(entry.max_pages || MAX_PAGES);
    const jobs = [];
    const seen = new Set();

    for (let pageTo = 1; pageTo <= maxPages; pageTo++) {
      const { url, body } = buildRequest(entry, pageTo);
      const json = await ctx.fetchJson(url, {
        method: 'POST',
        body: JSON.stringify(body),
        headers: {
          accept: 'application/json, text/plain, */*',
          'content-type': 'application/infiniteSearch+json',
        },
        redirect: 'error',
      });
      for (const job of parseNoFluffJobsResponse(json)) {
        if (seen.has(job.url)) continue;

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Check the portals.yml entry: it must have either api or careers_url set to a valid https://nofluffjobs.com URL.
  2. Ensure detect() and fetch() receive the same entry object — if entries are being rebuilt or merged, verify the nofluffjobs entry survives the process.
  3. If calling fetch() programmatically, call detect(entry) first and skip entries that return null.

Example fix

// before — entry has no usable URL
job_boards:
  nofluff:
    provider: nofluffjobs
    # missing api and careers_url

// after
job_boards:
  nofluff:
    provider: nofluffjobs
    api: 'https://nofluffjobs.com/api/search/posting'
    max_pages: 5
Defensive patterns

Strategy: validation

Validate before calling

/** Verify the entry can resolve to a trusted NoFluffJobs URL before fetch. */
function canFetchNoFluff(entry) {
  const url = entry.api || entry.careers_url || '';
  if (typeof url !== 'string' || !url.trim()) return false;
  try {
    const p = new URL(url);
    return p.protocol === 'https:' && p.hostname === 'nofluffjobs.com';
  } catch {
    return false;
  }
}

if (!canFetchNoFluff(entry)) {
  console.warn(`nofluffjobs entry ${entry.name} has no valid URL — skipping`);
  continue;
}

Type guard

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

Try / catch

try {
  await nofluffProvider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).includes('must be a trusted nofluffjobs.com URL')) {
    console.warn(`skipping ${entry.name}: no valid nofluffjobs URL in config`);
    continue;
  }
  throw err;
}

Prevention

When it happens

Trigger: detectUrl() returns null when: (1) entry.api and entry.careers_url are both empty/non-string; (2) the URL fails assertNoFluffUrl (wrong scheme, wrong host, unparseable) and detectUrl's catch returns null. If detect() returned non-null but the entry was mutated between detect() and fetch(), or if fetch() is called directly with an unvalidated entry, this fires.

Common situations: A portals.yml entry is missing both api and careers_url fields. The entry has a URL that was valid for a different provider (wrong host). The entry object was constructed programmatically (e.g. by a batch script) without the required URL field. A race condition or config reload changes the entry between detect() and fetch().

Related errors


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