-
Notifications
You must be signed in to change notification settings - Fork 13
Scanner Providers
As of v1.127.0 the scanner ships 70 adapters — 65 EN-region + 5 RU. All scanning is in-process and zero-token: adapters hit public job-board APIs/feeds directly over HTTPS; no LLM is involved and no external script is spawned.
Adding an EN board touches two registries:
-
Source meta registry —
server/lib/sources/<slug>.mjswith anexport const meta = { value, label, region, configKey? }block.server/lib/sources/registry.mjsreaddirSync-scans the folder at boot and dynamicallyimport()s every*.mjs(P-14, v1.69.0), so dropping a valid file auto-registers it (malformedmeta→ skipped with oneconsole.warn). This drivesGET /api/scan/sourcesand the#/scandropdown. -
Fetch-walk registry —
server/lib/portals/adapters/<slug>.mjsplus its entry inserver/lib/portals/registry.mjs::ALL_ADAPTERS(asserted=== 65with the exact sorted id list bytests/adapter-registry.test.mjs). Adapters implementmatches()/buildEndpoint()(string-or-null contract) and the fetch/parse walk overtracked_companies:inportals.yml.
RU sources additionally need a RU_DISPATCH row in server/lib/ru-scanner.mjs, keyed by the meta's configKey.
Kind: board-wide = one global feed searched by keyword · per-tenant = per-company slug/board (driven by tracked_companies:) · single-company = one fixed employer's careers backend.
id (value) |
Label | Kind | Notes |
|---|---|---|---|
greenhouse |
Greenhouse | per-tenant | boards-api.greenhouse.io/v1/boards/<slug>/jobs |
ashby |
Ashby | per-tenant | api.ashbyhq.com/posting-api/job-board/<slug> |
lever |
Lever | per-tenant |
api.lever.co/v0/postings/<slug>; v1.118.0: detects EU boards jobs.eu.lever.co → api.eu.lever.co
|
workable |
Workable | per-tenant | |
smartrecruiters |
SmartRecruiters | per-tenant | |
workday |
Workday | per-tenant | Reports workdayFallback in scan results; v1.119.0: browser-like headers (UA/accept-language/origin/referer derived from the CXS URL) clear Cloudflare-gated tenants |
recruitee |
Recruitee | per-tenant | |
teamtailor |
Teamtailor | per-tenant | Feed-based |
personio |
Personio | per-tenant | |
breezy |
Breezy HR | per-tenant | |
bamboohr |
BambooHR | per-tenant | |
rippling |
Rippling | per-tenant | ats.rippling.com/<slug> |
pinpoint |
Pinpoint | per-tenant | |
comeet |
Comeet | per-tenant | careers-api URL |
avature |
Avature | per-tenant | |
successfactors |
SAP SuccessFactors (multi-brand RMK brand paths preserved — v1.125.1) | per-tenant | |
jibeapply |
JibeApply (iCIMS) | per-tenant | Paginated (max 50 pages) — v1.117.0 |
softgarden |
softgarden | per-tenant | Cap 1000 jobs — v1.117.0 |
csod |
Cornerstone | per-tenant | 25/page pagination — v1.118.0 |
phenom |
Phenom | per-tenant | 100/page widget API — v1.118.0 |
radancy |
Radancy | per-tenant | Branded hosts carry no vendor token → provider:-selected only, endpoint-shape checked — v1.118.0 |
beesite |
beesite (GJB) | single-company | jobs.mercedes-benz.com backend, 100/page — v1.117.0 |
deutschebahn |
Deutsche Bahn | single-company |
db.jobs (anchored host regex) — v1.118.0 |
tkms |
TKMS | single-company |
jobs.tkmsgroup.com — v1.118.0 |
hecklerkoch |
Heckler & Koch | single-company | v1.118.0 |
meituan |
Meituan | single-company | zhaopin.meituan.com JSON API; per-entry keywords/max_pages, empty-page retry — v1.119.0 |
tencent |
Tencent | single-company | careers.tencent.com Query API; per-entry keywords/max_pages — v1.119.0 |
oraclecloud |
Oracle Cloud (ORC) | per-tenant |
recruitingCEJobRequisitions REST API of *.fa[.<region>][.ocs].oraclecloud.com tenants (JPMC, Oracle, BNY Mellon, Amex, …); siteNumber from careers_url, WAF-aware UA — v1.123.0 |
wttj |
Welcome to the Jungle | board-wide | public Algolia index (wttj_jobs_production_en), rotating creds bootstrapped from /api/env; explicit wttj: { queries: [...] } config required — v1.124.0 |
agenticjobs |
Agentic Jobs | board-wide | agentic-engineering-jobs.com server-rendered listing, parsed in-process — v1.124.0 |
jobvite |
Jobvite | per-tenant | zero-auth jobs.jobvite.com/api/company/<slug>/jobs; slug from careers_url — v1.124.0 |
gem |
Gem | per-tenant | public GraphQL batch endpoint jobs.gem.com/api/public/graphql/batch, ?board= per company — v1.124.0 |
alibaba |
Alibaba | single-company |
talent.alibaba.com/position/search JSON API (double-submit CSRF, no login); per-entry keywords/max_pages — v1.124.0 |
flowxtra |
Flowxtra | board-wide | no-auth cross-tenant aggregator app.flowxtra.com/api/central/jobs (paginated) — v1.127.0 |
vdab |
VDAB | board-wide | Flanders public employment service vindeenjob JSON API (www.vdab.be), keyword-driven, recall-first — v1.127.0 |
icims |
iCIMS | per-tenant | hosted-portal search pages careers-<tenant>.icims.com/jobs/search, auto-detected from any *.icims.com careers_url (distinct from jibeapply) — v1.127.0 |
rheinmetall |
Rheinmetall | single-company | v1.118.0 |
dassault |
Dassault Systèmes | single-company | Exalead feed |
amazon |
Amazon | single-company | |
ibm |
IBM | single-company | |
arbeitsagentur |
Arbeitsagentur | board-wide | German federal job board |
arbeitnow |
Arbeitnow | board-wide | |
remoteok |
RemoteOK | board-wide | |
remotive |
Remotive | board-wide | |
weworkremotely |
We Work Remotely | board-wide | |
workingnomads |
Working Nomads | board-wide | |
nodesk |
NoDesk | board-wide | |
himalayas |
Himalayas | board-wide | |
jobicy |
Jobicy | board-wide | |
jobspresso |
Jobspresso | board-wide | |
4dayweek |
4 Day Week | board-wide | |
justjoin |
JustJoin.it | board-wide | Polish IT board |
nofluffjobs |
NoFluffJobs | board-wide | Polish IT board |
solidjobs |
SolidJobs | board-wide | Polish IT board |
thehub |
The Hub | board-wide | Nordic startups |
landingjobs |
Landing.jobs | board-wide | |
getonbrd |
Get on Board | board-wide | LatAm |
glints |
Glints | board-wide | SE Asia |
jobstreet |
Jobstreet / SEEK | board-wide | SE Asia / ANZ |
themuse |
The Muse | board-wide | |
echojobs |
EchoJobs | board-wide | v1.118.0 |
larajobs |
LaraJobs | board-wide | RSS — v1.118.0 |
higheredjobs |
HigherEdJobs | board-wide | RSS category feeds — v1.117.0 |
hackernews |
Hacker News (Who is hiring) | board-wide | Monthly thread |
rss |
RSS | generic | Point it at any job RSS/Atom feed — the escape hatch when a board has no adapter |
Dispatched by ru-scanner.mjs::RU_DISPATCH and configured under russian_portals: in portals.yml (sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob"] is the default).
| id | Label | configKey | Notes |
|---|---|---|---|
hh.ru |
hh.ru | hh |
HTML scrape of hh.ru/search/vacancy (the JSON API 403s programmatic clients) |
habr-career |
Habr Career | habr |
career.habr.com/api/... |
trudvsem |
Trudvsem | trudvsem |
Russian federal job board |
getmatch |
GetMatch | getmatch |
|
geekjob |
GeekJob | geekjob |
The full walkthrough lives in the in-app help guide §17 ("How to add a new job-portal source"). Summary:
-
Create
server/lib/sources/<slug>.mjsexportingmeta = { value, label, region: 'en'|'ru', configKey? }and asearch<Name>(query, opts)returning normalized job objects:{ id, title, company, url, salary, location, isRemote, workplaceType, relocates, date, snippet, source }. It auto-registers at boot — no registry edit. -
EN boards: add
server/lib/portals/adapters/<slug>.mjsimplementing the adapter contract —matches(url)andbuildEndpoint(...)(must return a string or null, never an object) — and register it inserver/lib/portals/registry.mjs::ALL_ADAPTERS. Update the count assertions intests/adapter-registry.test.mjsandtests/scan-sources-endpoint.test.mjs. -
RU sources: add a
RU_DISPATCHrow inru-scanner.mjs. -
SSRF host-pinning (mandatory): every adapter pins its hostname with an anchored regex (e.g.
/(^|\.)justjoin\.it$/i— sojustjoin.it.evil.comcan't spoof) and rejectshttp:. Adapters with no stable vendor host (e.g. Radancy) validate the endpoint path shape and are selected only via an explicitprovider:key inportals.yml. Parity tests must cover: evil host rejected,http:rejected, suffix-spoof rejected, parse happy path, pagination stop condition. -
Config-driven providers: POST/GraphQL/paginated boards thread
opts.companythroughbuildEndpoint; keepbuildEndpointreturning a string. - Add a parity test suite (
tests/sources-parity-*.test.mjs, stubbed transport — no network), bump the registry counts, and fan out docs ×16 per the Release Process.
- Entry point:
GET /api/stream/scan?source=ats|regional|both(SSE, onedoneper phase,final:trueon the last). -
dryRun=1skips writes todata/scan-history.tsv/data/last-scan.json;company=<slug>narrows the ATS walk. - Results are deduped against
data/scan-history.tsv, sanitized (scan-sanitize.mjs), quarantine-filtered (scan-quarantine.mjs), location-filtered, and matched against your target roles / two-pager (◎badge). - Client disconnect aborts in-flight upstream fetches (
AbortSignal).
Since v1.119.1 the #/scan Source dropdown's offline fallback (FALLBACK_SOURCES in public/js/views/scan.js) is gated by tests/scan-fallback-sources.test.mjs — exact value+label parity with the source registry, so a new adapter can't ship without the client list.
career-ops-ui v1.209.0 · Repository · Releases · Issues
Guides
Reference
Development
Languages (Home)