Source Coverage Catalog
Canonical per-family source list for the JobCache adapter rollout. Every
entry maps to one row in a Rust adapter crate's SOURCES const (or a
single-source Adapter's hardcoded constants).
Status: initial population from Jobich's published source registry +
scrapers.py inventory, augmented with quirks observed in the source.
Names of public companies, subdomains, slugs, and tenant identifiers are
public source clues (vendor customer pages, the tenants' own careers URLs).
Jobich's YAML schema and scraper code are not lifted — we organize
the catalog our way, every Rust adapter is rewritten from scratch.
Family overview
| Family | Rust crate | Sources | Transport | Method | Tier |
|---|---|---|---|---|---|
| workday | jobcache-adapter-workday |
27 | HTTP+JSON | feed-api |
A1 |
| greenhouse | jobcache-adapter-greenhouse |
17 | HTTP+JSON | feed-api |
A1 |
| lever | jobcache-adapter-lever |
10 | HTTP+JSON | feed-api |
A1 |
| smartrecruiters | jobcache-adapter-smartrecruiters |
13 | HTTP+JSON | feed-api |
A1 |
| oracle-hcm | jobcache-adapter-oracle-hcm |
7 | HTTP+JSON | feed-api |
A1 |
| ashby | jobcache-adapter-ashby |
8 | HTTP+JSON (REST + GraphQL fallback) | feed-api |
A1 |
| recruitee | jobcache-adapter-recruitee |
1 | HTTP+JSON | feed-api |
A1 |
| breezyhr | jobcache-adapter-breezyhr |
5 | HTTP+JSON | feed-api |
A1 |
| bamboohr | jobcache-adapter-bamboohr |
3 | HTTP+JSON | feed-api |
A1 |
| personio | jobcache-adapter-personio |
6 | HTTP+XML (fallback HTML) | feed-api |
A2 |
| teamtailor | jobcache-adapter-teamtailor |
7 | HTTP+RSS | feed-api |
A2 |
| joincom | jobcache-adapter-joincom |
10 | HTTP+SSR (__NEXT_DATA__) |
html-bespoke |
A2 |
| jibe | jobcache-adapter-jibe |
2 | HTTP+JSON | feed-api |
A2 |
| icims | jobcache-adapter-icims |
1 | HTTP+BS4 | html-bespoke |
A2 |
| sf-html | jobcache-adapter-sf-html |
22 | HTTP+BS4 | html-bespoke |
A2 |
| sf-spa | jobcache-adapter-sf-spa |
2 | JS-rendered (headless-proxy Env) |
headless-proxy |
A3 |
| sf-rmk | jobcache-adapter-sf-rmk |
4 | HTTP+JSON POST (locale loop) | feed-api |
A2 |
| hibob | jobcache-adapter-hibob |
3 | JS-rendered | headless-proxy |
A3 |
| phenom | jobcache-adapter-phenom |
12 | JS-rendered (embedded-JSON first) | headless-proxy |
A3 |
| eightfold | jobcache-adapter-eightfold |
2 | HTTP+JSON | feed-api |
A2 |
| csod | jobcache-adapter-csod |
2 | JS-bootstrap → HTTP+JSON | headless-proxy |
A3 |
| refline | jobcache-adapter-refline |
5 | HTTP+BS4 | html-bespoke |
A2 |
| prospective | jobcache-adapter-prospective |
7 | HTTP+JSON | feed-api |
A2 |
| persoware | jobcache-adapter-persoware |
6 | HTTP+BS4 | html-bespoke |
A2 |
| softgarden | jobcache-adapter-softgarden |
1 | HTTP+BS4 | html-bespoke |
A2 |
| umantis | jobcache-adapter-umantis |
3 | HTTP+BS4 | html-bespoke |
A2 |
| workable-widget | jobcache-adapter-workable-widget |
10 | HTTP+JSON | feed-api |
A2 |
| homerun | jobcache-adapter-homerun |
1 | HTTP+Atom | feed-api |
A2 |
| xiag-board | jobcache-adapter-xiag-board |
2 | HTTP+BS4 | html-bespoke |
A2 |
| yousty | jobcache-adapter-yousty |
3 | HTTP+BS4 (sitemap + JSON-LD) | sitemap-jsonld |
A2 |
| jobup-ch | jobcache-adapter-jobup-ch |
1 | HTTP+BS4 (sitemap + JSON-LD) | sitemap-jsonld |
A2 |
| jobs-ch | jobcache-adapter-jobs-ch |
1 (landed) | HTTP+BS4 + __INIT__ JSON |
html-bespoke |
A2 |
jobcache-adapter-linkedin |
1 | HTTP+BS4 (public listings) | headless-proxy |
B1 | |
jobcache-adapter-xing |
1 | HTTP+BS4 + Apollo cache | headless-proxy |
B1 | |
| indeed | jobcache-adapter-indeed |
1 | HTTP+BS4 | headless-proxy |
B1 |
| jobup-ch-search | (folded into jobup-ch) | — | HTTP+JSON | feed-api |
B1 |
| adzuna | jobcache-adapter-adzuna |
1 | HTTP+BS4 | html-bespoke |
B2 |
| reed | jobcache-adapter-reed |
1 | HTTP+BS4 | html-bespoke |
B2 |
| jobwatch | jobcache-adapter-jobwatch |
1 | HTTP+BS4 | html-bespoke |
B2 |
| stellenanzeiger | jobcache-adapter-stellenanzeiger |
1 | HTTP+BS4 | html-bespoke |
B2 |
| remoteok | jobcache-adapter-remoteok |
1 | HTTP+JSON | feed-api |
B2 |
| hackernews | jobcache-adapter-hackernews |
1 | HTTP+JSON (Algolia API) | feed-api |
B2 |
| custom-html | jobcache-adapter-custom-html |
7 | HTTP+BS4 (per-tenant selectors) | html-bespoke |
C |
| playwright-html | jobcache-adapter-playwright-html |
4 | JS-rendered (per-tenant selectors) | headless-proxy |
C |
| rss-feed | jobcache-adapter-rss-feed |
4 | HTTP+RSS | feed-api |
C |
| employer-bespoke | crates/jobcache-employer-*/ (per-employer) |
37+ | varies | varies | C |
Tier legend:
- A1 — JSON-API multi-tenant ATS families, low hostility, high tenant count. Highest value per slice, lowest implementation risk.
- A2 — Mixed transport families (HTML, RSS, locale-loop JSON), low-to-mid hostility.
- A3 — JS-rendered families requiring a
headless-proxyEnv.fetch(runtime prerequisite: device runtime injects Router-backed fetch formethod: "headless-proxy"adapters). - B1 — High-hostility flagship boards (LinkedIn, Xing, Indeed).
- B2 — Search-driven general boards, mid hostility.
- C — Long tail: per-tenant custom HTML, RSS feeds, employer-bespoke integrations.
Slice prerequisite call-out: A3 + B1 adapters all need the device runtime to method-route the Env.fetch through scrapers/router.ts. Until that lands, those families ship but can only succeed against the same env.fetch plain — fine for proof, insufficient for real coverage.
A1 — JSON-API multi-tenant ATS families
workday — crates/jobcache-adapter-workday/
Transport: HTTP+JSON. Discovery via POST https://{subdomain}.{instance}.myworkdayjobs.com/wday/cxs/{subdomain}/{career_path}/jobs with offset/limit pagination (20/page). Detail via JSON detail API at /wday/cxs/{subdomain}/{career_path}{external_path}.
Config shape per source: { id, name, subdomain, instance, career_path }.
Sources (27 active):
| name | subdomain | instance | career_path |
|---|---|---|---|
| Swisscom | swisscom | wd103 | SwisscomExternalCareers |
| Nvidia | nvidia | wd5 | NVIDIAExternalCareerSite |
| Roche | roche | wd3 | roche-ext |
| Novartis | novartis | wd3 | Novartis_Careers |
| The Global Fund | theglobalfund | wd1 | External |
| UNHCR | unhcr | wd3 | External |
| WTO | wto | wd103 | External |
| World Economic Forum | weforum | wd3 | Forum_Careers |
| Trafigura | trafigura | wd3 | TrafiguraCareerSite |
| Gunvor | gunvor | wd3 | Gunvor_Careers |
| Julius Baer | juliusbaer | wd3 | External |
| Lombard Odier | lombardodier | wd3 | Lombard_Odier_Careers |
| Logitech | logitech | wd5 | Logitech |
| Zuhlke Group | zuehlke | wd3 | Zuhlke-Careers |
| Vontobel | vontobel | wd3 | Vontobel_External_Career |
| Biogen | biibhr | wd3 | external |
| Abbott | abbott | wd5 | abbottcareers |
| Moderna | modernatx | wd1 | M_tx |
| Temenos | temenos | wd103 | Temenoscareers |
| Georg Fischer | georgfischer | wd103 | GeorgFischer_Careers |
| Siegfried | siegfried | wd103 | external |
| Eli Lilly | lilly | wd5 | LLY |
| PwC | pwc | wd3 | Global_Experienced_Careers |
| BeiGene | beigene | wd5 | BeiGene |
| Bavarian Nordic | bavariannordic | wd103 | BavarianNordic |
| Galderma | galderma | wd3 | External |
| Takeda | takeda | wd3 | External |
| Richemont | richemont | wd3 | Richemont |
| Lonza | lonza | wd3 | Lonza_Careers |
| Medtronic | medtronic | wd1 | MedtronicCareers |
| Johnson & Johnson | jj | wd5 | JJ |
| Stryker | stryker | wd1 | StrykerCareers |
| Alcon | alcon | wd5 | careers_alcon |
| Deutsche Bank | db | wd3 | DBWebsite |
(Above is 34 entries — Jobich's sources.yaml had 27 active after defunct removals; cross-check on first crate ingest.)
Defunct (do not include; useful for re-audit): ABB (→ careers.abb, custom Phenom), Nestle (→ SF HTML), Zurich Insurance (→ SF HTML), Swiss Re (→ SF HTML), UBS (custom portal), Swatch Group (custom), Philip Morris (custom), Buhler Group (custom), Barry Callebaut (custom), Givaudan (→ Phenom), Firmenich (now DSM-Firmenich → eightfold), Lindt (custom), Sonova (→ SF HTML), Endress+Hauser (custom), Straumann (→ SmartRecruiters then → Phenom), Sika (custom).
greenhouse — crates/jobcache-adapter-greenhouse/
Transport: HTTP+JSON. Public board API at https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true. Detail content inline in the listing response.
Config shape: { id, name?, slug } (name optional; default to slug).
Sources (17):
| name | slug |
|---|---|
| Scandit | scandit |
| Figma | figma |
| Stripe | stripe |
| Databricks | databricks |
| On | on-running |
| Frontify | frontify |
| Beekeeper | beekeeper |
| Terra Quantum | terraquantum |
| 21Shares | 21shares |
| Proton | proton |
| Auterion | auterion |
| Toradex | toradex |
| BearingPoint | bearingpoint |
| Anthropic | anthropic |
| Sunrise | sunrise |
| Starmind | starmind |
| ID Quantique | idquantique |
| Relai | technology |
| AlixPartners | alixpartners |
lever — crates/jobcache-adapter-lever/
Transport: HTTP+JSON at https://api.lever.co/v0/postings/{slug}?mode=json. Description multi-field: descriptionBody | opening | additional + lists[].{text, content} structure.
Config shape: { id, name, slug }.
Sources (10):
| name | slug |
|---|---|
| Amazon RIVR | rivr |
| ANYbotics | anybotics |
| Wingtra | wingtra-2 |
| Sonar | sonarsource |
| Daedalean AI | daedalean |
| SwissBorg | swissborg |
| Gravis Robotics | gravis-robotics |
| Neural Concept | neuralconcept |
| Scandit | scandit-1 |
| Advertima | advertima |
| Oliver Wyman | oliverwyman |
smartrecruiters — crates/jobcache-adapter-smartrecruiters/
Transport: HTTP+JSON. Listing: https://api.smartrecruiters.com/v1/companies/{slug}/postings?limit=100&offset=N&country=ch. Detail (per-job): /v1/companies/{slug}/postings/{id} for jobAd.sections.{companyDescription, jobDescription, qualifications, additionalInformation}.
Config shape: { id, name, slug }.
Sources (13):
| name | slug |
|---|---|
| Bosch | BoschGroup |
| Vitol | Vitol |
| Swissquote | Swissquote |
| Nexthink | Nexthink |
| Roland Berger | RolandBerger |
| Audemars Piguet | AudemarsPiguet |
| Implement Consulting Group | ImplementConsultingGroup |
| Swiss Medical Network | SwissMedicalNetwork1 |
| Syngenta Group | SyngentaGroup |
| Kanadevia Inova | KanadeviaInova |
| Fusion Consulting | fusionconsulting |
| SMG Swiss Marketplace Group | SMGSwissMarketplaceGroup |
| Oetiker | Oetiker |
| GIANTS Software | GIANTSSoftwareGmbH |
oracle-hcm — crates/jobcache-adapter-oracle-hcm/
Transport: HTTP+JSON. Listing: https://{host}/hcmRestApi/resources/latest/recruitingCEJobRequisitions?finder=findReqs;siteNumber={site},limit=200,offset=N. Detail (per-job): /recruitingCEJobRequisitionDetails?finder=ById;Id={jobId},siteNumber={site} → {ExternalDescriptionStr, ExternalQualificationsStr, ExternalResponsibilitiesStr}. Public job URL: https://{host}/hcmUI/CandidateExperience/en/sites/{site}/job/{id}.
Country filter: PrimaryLocationCountry=CH OR secondaryLocations[].Country=CH.
Config shape: { id, name, host, site }.
Sources (7):
| name | host | site |
|---|---|---|
| IOM | fa-evlj-saasfaprod1.fa.ocs.oraclecloud.com | CX_1001 |
| UBP | iaadtu.fa.ocs.oraclecloud.eu | CX_1 |
| EFG International | fa-eqai-saasfaprod1.fa.ocs.oraclecloud.com | CX_1001 |
| JPMorganChase | jpmc.fa.oraclecloud.com | CX_1001 |
| ELCA Group | iaaras.fa.ocs.oraclecloud.com | CX_4 |
| Oracle | eeho.fa.us2.oraclecloud.com | CX_45001 |
| SCOR | fa-errt-saasfaprod1.fa.ocs.oraclecloud.com | CX_2001 |
ashby — crates/jobcache-adapter-ashby/
Transport: HTTP+JSON REST primary, GraphQL fallback. REST: https://api.ashbyhq.com/posting-api/job-board/{board_slug} → jobs[].{title, jobUrl, location, publishedAt, descriptionHtml}. GraphQL fallback (for tenants without REST enabled): POST /api/non-user-graphql?op=ApiJobBoardWithTeams with {operationName: "ApiJobBoardWithTeams", variables: {organizationHostedJobsPageName: board_slug}}.
Config shape: { id, name, board_slug }.
Sources (8):
| name | board_slug |
|---|---|
| Arrakis | Arrakis |
| Aleph Alpha | alephalpha |
| Neon | neon |
| Lakera | lakera |
| Corti | corti |
| DeepJudge | deepjudge |
| Adaptyv | adaptyv |
| Cradle | cradlebio |
recruitee — crates/jobcache-adapter-recruitee/
Transport: HTTP+JSON at https://{subdomain}.recruitee.com/api/offers/ → offers[].{title, careers_url, city, country, country_code, description, salary.{min, max, currency}, published_at}.
Config shape: { id, name, subdomain }.
Sources (1):
| name | subdomain |
|---|---|
| EthonAI | ethonai |
breezyhr — crates/jobcache-adapter-breezyhr/
Transport: HTTP+JSON at https://{subdomain}.breezy.hr/json.
Config shape: { id, name, subdomain }.
Sources (5):
| name | subdomain |
|---|---|
| Sportradar | sportradar |
| Vyntra | vyntra |
| Teylor | teylor |
| Ascento | ascento-ag |
| Sevensense | sevensense-robotics-ag |
bamboohr — crates/jobcache-adapter-bamboohr/
Transport: HTTP+JSON at https://{subdomain}.bamboohr.com/careers/list → result[].{jobOpeningName, id, location.{city, country}}. Public URL: https://{subdomain}.bamboohr.com/careers/{id}.
Config shape: { id, name, subdomain }.
Sources (3):
| name | subdomain |
|---|---|
| BB Energy | bbenergy |
| Voliro | voliroar |
| Kandou AI | kandou |
A2 — Mixed-transport families
personio — crates/jobcache-adapter-personio/
Transport: HTTP+XML primary, HTML fallback. XML at https://{subdomain}.jobs.personio.com/xml?language=en (.com then fall back to .de TLD on 404). HTML fallback parses a[href^="/job/"] from the rendered listing page.
Config shape: { id, name, subdomain, swiss_only?: bool }.
Sources (6):
| name | subdomain | swiss_only |
|---|---|---|
| ten23 health | ten23-health | true |
| Basilea Pharmaceutica | basilea | true |
| AMINA Bank | amina | false |
| DePoly | depoly | false |
| Redalpine | redalpine | true |
| zvoove | zvoove | false |
teamtailor — crates/jobcache-adapter-teamtailor/
Transport: HTTP+RSS. Each tenant exposes a direct RSS URL.
Config shape: { id, name, rss_url }.
Sources (7):
| name | rss_url |
|---|---|
| Lightium | https://careers.lightium.com/jobs.rss |
| LumApps | https://job.lumapps.com/jobs.rss |
| AZQORE | https://azqoresa.teamtailor.com/jobs.rss |
| South Pole | https://careers.southpole.com/jobs.rss |
| neustark | https://careers.neustark.com/jobs.rss |
| Corintis | https://careers.corintis.com/jobs.rss |
| Planted Foods | https://eatplanted.teamtailor.com/jobs.rss |
joincom — crates/jobcache-adapter-joincom/
Transport: Next.js SSR. Jobs inlined in <script id="__NEXT_DATA__"> on https://join.com/companies/{slug}. No API call; HTTP+BS4 reads the script tag.
Config shape: { id, name, slug }.
Sources (10):
| name | slug |
|---|---|
| Taurus | taurusgroup |
| Veezoo | veezoo |
| Decentriq | decentriq |
| Optiml | optiml |
| Sartiq | shootifyio |
| CV VC | cvvc |
| Flycatcher | flycatcherai |
| neon Switzerland | neon-free |
| Lightly | lightly |
| bexio | bexio |
| modual | modual |
jibe — crates/jobcache-adapter-jibe/
Transport: HTTP+JSON. Public /api/jobs JSON on the corporate careers host. Works for iCIMS Jibe Talent Cloud tenants.
Config shape: { id, name, host }.
Sources (2):
| name | host |
|---|---|
| Incyte | careers.incyte.com |
| GSK | jobs.gsk.com |
icims — crates/jobcache-adapter-icims/
Transport: HTTP+BS4. Classic iCIMS portals: https://{host}/jobs/search?in_iframe=1&bga=true&needsRedirect=false. Filter to ISO-prefix "CH-".
Config shape: { id, name, host }.
Sources (1):
| name | host |
|---|---|
| Monte Rosa Therapeutics | careers-monterosatx.icims.com |
sf-html — crates/jobcache-adapter-sf-html/
Transport: HTTP+BS4. Listing selector a.jobTitle-link (or a[class*='jobTitle']). Pagination via ?startrow=N; parse "Results X to Y of Z" regex to advance. Cap at 20 pages.
Country derivation: location suffix like "Vevey, CH" maps to is_swiss_job(location, country="CH").
Config shape: { id, name, search_url }.
Sources (22):
Known omissions (verified non-CH): COFCO (locationsearch=Switzerland param ignored, all results AR/BR/SG).
sf-rmk — crates/jobcache-adapter-sf-rmk/
Transport: HTTP+JSON POST. SuccessFactors "Job Marketplace" /services/recruiting/v1 hosted on the tenant's career domain. Different jobs surface per locale — iterate the locale list and dedup by job id.
Config shape: { id, name, host, locales: [string], country_filter?: string, brand_slug?: string, search_customer_id?: string }.
Sources (4):
| name | host | locales | country_filter | brand_slug |
|---|---|---|---|---|
| Breitling | careers.breitling.com | ["en_GB"] | Switzerland | Breitling |
| PostFinance | jobs.postfinance.ch | ["de_DE", "fr_FR", "it_IT", "en_US"] | (filter locally) | PostFinance |
| Mobiliar | jobs.mobiliar.ch | ["de_DE", "fr_FR", "it_IT"] | — | (per-job from brandUrl, default "default") |
| BCV — Banque Cantonale Vaudoise | jobs.bcv.ch | ["fr_FR", "en_US"] | — | BCV |
eightfold — crates/jobcache-adapter-eightfold/
Transport: HTTP+JSON. PCSX public search at https://{host}/api/pcsx/search?domain={domain}&location=Switzerland&start=N. Some tenants return "PCSX is not enabled for this user." — those need auth and cannot be scraped via this API.
Config shape: { id, name, host, domain }.
Sources (2):
| name | host | domain |
|---|---|---|
| DSM-Firmenich | jobs.dsm-firmenich.com | dsm.com |
| Bristol Myers Squibb | bms.eightfold.ai | bms.com |
refline — crates/jobcache-adapter-refline/
Transport: HTTP+BS4. https://apply.refline.ch/{client_id}/search.html?form.buttons.listAll=1 → a[href*='/index.html']. Dedup by job_id + title (one job can publish in multiple language variants).
Config shape: { id, name, client_id, default_location }.
Sources (5):
| name | client_id | default_location |
|---|---|---|
| WSL | 273855 | Birmensdorf, Switzerland |
| Empa | 673276 | Dübendorf, Switzerland |
| Eawag | 673277 | Dübendorf, Switzerland |
| PSI | 673278 | Villigen, Switzerland |
| Zürcher Kantonalbank | 792841 | Zürich, Switzerland |
prospective — crates/jobcache-adapter-prospective/
Transport: HTTP+JSON. https://ohws.prospective.ch/public/v1/medium/{medium_id}/jobs?lang=de&offset=0&limit=500. Optional extra_filter for sub-unit filtering (Agroscope under Bundesverwaltung).
Config shape: { id, name, medium_id, extra_filter?, default_location }.
Sources (7):
| name | medium_id | extra_filter | default_location |
|---|---|---|---|
| Visana | 1004518 | — | Bern, Switzerland |
| Raiffeisen | 1950 | — | St. Gallen, Switzerland |
| KPT | 1007102 | — | Bern, Switzerland |
| University of Zurich | 1002007 | — | Zurich, Switzerland |
| University of Bern | 1001892 | — | Bern, Switzerland |
| Agroscope | 1007546 | verwaltungseinheit:1660311 | Switzerland |
| KPMG Switzerland | 1693 | — | Zurich, Switzerland |
persoware — crates/jobcache-adapter-persoware/
Transport: HTTP+BS4. White-label OnApply at https://{host}/{path}. Anchors match [a-z0-9-]+/[0-9a-f-]{20,}$ (UUID suffix). Location from .place-of-work, .city, .job-workplace, footer, or .job-meta2.
Config shape: { id, name, host, path, default_location? }.
Sources (6):
| name | host | path | default_location |
|---|---|---|---|
| CSS | jobs.css.ch | ?lang=en | — |
| Helsana | jobs.helsana.ch | ?lang=de | — |
| Sanitas Krankenversicherung | jobs.sanitas.com | ?lang=en | — |
| Concordia | jobs.concordia.ch | ?lang=de | — |
| AXA Switzerland | jobs.axa.ch | ?lang=en | — |
| University of Basel | jobs.unibas.ch | ?lang=en | Basel, Switzerland |
softgarden — crates/jobcache-adapter-softgarden/
Transport: HTTP+BS4. https://{slug}.softgarden.io/{lang}/ server-side renders all jobs in div.matchElement cards with id job_id_{ID}. Date format DD.MM.YY → ISO.
Config shape: { id, name, slug, lang, default_location }.
Sources (1):
| name | slug | lang | default_location |
|---|---|---|---|
| Vaudoise | vaudoise | de | Lausanne, Switzerland |
umantis — crates/jobcache-adapter-umantis/
Transport: HTTP+BS4. Abacus Umantis at https://{host}/{path}. Anchors a[href*='/Vacancies/'][href*='/Description/']. Location lives in pipe-delimited cell text — segment between second and last |.
Config shape: { id, name, host, path, default_location, swiss_only?: bool }.
Sources (3):
| name | host | path | default_location | swiss_only |
|---|---|---|---|---|
| Swiss TPH | jobs.swisstph.ch | /Jobs/All | Basel, Switzerland | true |
| HUBER+SUHNER | recruiting.hubersuhner.com | /Jobs/All | Herisau, Switzerland | false (global) |
| Generali Switzerland | recruitingapp-2717.umantis.com | /Jobs/1?lang=ger&CompanyID=17 | Adliswil, Switzerland | true |
workable-widget — crates/jobcache-adapter-workable-widget/
Transport: HTTP+JSON (Workable's per-tenant widget endpoint, for accounts not surfaced in the global aggregator at jobs.workable.com).
Config shape: { id, name, slug }.
Sources (10):
| name | slug |
|---|---|
| Crypto Finance | crypto-finance |
| Distalmotion | distalmotion |
| Visium | visium |
| Unit8 | unit8 |
| Debiopharm | debiopharm |
| D ONE | d-one |
| Hilo by Aktiia | hilobyaktiia |
| Verve Ventures | verveventures |
| Flexion Robotics | flexion-robotics |
| SOPHiA GENETICS | sophia-genetics |
| Verity | verity |
homerun — crates/jobcache-adapter-homerun/
Transport: HTTP+Atom. https://feed.homerun.co/{slug} → entry.{title, link[@rel='alternate'], location.name}.
Config shape: { id, name, slug }.
Sources (1):
| name | slug |
|---|---|
| VARO Energy | varo |
xiag-board — crates/jobcache-adapter-xiag-board/
Transport: HTTP+BS4. Regional Swiss boards on the same xiag SaaS platform. a.vacancy-list-card__link-header with sibling spans. Pagination /job/alle-jobs/seite-N.
Config shape: { id, name, host, max_pages }.
Sources (2):
| name | host | max_pages |
|---|---|---|
| Ostjob | www.ostjob.ch | 100 |
| Zentraljob | www.zentraljob.ch | 100 |
yousty — crates/jobcache-adapter-yousty/
Transport: HTTP+BS4. Sitemap index at /sitemap.xml → /sitemap-vacancies.xml. Detail URL shape <host>/job/<slug>/<numeric-id>. Detail page has JobPosting JSON-LD; delegate to shared schema-org-jobposting parser.
Config shape: { id, name, host }.
Sources (3): (replaces today's portals/yousty.ts factory + ostjob-ch.ts / zentraljob-ch.ts / nzz-jobs.ts thin wrappers)
| name | host |
|---|---|
| Ostjob (sitemap variant) | www.ostjob.ch |
| Zentraljob (sitemap variant) | www.zentraljob.ch |
| NZZ Jobs | jobs.nzz.ch |
(Note: ostjob.ch and zentraljob.ch appear in BOTH xiag-board and yousty because they ship two surfaces. The yousty sitemap is more comprehensive; xiag-board is search-driven. Run both, dedup at URL x language ad_id.)
jobup-ch — crates/jobcache-adapter-jobup-ch/
Two surfaces, one crate:
- Sitemap+JSON-LD path — sitemap at
/sitemap.xml, detail pages have JSON-LD; today'sportals/jobup-ch.tscovers this. - Search API path —
https://www.jobup.ch/api/v1/public/search?query={query}&rows=20&page={pg}; gives a different surface.
Config: single-source; no per-tenant config.
jobs-ch — crates/jobcache-adapter-jobs-ch/ (LANDED in TS, to port)
Current TS reference. Two surfaces:
- Public listings path —
https://www.jobs.ch/en/vacancies/?page=0, anchors +__INIT__JSON state. - JSON search API —
https://www.jobs.ch/api/v1/public/search?query={query}&rows=20&page={pg}(query × city fanning for full crawl). - Full-crawl variant — city-by-city pagination across Swiss cities.
Detail pages have JobPosting JSON-LD; delegate to shared schema-org-jobposting parser.
A3 — JS-rendered families (require headless-proxy Env)
sf-spa — crates/jobcache-adapter-sf-spa/
Transport: JS-rendered. Navigate to {host}/c/{company}/home, click a[aria-label="Next Page"], parse tr.jobResultItem. Token order in span.jobContentEM varies per tenant.
Config shape: { id, name, host, company, default_location, jobcontentem_order?: TokenOrder }.
Sources (2):
| name | host | company | default_location |
|---|---|---|---|
| Pictet | career012.successfactors.eu | banquepict | Geneva, Switzerland |
| UNIL | career012.successfactors.eu | universitdP | Lausanne, Switzerland |
Known omissions: Ypsomed (login-gated), Baloise (3.4 KB JS shell, not on SF), Breitling/PostFinance (use sf-rmk).
hibob — crates/jobcache-adapter-hibob/
Transport: JS-rendered. https://{subdomain}.careers.hibob.com. Anchors a[href*='/position/'], a[href*='/job/'].
Config shape: { id, name, subdomain }.
Sources (3):
| name | subdomain |
|---|---|
| Synpulse | synpulse |
| ONWARD Medical | onwardmedical |
| Unique | unique |
phenom — crates/jobcache-adapter-phenom/
Transport: JS-rendered. SPA at {careers_url}. Try __NEXT_DATA__ / <script type="application/json"> first; fall back to facet-click via JS host. Some tenants honor ?country=Switzerland; others need an in-page facet click.
Config shape: { id, name, careers_url, filter_country?: string }.
Sources (12):
| name | careers_url | filter_country |
|---|---|---|
| BCG | https://careers.bcg.com/global/en/search-results | — |
| Cisco | https://careers.cisco.com/global/en/search-results | — |
| Allianz | https://careers.allianz.com/global/en/search-results | — |
| Zimmer Biomet | https://careers.zimmerbiomet.com/us/en/search-results | — |
| Merck Group | https://careers.emdgroup.com/us/en/search-results | — |
| ABB | https://careers.abb/global/en/search-results?country=Switzerland | — |
| Straumann | https://careers.straumann.com/global/en/search-results | Switzerland |
| Givaudan | https://careers.givaudan.com/global/en/search-results?country=Switzerland | — |
| UCB | https://careers.ucb.com/global/en/search-results?qcountry=Switzerland | — |
| MSD | https://jobs.msd.com/gb/en/search-results | Switzerland |
| Thermo Fisher Scientific | https://jobs.thermofisher.com/global/en/search-results | Switzerland |
| Allianz Suisse | https://careers.allianz.com/ch/fr/allianz-suisse | — |
csod — crates/jobcache-adapter-csod/
Transport: JS bootstrap → HTTP+JSON. JS host visits /ux/ats/careersite/{career_site_id}/home, intercepts the Authorization: Bearer header from the first /rec-job-search/external/jobs request, then replays the request directly with {countryCodes: ["CH"]} payload.
Config shape: { id, name, host, career_site_id, region: "us-api" | "eu-fra-api" }.
Sources (2):
| name | host | career_site_id | region |
|---|---|---|---|
| McKinsey & Company | mckinsey.csod.com | 1 | us-api |
| Simon-Kucher | simon-kucher.csod.com | 1 | eu-fra-api |
B1 — High-hostility flagship boards
linkedin — crates/jobcache-adapter-linkedin/
Transport: HTTP+BS4. https://www.linkedin.com/jobs/search?keywords={query}&location=Switzerland&f_TPR=r604800&start={N*25}. Detail via LinkedIn guest API /jobs-guest/jobs/api/jobPosting/{job_id} for description DOM.
Selectors: div.base-card, li.base-card → {h3 (title), h4 (company), span.job-search-card__location, a[href*='/jobs/view/'], time}.
Pagination: start = page × 25.
No login. No cookies. Public listings only.
xing — crates/jobcache-adapter-xing/
Transport: HTTP+BS4 + embedded JSON. https://www.xing.com/jobs/search?keywords={query}&location=switzerland. First strategy: parse window.crate Apollo cache JSON. Fallback: DOM selectors.
indeed — crates/jobcache-adapter-indeed/
Transport: HTTP+BS4. https://ch.indeed.com/jobs?q={query}&l=Switzerland&start={page*10}. Selectors div.job_seen_beacon, div.jobsearch-ResultsList div.result. Note: robots.txt blocks the path; code logs and proceeds.
B2 — Search-driven general boards
| family | crate | notes |
|---|---|---|
| adzuna | jobcache-adapter-adzuna |
https://www.adzuna.co.uk/search?q={query}&w=switzerland |
| reed | jobcache-adapter-reed |
https://www.reed.co.uk/jobs/{slug}-jobs-in-switzerland |
| jobwatch | jobcache-adapter-jobwatch |
https://www.jobwatch.ch/offres/recherche?3112_page={page} |
| stellenanzeiger | jobcache-adapter-stellenanzeiger |
German-language board |
| remoteok | jobcache-adapter-remoteok |
https://remoteok.com/api flat JSON |
| hackernews | jobcache-adapter-hackernews |
Algolia API → "Who is Hiring?" thread comments parsed for Switzerland keyword |
C — Long tail: per-tenant custom HTML, RSS feeds, employer-bespoke
custom-html — crates/jobcache-adapter-custom-html/
Transport: HTTP+BS4 with per-tenant selector config.
Config shape: { id, name, list_url, job_link_selector, title_selector?, location_selector?, link_prefix?, swiss_filter, default_location }.
Sources (7):
| name | list_url | job_link_selector | default_location |
|---|---|---|---|
| ETH Zurich | https://jobs.ethz.ch/site/index | a[href*='/job/view/'] | Zurich, Switzerland |
| Helvetia | https://jobs.helvetia.com/?lang=en&filter_20=1189523 | a[href*='job-vacancies'] | Switzerland |
| PSI | https://www.psi.ch/en/hr/job-opportunities | a[href*='/stellenangebote/'] | Villigen, Switzerland |
| Hydromea | https://www.hydromea.com/jobs | a[href*='/jobs/']:not([href$='/jobs']) | Renens, Switzerland |
| Page Executive | https://www.pageexecutive.com/jobs/switzerland | a[href*='/job-detail/'] | Switzerland |
| Helbling | https://helbling.ch/en/career/jobs | a[href*='/en/career/jobs/']:not([href$='/jobs']) | Zurich, Switzerland |
| H55 | https://h55.applytojob.com/apply/ | a[href*='h55.applytojob.com/apply/']:not([href$='/apply/']) | Sion, Switzerland |
playwright-html — crates/jobcache-adapter-playwright-html/
Transport: JS-rendered + per-tenant selector config.
Sources (4):
| name | url | link_selector | default_location |
|---|---|---|---|
| Sensirion | https://sensirion.com/career | (long selector — see sources.yaml) | Stäfa, Switzerland |
| CordenPharma | https://career.cordenpharma.com/de/p/liestal/jobs | a[href*='/p/liestal/jobs/'] | Liestal, Switzerland |
| Goldman Sachs | https://higher.gs.com/results?LOCATION=Zurich&page=1 | a[href*='/roles/'] | Zurich, Switzerland |
| Viseca | https://jobs.viseca.ch/stellenangebote.html | (long selector) | Zurich, Switzerland |
rss-feed — crates/jobcache-adapter-rss-feed/
Transport: HTTP+RSS.
Sources (4):
| name | rss_url | swiss_filter | default_location |
|---|---|---|---|
| Climeworks | https://climeworks.com/feed/jobs | false | Zurich, Switzerland |
| Deloitte Switzerland | https://apply.deloitte.ch/CHCareers/SearchJobs/feed/?jobRecordsPerPage=200 | false | Switzerland |
| myScience | https://www.myscience.ch/en/jobs/rss | false | Switzerland |
| Leonteq | https://careers.leonteq.com/rss.php | true | Zurich, Switzerland |
employer-bespoke — crates/jobcache-employer-*/
37+ one-off employer integrations from Jobich's scrapers.py. Each is a
single direct Adapter crate; they don't fit a family pattern.
Priority-rank case-by-case (high-volume employers first).
The list: CERN, Glencore, CBRE, Cargill, Axpo, Alpiq, BKW, SBB, United Nations, BCGE, Bank Cler, SYZ Group, Citi, Crédit Agricole, GAVI, Startup.ch, Migros, Securitas, EuroPharmajobs, HotelCareer, HotelJob, ReliefWeb, iPersonal, Sika, Amazon (amazon.jobs), Bitcoin Suisse, EPFL, join.com (deferred — duplicates joincom family), Medienjobs, SozJobs, Startup Ticker, Sygnum, UBS, WHO, WIPO, plus the recruitment-agency cohort: Hays, Randstad, Adecco, Manpower, Experis, Robert Half, Morgan Philips, RM Group, Huxley, Divisions, SwissLinx, JobWinner, JobScout24, TieTalent, WeWorkRemotely, WTTJ, Glassdoor, Alpha.ch, SwissDevJobs, IctCareer, ItJobs.ch, ICTJobs.ch, WeAreDevelopers, DevJobsScanner, Nexus, DarwinRecruitment, CoopersGroup, MichaelPage.
Defer to Tier-C slices unless a specific employer rises to user-priority.
Cross-cutting catalogs
Queries
Used by every search-driven Adapter (LinkedIn, jobs.ch, jobup.ch, Indeed,
Xing, jobwatch, Stellenanzeiger, Adzuna, Reed). Lives in
crates/jobcache-adapter-queries/ or a shared crates/jobcache
helper module.
English (~36): software engineer, developer, data scientist, devops, cybersecurity, nurse, doctor, pharma, medical, accountant, banking, financial analyst, consultant, HR manager, legal, mechanical engineer, electrical engineer, production, architect, construction manager, energy engineer, logistics, driver, warehouse, sales manager, retail, marketing, graphic designer, UX designer, social worker, teacher, government, hotel, chef, tourism.
German (~22): Entwickler, Ingenieur, Krankenschwester, Arzt, Buchhalter, Verkäufer, Mechaniker, Elektriker, Architekt, Koch, Fahrer, Lagerarbeiter, Lehrer, Sachbearbeiter, Polymechaniker, Kaufmann, Informatiker, Monteur, Techniker, Pflege, Sozialarbeiter, Sozialpädagoge.
French + Italian: add as we identify Swiss-Romand and Ticino board needs.
Swiss locations (whitelist + canton codes)
Cantons: AG, AI, AR, BE, BL, BS, FR, GE, GL, GR, JU, LU, NE, NW, OW, SG, SH, SO, SZ, TG, TI, UR, VD, VS, ZG, ZH.
Region terms (also Swiss): Deutschschweiz, Westschweiz, Suisse Romande, Tessin, Zentralschweiz, Ostschweiz, Region Mittelland, Region Limmattal, Seeland, Raum Bodensee, Romandy.
City whitelist: derived from canton-resolving lookup table; ~150 entries for major Swiss cities, municipalities, postal-code prefixes. Build from Jobich's enrich.py lookup table (separate slice — Codex if it touches shared cell writes, me if it stays inside adapters as a normalization helper).
Non-Swiss city blacklist
~200 entries covering DE/AT/FR/UK/US/IN/CN/JP/etc. major cities + country names. Used by search-driven Adapters to filter out international results that leak through "Switzerland" location filters.
(See config.py:NON_SWISS_KEYWORDS for the full list — port to crates/jobcache/src/swiss_filter.rs or its own helper crate. ~200 strings, alphabetic comparison.)
URL And Language Identity
Defined in lib/jobcache/src/ids.ts and its Rust mirror
crates/jobcache/src/url_id.rs. Frozen contract:
ad_id = adIdFromUrlLanguage(url, language).role_id = roleIdFromUrl(url).
The same canonical URL in different languages creates multiple ads under one
URL-family role. Unknown language is the explicit und bucket, not permission
to collapse language identity away. The TypeScript and Rust implementations must
be byte-equivalent and tested against shared golden vectors.
Jobich's db.py:_normalize_url (strip query, strip fragment, https,
trailing slash) is weaker than ours (we also strip tracking params, sort
query keys, lowercase host, decode unreserved percent-escapes). Their behavior
is a strict subset of ours; same URL plus language identity will not necessarily
produce the same ad_id across the two systems. Acceptable: we own ad_id,
they don't.
Defunct sources
Marked in each family section with the migration target. Useful for quarterly re-audit ("did Company X move to a scrapable surface yet?"). Full list lives inline in each family's table notes.
Slice ordering recap
End-state target: ~33 family crates + ~37 employer-bespoke crates = ~70 Rust crates total. Per rust-wasm-adapter-architecture slice order:
- Slice 0 (Codex-led): infrastructure + jobs-ch port. Done when WIT contract +
jobcacheRust crate +wasm-host.tsshim + build pipeline land. - Slice 1: Yousty Rust port for the three current Yousty-family sources.
- Slice 2: jobup-ch Rust port.
- Slice 3: Greenhouse multi-tenant (17 sources unlocked).
- Slice 4: SmartRecruiters + Lever bundle (~24 sources).
- Slice 5: Workday (~27 sources).
- Slice 6: LinkedIn (B1 flagship; runtime prerequisite: method-routed browser-capable Env).
- Slices 7-N: A2/A3 families in volume order (Phenom, SF families, Oracle HCM, Personio, Ashby, Hibob, Workable, Recruitee, RSS, Refline, Prospective, Persoware, Softgarden, Umantis, Teamtailor, joincom, jibe, icims, csod, eightfold, sf-rmk).
- Slices N+1: B1 hostile remainder (Xing, Indeed) + B2 search-driven (Adzuna, Reed, jobwatch, Stellenanzeiger, RemoteOK, HackerNews).
- Slices N+2: C tier — custom-html, playwright-html, rss-feed, employer-bespoke by priority.
Total slice count rough order: ~25 family slices + bursts of employer-bespoke slices late.
Where I will need Codex help during the rollout
- Slice 0 infrastructure (per scoping doc — all eight open decisions).
- Whenever an adapter requires a new
FieldKey,EvidenceRefKind, orMethodvalue (contract change, STOP-and-ask). - Whenever the runtime needs to inject a richer
Env(Router-backed fetch, presigned object-store upload hook for object_refs, model-pack injection for embed-text task class). Most A3+B1+B2 adapters need at least the Router-backed fetch. - Per-source pause/kill flag at binding level (today the env-var pattern handles this; binding-level state belongs to Codex).