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
- Add a valid careers_url to the portals.yml entry: 'https://<tenant>.breezy.hr'.
- Or set the explicit api field to 'https://<tenant>.breezy.hr/json'.
- Check YAML indentation — the field must sit directly under the entry, not nested one level deeper.
- 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
- Require careers_url on every portals entry via a config schema check at load time.
- Check YAML indentation so careers_url sits directly under the entry object.
- Run provider.detect() over all entries at startup; null detections surface missing fields early.
- Re-verify entries after a company changes ATS vendor.
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
- arbeitnow: invalid URL
- arbeitsagentur: entry
- collage: cannot derive API URL for
- comeet: invalid URL
- deutschebahn: cannot resolve db.jobs search id for
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 dedupView on GitHub (pinned to aac998c7ed)