santifer/career-ops · error

breezy: cannot derive API URL for

Error message

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

What it means

The breezy provider's fetch() throws this when resolveOrigin(entry) returns null, i.e. neither entry.api nor entry.careers_url yields a usable https://<tenant>.breezy.hr origin. The provider cannot construct its board feed URL (<origin>/json) without one, so it fails fast instead of fetching a guess.

Solutions

  1. Add a valid careers_url to the portals.yml entry: 'https://<tenant>.breezy.hr'.
  2. Or set the explicit api field to 'https://<tenant>.breezy.hr/json'.
  3. Check YAML indentation — the field must sit directly under the entry, not nested one level deeper.
  4. Confirm the company still hosts its board on breezy.hr; if it migrated ATS, remove or repoint the entry.

Example fix

# before (entry missing careers_url)
- name: Acme
# after
- name: Acme
  careers_url: https://acme.breezy.hr
Defensive patterns

Strategy: validation

Validate before calling

function hasBreezyOrigin(entry) {
  const raw = (typeof entry.api === 'string' && entry.api) || (typeof entry.careers_url === 'string' && entry.careers_url) || '';
  if (!raw.trim()) return false;
  try {
    const u = new URL(raw.trim());
    return u.protocol === 'https:' && /^[a-z0-9][a-z0-9-]*\.breezy\.hr$/.test(u.hostname);
  } catch { return false; }
}
if (!hasBreezyOrigin(entry)) throw new Error(`entry ${entry.name} has no usable breezy origin`);

Type guard

function hasOriginField(entry) { return (typeof entry.api === 'string' && entry.api.trim() !== '') || (typeof entry.careers_url === 'string' && entry.careers_url.trim() !== ''); }

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('breezy: cannot derive API URL')) {
    console.warn(`Skipping ${entry.name}: no valid careers_url/api for breezy provider`);
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling provider.fetch with a portals entry that has no api or careers_url field, both fields empty/whitespace-only, non-string values, or values that fail to parse as https URLs matching the breezy hostname pattern (e.g. 'http://acme.breezy.hr' or 'https://acme.example.com').

Common situations: A portals.yml entry for a Breezy company where the careers_url was deleted during an edit; a YAML indentation mistake leaving the field nested under the wrong key; a company that moved off Breezy so its URL no longer matches the pattern; entries created before the api field convention existed.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/90aa38445421ab17. Report an issue: GitHub.

Appendix: source

Thrown at providers/breezy.mjs:66

    return null;
  }
  if (parsed.protocol !== 'https:') return null;
  if (!BREEZY_HOST_RE.test(parsed.hostname)) return null;
  return `https://${parsed.hostname}`;
}

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

  detect(entry) {
    const origin = resolveOrigin(entry);
    return origin ? { url: `${origin}/json` } : null;
  },

  async fetch(entry, ctx) {
    const origin = resolveOrigin(entry);
    if (!origin) throw new Error(`breezy: cannot derive API URL for ${entry.name}`);
    const apiUrl = `${origin}/json`;
    assertBreezyUrl(apiUrl);
    // redirect:'error' + the host check above keep the final hostname pinned to
    // the tenant — a server-side redirect can't bounce us off-domain (SSRF).
    const json = /** @type {any} */ (await ctx.fetchJson(apiUrl, { redirect: 'error' }));
    return parseBreezyResponse(json, entry.name);
  },
};

/**
 * Parse a Breezy `<tenant>.breezy.hr/json` response. Exported for unit tests.
 *
 * Breezy returns a top-level array of positions:
 *   [{ name, url, published_date?,
 *      location: { name?, city?, state?, country?: { name }, is_remote? } }]
 *
 * - url: Breezy supplies an absolute posting URL on the tenant domain
 *   (`https://<tenant>.breezy.hr/p/<id>-<slug>`); it is the Job contract's dedup

View on GitHub (pinned to aac998c7ed)