santifer/career-ops · error
feishu-jobs: careers_url must use HTTPS on…
Error message
feishu-jobs: careers_url must use HTTPS on jobs.bytedance.com or a *.jobs.feishu.cn tenant
What it means
feishu-jobs provider fetch() resolves the tenant origin from entry.careers_url via resolveFeishuOrigin(), which only accepts HTTPS URLs on jobs.bytedance.com or a *.jobs.feishu.cn subdomain. If careers_url is missing, not a string, unparseable, non-HTTPS, or on any other host, fetch throws this error because it cannot build the /api/v1/search/job/posts endpoint. This is the provider's SSRF/trust boundary, shared with detect().
Solutions
- Set careers_url to the exact origin: https://jobs.bytedance.com for ByteDance, or the tenant's https://<subdomain>.jobs.feishu.cn for third-party tenants (e.g. https://vrfi1sk8a0.jobs.feishu.cn for MiniMax).
- Ensure the value is a full URL with https:// scheme — resolveFeishuOrigin returns null for bare hostnames and silently skips them.
- Verify the hostname: it must be exactly jobs.bytedance.com or end with .jobs.feishu.cn; a company's own domain that fronts a Feishu board will not pass — find the underlying feishu.cn tenant origin (check where the careers site's API calls go).
- If the entry is not actually a Feishu Jobs board, remove or correct the provider: feishu-jobs pinning so detect() picks the right provider.
Example fix
// before - name: MiniMax careers_url: https://www.minimaxi.com/careers // after - name: MiniMax careers_url: https://vrfi1sk8a0.jobs.feishu.cn
Defensive patterns
Strategy: validation
Validate before calling
function isFeishuOrigin(value) {
if (typeof value !== 'string') return false;
try {
const u = new URL(value);
return u.protocol === 'https:' &&
(u.hostname === 'jobs.bytedance.com' || u.hostname.endsWith('.jobs.feishu.cn'));
} catch { return false; }
} Type guard
const hasValidFeishuCareersUrl = (entry) => isFeishuOrigin(entry?.careers_url);
Try / catch
try {
const jobs = await feishuProvider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).includes('must use HTTPS on jobs.bytedance.com')) {
console.error(`Fix ${entry.name}.careers_url: needs https://jobs.bytedance.com or a *.jobs.feishu.cn origin`);
return [];
}
throw err;
} Prevention
- Use the exact careers-site origin (scheme + host, no path needed)
- For ByteDance use https://jobs.bytedance.com; for other tenants find their *.jobs.feishu.cn subdomain
- Never point the entry at the company's own marketing/careers domain — find the underlying Feishu tenant origin
- Sanity-check entries with provider.detect() before batch scans
When it happens
Trigger: fetch(entry) where entry.careers_url is undefined, an empty or malformed string ('jobs.bytedance.com' without scheme), uses http:, or points at a host outside the allowlist (e.g. a company's own domain that merely embeds a Feishu widget, or www.bytedance.com). Also fires when an explicit provider: feishu-jobs selection bypasses detect() on an incompatible entry.
Common situations: Configuring a company that uses Feishu Jobs internally but hosts careers on its own domain — the real tenant URL is its *.jobs.feishu.cn subdomain which must be found and used; typos or missing https:// in portals.yml; copy-pasting a marketing URL instead of the careers-site origin.
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
- eightfold: untrusted hostname
- flowxtra: invalid URL
- a16z-speedrun-talent: invalid URL
- Access denied: Localhost or internal domain target detected.
- agentic-jobs: invalid URL
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/854ab49aeb3927f1.
Report an issue: GitHub.
Appendix: source
Thrown at providers/feishu-jobs.mjs:133
postedAt: Number.isFinite(p.publish_time) ? p.publish_time : undefined,
});
}
return { jobs, total };
}
/** @type {Provider} */
export default {
id: 'feishu-jobs',
detect(entry) {
const origin = resolveFeishuOrigin(entry.careers_url);
return origin ? { url: origin } : null;
},
async fetch(entry, ctx) {
const origin = resolveFeishuOrigin(entry.careers_url);
if (!origin) {
throw new Error('feishu-jobs: careers_url must use HTTPS on jobs.bytedance.com or a *.jobs.feishu.cn tenant');
}
const api = `${origin}/api/v1/search/job/posts`;
const keywords = Array.isArray(entry.keywords) && entry.keywords.length
? entry.keywords
: DEFAULT_KEYWORDS;
const entryLimit = Number(entry.max_pages);
const probeLimit = Number(ctx?.maxPages);
const entryMaxPages = Number.isSafeInteger(entryLimit) && entryLimit > 0
? entryLimit
: DEFAULT_MAX_PAGES;
const probeMaxPages = Number.isSafeInteger(probeLimit) && probeLimit > 0
? probeLimit
: Infinity;
const maxPages = Math.min(entryMaxPages, probeMaxPages);
/** @type {Map<string, import('./_types.js').Job>} */
const seen = new Map();View on GitHub (pinned to aac998c7ed)