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

  1. Add the full API URL to the entry: `api: https://api.collage.co/v1/positions/<job-site-address>` in portals.yml
  2. Or set `careers_url:` to the public site URL shaped like https://secure.collage.co/jobs/<site-address> (no dots in the last segment)
  3. Confirm detect() recognized the entry — if detect returned null the provider skipped it; fetch() only runs when detection raced or was bypassed
  4. 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

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


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)