santifer/career-ops · error · Error
jobstreet: invalid URL
Error message
jobstreet: invalid URL: ${url} What it means
assertJobstreetUrl validates URLs used by the Jobstreet/SEEK provider. It throws 'invalid URL' when new URL(url) throws, meaning the input is not a well-formed absolute URL. This is the first of three gates (parse → https → host allowlist) protecting the server-side fetch from malformed config and SSRF.
Solutions
- Set the api field to a full absolute URL, e.g. https://id.jobstreet.com/api/jobsearch/v5/search
- Trim/inspect the raw config value for stray whitespace or placeholder text
- Verify with new URL(value) in a Node REPL
- Prefer omitting api entirely and letting the provider use DEFAULT_API
Example fix
// before (portals.yml) api: id.jobstreet.com/api/jobsearch/v5/search // after api: https://id.jobstreet.com/api/jobsearch/v5/search
Defensive patterns
Strategy: validation
Validate before calling
function isValidJobstreetUrl(url) {
try {
const p = new URL(url);
const allowed = ['id.jobstreet.com','www.jobstreet.com','www.jobstreet.co.id','jobstreet.com','jobstreet.co.id','sg.jobstreet.com','my.jobstreet.com','hk.jobsdb.com','www.seek.com.au','www.seek.co.nz'];
return p.protocol === 'https:' && allowed.includes(p.hostname);
} catch { return false; }
} Type guard
function isParseableAbsoluteUrl(v) {
return typeof v === 'string' && URL.canParse(v);
} Try / catch
try {
assertJobstreetUrl(cfg.api);
} catch (e) {
if (/invalid URL/.test(e.message)) {
console.error(`jobstreet api must be an absolute https:// URL, got: ${JSON.stringify(cfg.api)}`);
return [];
}
throw e;
} Prevention
- Omit the api field to use the provider's DEFAULT_API instead of hand-writing URLs
- Always include the https:// scheme in configured endpoints
- Trim YAML values; a trailing newline can make URL parsing fail
- Pick host + siteKey pairs consistently (e.g. hk.jobsdb.com with HK-Main) and validate with the allowlist in mind
When it happens
Trigger: Calling assertJobstreetUrl with '', 'id.jobstreet.com/api/jobsearch/v5/search' (no scheme), 'https://', or any other string the URL constructor rejects.
Common situations: The api: field in a portals.yml jobstreet entry written without https://; env-var placeholders left unsubstituted; trailing whitespace/newlines from YAML editing; relative endpoint paths configured instead of absolute URLs.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- jobbankca: invalid URL
- jobspresso: invalid URL
- himalayas: untrusted hostname
- itviec: untrusted hostname
- jobstreet: URL must use HTTPS
AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-16).
Data as JSON: /api/errors/bf6bdebc2c61dd9f.
Report an issue: GitHub.
Appendix: source
Thrown at providers/jobstreet.mjs:82
// — my/sg.jobstreet.com, www.seek.com.au, www.seek.co.nz — serves /job/<id> and
// answers 404 on /id/job/<id> (verified against live ids, 2026-08-28). A global
// switch either way breaks one market, which is why this is keyed on the host.
const ID_LOCALE_HOSTS = new Set(['id.jobstreet.com', 'www.jobstreet.co.id', 'jobstreet.co.id']);
/** @param {string} origin — scheme + hostname */
function jobDetailPath(origin) {
let host = '';
try { host = new URL(origin).hostname; } catch { /* fall through to the common path */ }
return ID_LOCALE_HOSTS.has(host) ? '/id/job/' : '/job/';
}
/** @param {string} url */
function assertJobstreetUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`jobstreet: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`jobstreet: URL must use HTTPS: ${url}`);
if (!ALLOWED_JOBSTREET_HOSTS.has(parsed.hostname))
throw new Error(`jobstreet: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_JOBSTREET_HOSTS].join(', ')}`);
return url;
}
/**
* Derive the origin from the API hostname.
* e.g. id.jobstreet.com → https://id.jobstreet.com
* @param {string} apiUrl
* @returns {string}
*/
function deriveOrigin(apiUrl) {
try {
const parsed = new URL(apiUrl);
return `${parsed.protocol}//${parsed.hostname}`;
} catch {View on GitHub (pinned to e7abd431fc)