santifer/career-ops · error

bamboohr: invalid URL

Error message

bamboohr: invalid URL: ${url}

What it means

The BambooHR provider's assertBambooHRUrl first checks that the string parses as a URL via `new URL()`; unparseable input throws this error. It mirrors the Ashby gate: parseability, then HTTPS, then hostname pattern, in that order.

Solutions

  1. Inspect the exact URL value; ensure it is a full absolute URL including the scheme.
  2. Use `https://<tenant>.bamboohr.com` as the origin form in config, not a bare tenant name.
  3. Trim whitespace and expand any template placeholders in the config entry.
  4. Build origins with `new URL('https://' + tenant + '.bamboohr.com')` and validate the tenant name first (no slashes/spaces).

Example fix

// before
const origin = 'mycompany.bamboohr.com';
assertBambooHRUrl(`${origin}/careers/list`); // throws: not parseable

// after
const origin = 'https://mycompany.bamboohr.com';
assertBambooHRUrl(`${origin}/careers/list`); // ok
Defensive patterns

Strategy: validation

Validate before calling

function isValidBambooUrl(url) {
  if (typeof url !== 'string' || !url.trim()) return false;
  try { new URL(url.trim()); return true; } catch { return false; }
}

Type guard

function parseUrlSafe(value) {
  try { return { ok: true, url: new URL(value) }; } catch { return { ok: false }; }
}

Try / catch

try {
  assertBambooHRUrl(apiUrl);
} catch (err) {
  console.warn(`Skipping BambooHR entry: ${err.message}`);
  return null;
}

Prevention

When it happens

Trigger: Calling assertBambooHRUrl (or the fetch path that builds `<origin>/careers/list` and validates it) with a string `new URL()` rejects — empty string, missing scheme like `mycompany.bamboohr.com`, whitespace, or a malformed origin built from a bad tenant name.

Common situations: portals.yml BambooHR entries with just a tenant subdomain instead of the full URL, placeholders like `<tenant>.bamboohr.com` left literally in config, or an empty tenant value producing an empty/invalid origin string.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at providers/bamboohr.mjs:25

// Auto-detects from careers_url pattern `https://<tenant>.bamboohr.com[/...]`.
// Per-tenant subdomains are the variable part, so SSRF defence uses a regex
// match on `<safe-tenant>.bamboohr.com` rather than a static allowlist
// (same approach as the recruitee provider).
//
// The list endpoint (`/careers/list`) returns lightweight metadata — enough for
// the Job contract (title, url, location) at zero token cost. The full JD lives
// behind a second `/careers/<id>/detail` request, which the scanner deliberately
// skips to stay zero-token (so `description`/`postedAt` are omitted).

const BAMBOOHR_HOST_RE = /^[a-z0-9][a-z0-9-]*\.bamboohr\.com$/;

/** @param {string} url */
function assertBambooHRUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`bamboohr: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`bamboohr: URL must use HTTPS: ${url}`);
  if (!BAMBOOHR_HOST_RE.test(parsed.hostname)) {
    throw new Error(`bamboohr: untrusted hostname "${parsed.hostname}" — must match <tenant>.bamboohr.com`);
  }
  return url;
}

/**
 * Resolve the tenant origin (`https://<tenant>.bamboohr.com`) from an entry.
 * Honours an explicit `api:` URL, else parses `careers_url`.
 * @param {import('./_types.js').PortalEntry} entry
 * @returns {string | null}
 */
function resolveOrigin(entry) {
  const rawApi = typeof entry.api === 'string' ? entry.api : '';
  const rawCareers = typeof entry.careers_url === 'string' ? entry.careers_url : '';
  const raw = (rawApi || rawCareers).trim();

View on GitHub (pinned to aac998c7ed)