santifer/career-ops · error
collage: cannot derive API URL for
Error message
collage: cannot derive API URL for ${entry.name} What it means
The collage provider's fetch() calls resolveApiUrl(entry) to turn a portal entry into a concrete Collage API URL. It accepts an explicit `entry.api` (validated /v1/positions/<address> URL) or derives one from an `entry.careers_url` on secure.collage.co of the form /jobs/<site>. When neither yields a URL, the provider cannot know which tenant to query and throws this error naming the entry.
Solutions
- Add the full API URL to the entry: `api: https://api.collage.co/v1/positions/<job-site-address>` in portals.yml
- Or set `careers_url:` to the public site URL shaped like https://secure.collage.co/jobs/<site-address> (no dots in the last segment)
- Confirm detect() recognized the entry — if detect returned null the provider skipped it; fetch() only runs when detection raced or was bypassed
- Run the entry through resolveApiUrl's checks manually: https scheme, secure.collage.co host, single-segment /jobs/ path
Example fix
// before (portals.yml) - name: Acme provider: collage careers_url: https://acme.com/careers // after - name: Acme provider: collage api: https://api.collage.co/v1/positions/acme-jobs # or: careers_url: https://secure.collage.co/jobs/acme-jobs
Defensive patterns
Strategy: validation
Validate before calling
function hasCollageTarget(entry) {
if (typeof entry.api === 'string' && entry.api.trim()) return true;
if (typeof entry.careers_url !== 'string') return false;
try {
const u = new URL(entry.careers_url.trim());
return u.protocol === 'https:' && u.hostname === 'secure.collage.co' && /^\/jobs\/[^/]+\/?$/.test(u.pathname);
} catch { return false; }
}
// run over portals.yml entries before scanning Type guard
function isDerivableCollageEntry(entry) {
const site = typeof entry?.careers_url === 'string' ? entry.careers_url.trim() : '';
return Boolean(typeof entry?.api === 'string' && entry.api.trim()) || /^https:\/\/secure\.collage\.co\/jobs\/[^/.]+\/?$/.test(site);
} Try / catch
try {
await provider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).startsWith('collage: cannot derive API URL')) {
logger.error({entry: entry.name}, 'collage entry misconfigured: set api: to https://api.collage.co/v1/positions/<site>');
} else throw err;
} Prevention
- Always prefer the explicit api: field with the full https://api.collage.co/v1/positions/<address> URL
- Validate portals.yml entries at load time with a schema check per provider
- Copy the secure.collage.co/jobs/<site> URL directly from the tenant's public jobs page, not their marketing site
- Run detect()/dry-run over all entries after every portals.yml edit
When it happens
Trigger: fetch() is called with a portals.yml entry whose `api` is missing/empty and whose `careers_url` is absent, not https, not on secure.collage.co, not a /jobs/<site> path, or whose last path segment contains a dot — resolveApiUrl returns null.
Common situations: Configuring a Collage company using its branded marketing URL (acme.com/careers) instead of the secure.collage.co/jobs/<site> address; typos in careers_url (http instead of https); forgetting the `api:` field entirely; using the Collage admin app URL which has a dotted subdomain segment.
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
- breezy: cannot derive API URL for
- comeet: invalid URL
- deutschebahn: cannot resolve db.jobs search id for
- icims: cannot derive portal origin for
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/13edd6e846cb26cf.
Report an issue: GitHub.
Appendix: source
Thrown at providers/collage.mjs:101
].filter(Boolean).join('\n');
const description = [text(j.descriptionPlain), metadata].filter(Boolean).join('\n\n');
return {
title: text(j.title), url, company: companyName,
location, description,
postedAt: toEpochMs(j.createdDate ?? j.createdAt ?? j.publishedAt),
};
}).filter(Boolean);
}
/** @type {Provider} */
export default {
id: 'collage',
detect(entry) {
try { const url = resolveApiUrl(entry); return url ? { url } : null; } catch { return null; }
},
async fetch(entry, ctx) {
const apiUrl = resolveApiUrl(entry);
if (!apiUrl) throw new Error(`collage: cannot derive API URL for ${entry.name}`);
const json = await ctx.fetchJson(apiUrl, { redirect: 'error' });
return parseCollageResponse(json, entry.name);
},
};
View on GitHub (pinned to aac998c7ed)