santifer/career-ops · error · Error

comeet: cannot derive API URL for ${entry.name} (set api: to

Error message

comeet: cannot derive API URL for ${entry.name} (set api: to the full careers-api positions URL)

What it means

The primary comeet misconfiguration error: resolveApiUrl(entry) returned null, meaning neither entry.api nor entry.careers_url is a full Comeet careers-api positions URL. Comeet's positions endpoint needs both a company uid and a per-tenant token, neither of which is derivable from a branded careers page, so the full https://www.comeet.co/careers-api/2.0/company/<uid>/positions?token=<token> URL must be supplied explicitly via the api: field. This is the error real users hit; the four assertComeetUrl errors (161-164) are pre-empted by it.

Source

Thrown at providers/comeet.mjs:88

  const parsed = Date.parse(value);
  return Number.isNaN(parsed) ? undefined : parsed;
}

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

  detect(entry) {
    const apiUrl = resolveApiUrl(entry);
    // The DetectHit url is informational (the framework may log it), so strip
    // the secret ?token= before returning it — fetch() re-resolves the real
    // URL from the entry, so redaction here is safe.
    return apiUrl ? { url: redactToken(apiUrl) } : null;
  },

  async fetch(entry, ctx) {
    const apiUrl = resolveApiUrl(entry);
    if (!apiUrl) throw new Error(`comeet: cannot derive API URL for ${entry.name} (set api: to the full careers-api positions URL)`);
    assertComeetUrl(apiUrl);
    // redirect:'error' prevents SSRF via server-side redirects; combined with
    // assertComeetUrl above it guarantees the final hostname stays www.comeet.co.
    const json = await ctx.fetchJson(apiUrl, { redirect: 'error' });
    return parseComeetResponse(json, entry.name);
  },
};

/**
 * Parse a Comeet careers-api positions response. Exported for unit tests.
 *
 * Comeet returns a top-level ARRAY of position objects:
 *   [{ name, location: { name, is_remote }, url_active_page,
 *      url_comeet_hosted_page, time_updated, ... }]
 *
 * - url: prefer `url_active_page` (the tenant's live careers page), fall back to
 *   `url_comeet_hosted_page` (the Comeet-hosted page). Both are public, display-
 *   only URLs (recorded in the pipeline/history, never server-fetched here), so

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Set entry.api to the full positions URL from Comeet's careers-API docs, including the ?token= query param: https://www.comeet.co/careers-api/2.0/company/<uid>/positions?token=<token>.
  2. If you only have the branded page, obtain the careers-api URL from Comeet's admin/recruiter dashboard — the token is not scrapable from the public page.
  3. Gate with provider.detect(entry) (returns null when resolveApiUrl does) so misconfigured entries are skipped instead of throwing.

Example fix

# before — branded page, no token, will not resolve
- name: Acme
  provider: comeet
  careers_url: https://www.comeet.com/jobs/acme

# after — full careers-api positions URL with token
- name: Acme
  provider: comeet
  api: https://www.comeet.co/careers-api/2.0/company/abc/positions?token=SECRET_TOKEN
Defensive patterns

Strategy: validation

Validate before calling

// detect() returns null iff resolveApiUrl does — i.e. exactly when fetch() would throw 165.
import comeet from './providers/comeet.mjs';
if (!comeet.detect(entry)) {
  // entry.api is not a full https://www.comeet.co/careers-api/.../positions?token=... URL — set it
}

Type guard

/** True when entry has a usable Comeet careers-api positions URL (with token). */
function isComeetEntry(entry) {
  const raw = typeof entry?.api === 'string' ? entry.api : '';
  if (!raw) return false;
  try {
    const p = new URL(raw);
    return p.protocol === 'https:' && p.hostname === 'www.comeet.co' && p.pathname.startsWith('/careers-api/') && p.searchParams.has('token');
  } catch { return false; }
}

Try / catch

try { await comeet.fetch(entry, ctx); }
catch (e) {
  if (/^comeet: cannot derive API URL/.test(e.message)) {
    // config issue — set entry.api to the full positions URL; do not retry
  } else throw e;
}

Prevention

When it happens

Trigger: entry.api is absent and entry.careers_url is the branded www.comeet.com/jobs/... page (no token, wrong host, wrong path); or entry.api is set but is not https / not on www.comeet.co / not under /careers-api/. isComeetApiUrl returns false for both fields, so resolveApiUrl returns null.

Common situations: Copying the public careers page URL into careers_url and expecting the provider to derive the API call (it cannot — the token is secret and not on that page); pasting an http or www.comeet.com URL into api:; forgetting to migrate an old entry after Comeet renamed the endpoint.

Related errors


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