🇨🇭 Jobs.ch $0.99/1K: Swiss Job Board, 46K Swiss Job Listings
Pricing
from $0.99 / 1,000 jobs
🇨🇭 Jobs.ch $0.99/1K: Swiss Job Board, 46K Swiss Job Listings
$0.99/1K | 46,576 Swiss jobs in one run — the public jobs.ch API stops at 2,000. Full advert text, apply URL, workload %, coordinates. Incremental mode pays only for what appeared since yesterday. A Switzerland job board of Switzerland job listings data. Sion jobs, Biel jobs, Lucerne jobs.
Pricing
from $0.99 / 1,000 jobs
Rating
0.0
(0)
Developer
Snow Leo Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
🇨🇭 Jobs.ch Scraper — every Swiss job, past the 2,000 cap
$0.99 per 1,000 jobs. Swiss job data with the full advert text, the direct apply URL, the Swiss workload percentage and coordinates — from jobs.ch, the largest Swiss job board.
| 46,121 jobs live on jobs.ch | measured today from the site's own counter |
| The public API stops at 2,000 per query | page 101 returns HTTP 422, and 20 rows per page is the hard maximum |
| This actor returns more than that in one run | measured: 2,400 unique jobs in 64 s, zero duplicates |
| 38 fields | the search endpoint alone gives 20; the rest come from the detail record |
| $0.99 / 1,000 | the other jobs.ch actor on this store charges $2.00 |
The 2,000 wall, and how this gets past it
Ask jobs.ch for page 101 and it answers HTTP 422. Ask for 50 rows instead of 20 — HTTP 422 again. So one query can never hand you more than 2,000 jobs, no matter what limit you set. With 46,121 jobs on the site, a scraper that simply pages through a search leaves 44,121 of them unreachable. This one reaches the Switzerland vacancies the search endpoint will not hand over.
This actor splits a wide search automatically: first by category, then by region, then by industry, employment type and finally by workload band, until every slice fits under the wall. Slices overlap, so every job is deduplicated by its id — the run report shows how the query was split.
walk: 2400 unique jobs in 64 s, 12 queries
Who this is for
Recruiters and staffing firms watching the Swiss market. Turn on Only jobs not seen before, run it daily, and you are charged only for what appeared since yesterday.
Job boards and aggregators. Every row carries the advert text and the direct apply URL, so a listing can be republished or linked without a second fetch.
Market research. The Swiss workload percentage is a first-class field, not a
string to parse: a 60–80% advert comes out as workload_min: 60,
workload_max: 80, workload: "60–80%".
Quick start
Press Start. With an empty input you get a 500-job sample of the newest listings — a trial, not an invoice.
For a real run, put a term into Search term and a town into Location. The location list is generated from the live API, with the number of open jobs next to each town.
{"query": "software engineer","location": "Zürich","workloadMin": 80,"postedWithinDays": 7,"maxItems": 0}
The Swiss words for what you are looking for
Half of the confusion around Swiss job data is vocabulary, so here is the map. What an English speaker calls a job advert is a Stelleninserat or a Stellenanzeige on jobs.ch, and a list of them is Stellenangebote — this Actor returns Stellenangebote data and jobs.ch data as rows. If you are searching for Stellen Schweiz, Jobs Schweiz or Swiss vacancies, this Actor is the thing that returns them — the whole set, not the first two thousand.
Pensum is the Swiss word for the workload percentage, broken out into numbers further down: a Pensum of 80% is four days a week. Arbeitsort is the place of work, which arrives split into place, street and zipcode rather than as one line of text. In French Switzerland the same advert is an offre d'emploi, and emploi Suisse searches land on the same listings, which is why every row carries both a German and a French link to the posting.
The search term goes to jobs.ch untranslated, so type it in the language the advert was written in: pflegefachfrau pulls the healthcare jobs data that the English word nurse would miss entirely, ingenieur pulls engineering jobs data, and elektriker finds electricians. Most adverts here are written in German, so German jobs data — Swiss listings in German, not listings from Germany — is the bulk of any wide run.
Where the Swiss jobs actually are
Switzerland is small, and the Swiss job market is not spread evenly across it: jobs in Switzerland cluster in a handful of towns. These are the ones with the most open adverts right now, counted from the site's own place facet on the day this was written:
| Town | Open adverts | Town | Open adverts |
|---|---|---|---|
| Zürich | 4,478 | Frauenfeld | 540 |
| Bern | 1,860 | Chur | 433 |
| Basel | 1,131 | Aarau | 429 |
| Winterthur | 1,105 | Oftringen | 396 |
| Luzern | 1,082 | Thun | 369 |
| Sursee | 696 | Solothurn | 272 |
| Baden | 666 | Genève | 263 |
| St. Gallen | 586 | Schaffhausen | 263 |
| Lausanne | 556 | Zug | 555 |
The same list, with the counts refreshed from the live API, is the drop-down behind the Location field, so you never have to guess whether a town is worth a run. Basel jobs data and Bern jobs data need no second run: the town is a filter on one search, not a separate Actor.
Swiss job listings as data
Swiss job listings come back as rows rather than as a page: title, employer,
workload percentage, apply URL and coordinates. Full time jobs data separates
from part-time on the workload field — part time jobs data is the same column
read from the other end, workload_max under 100 — and job coordinates data comes from the
site, not from geocoding. Anyone measuring the Switzerland
job market gets Swiss employment data with the advert body attached, which is
where the language requirement is actually stated.
Employer contact data is on the row wherever the employer published one:
contact_name and contact_address rather than a form to fill in. And
employment_types carries jobs.ch's own type ids, so permanent jobs data
separates from temporary work on a field instead of on a keyword — the ids are
passed through raw, because jobs.ch publishes no dictionary for them.
Job ads Switzerland-wide come from the same run: jobs in Zurich, Geneva, Basel
and every smaller town, with no per-canton setup.
Filters run before you are charged
Keywords, excluded keywords, town, workload range, posting age, employer allow-list and block-list. A row removed by a filter never reaches the dataset and never appears on your bill. The run report counts what each filter dropped.
Dataset views can only choose columns — no view on Apify can drop rows — so the filtering lives in the input, where it is also cheaper.
Incremental runs
onlyNew: true remembers delivered jobs in a named key-value store that
survives between runs, so a schedule pays only for new listings. Memory is
written after a batch is delivered, never before: a job that did not reach
you is never marked as delivered.
Honest limits
- jobs.ch publishes no dictionary for its facet ids. Category, region and industry ids are passed through raw rather than given invented labels.
- No salary field. Swiss adverts rarely state pay, and jobs.ch does not expose it, so this actor does not pretend to have it.
- Slices of a split query overlap; deduplication is by job id, so the same job is delivered — and charged — once.
How the split actually works
Why a limit setting cannot help you
The cap is not a setting on your side. maxItems controls how many rows you
want; it cannot make jobs.ch hand over page 101. Any scraper that issues one
query and pages through it stops at 2,000 rows no matter what the input says.
What the Actor does instead
It asks jobs.ch how many jobs match your search. If the answer is under 2,000, it simply pages through. If it is over, the search is cut into slices that each fit under the wall, and every slice is walked separately:
- By category — 165 buckets, the biggest holding about 10,000 jobs.
- By region — 24 buckets, the largest canton holding around 38,000.
- By industry — 25 buckets.
- By employment type — 6 buckets.
- By workload band — 10–39%, 40–59%, 60–79%, 80–89%, 90–100%.
Each step is applied only when the previous one leaves a slice still too large, so a narrow search never pays for splitting it does not need.
What happens to jobs that appear in two slices
They are delivered once. Every job carries an id, and the walk keeps the ids it has already handed over. The run report tells you how many queries and pages it took and which dimensions were used, so the cost is never a mystery.
Fields
Every row has 38 fields. The ones that matter most:
| Field | What it is |
|---|---|
title, profession, synonym | the advert headline, the employer's own job name, and the job family jobs.ch files it under |
company, company_logo | employer and logo URL |
place, street, zipcode, latitude, longitude | where the work is, down to coordinates |
workload, workload_min, workload_max | Swiss workload percentage, parsed into numbers |
description_text, description_html | the full advert, as text and as HTML |
apply_url, external_url | straight to the application, and to the employer's own page |
url, url_de, url_fr | the listing in English, German and French |
published_at, posted_days_ago, expires_at | when it appeared and when the advert comes down |
contact_name, contact_address | named contact, when the employer published one |
Empty means the employer did not publish it. Nothing here is inferred.
Fields in detail
Workload, the Swiss particularity
Swiss adverts state a percentage instead of full-time or part-time. An 80–100%
role means the employer will take someone at four days a week or five. This
Actor gives you workload_min: 80, workload_max: 100 and workload: "80–100%"
— numbers you can filter on, plus the label for display.
Filtering respects the range rather than a single number: asking for at least 80% keeps a 60–80% advert out, and asking for at most 60% keeps a 40–60% advert in. A scraper that compares one number gets both of those wrong.
Why the umlaut changes the answer
jobs.ch matches the location string you give it, and it does not fold accents for you. Searching the location Zürich returns 11,198 adverts; the ASCII spelling Zurich returns 11,190 — close, but not the same set. In French Switzerland the gap is wider: Genève returns 881 and Geneva returns 687, because roughly two hundred adverts spell the city only in French. Basel returns 2,683 and Lausanne 925 with either spelling, since neither carries an accent.
Two consequences worth knowing before you schedule anything. First, a location
search is wider than the town itself: the place facet counts 4,478 adverts whose
address is literally Zürich, while a location search for Zürich returns 11,198,
because it reaches the commuter belt around the city. Second, if you want every
Geneva advert, run the French spelling — or leave the location empty, keep the
search term, and filter on place afterwards.
Addresses and coordinates
place, street and zipcode come straight from the employer's entry, and
latitude/longitude are the coordinates jobs.ch itself publishes. Plotting a
map or running a radius search of your own needs no geocoding step.
Three languages, one job
Switzerland posts in German, French and English. Every row carries url,
url_de and url_fr for the same listing, so a board can link readers to the
version they read.
What is deliberately missing
There is no salary field. Swiss adverts rarely state pay and jobs.ch does not expose it, so this Actor offers no parsed pay figures at all and does not invent them. Facet ids for category, region and industry are passed through raw: the site publishes no public dictionary for them, and inventing labels would mean inventing data.
Running it on a schedule
Paying only for what is new
Turn on Only jobs not seen before and set a schedule. The Actor keeps the ids it has delivered in a named key-value store that survives between runs, so tomorrow's run returns only what appeared since today — and bills only for those rows.
The memory holds up to 150,000 ids and drops the oldest first when it fills. Ids are written after a batch reaches your dataset, never before, so a run that dies mid-way never marks undelivered jobs as delivered.
Putting the output somewhere useful
The run page hands you the dataset as JSON, CSV, XML or Excel, which is the whole workflow for a one-off list. Anything repeatable goes through the API:
https://api.apify.com/v2/datasets/<datasetId>/items?format=csv&clean=true
You do not have to track dataset ids for a nightly job. This address always answers with the last finished run of the Actor:
https://api.apify.com/v2/acts/<actorId>/runs/last/dataset/items?format=json
No-code pipelines, and the webhook underneath them
Apify maintains a connector for the tools most buyers already run — Make, n8n, Zapier and Slack — and a webhook for everything else: when a run finishes, Apify calls your URL with the run id and the dataset id in the payload.
In Make and n8n the scenario is the same two blocks every time: run the Actor, then read its dataset. Name the storage if the pipeline depends on it — unnamed storages on Apify are temporary by design, named ones stay, and the delta memory described above is a named store for exactly that reason.
Three things people build with this
A morning feed of new Swiss adverts. Delta switch on, daily schedule, output into Slack or a sheet. Each row already carries the apply URL, so a recruiter acts without opening jobs.ch.
A local market report. Leave the search term empty, pick a town, and count adverts by profession and by workload band. The Pensum arrives as two numbers, so part-time share is a calculation and not a guess.
A Swiss job board. The advert body comes as text and as HTML, the address is split into place, street and postcode with coordinates attached, and every listing carries both its German and its French URL.
Questions people ask
How many jobs can one run return?
As many as your limit allows. The 2,000 ceiling belongs to a single query, not to a run: the Actor issues as many queries as the search needs. A measured run returned 2,400 unique jobs in 64 seconds, and nothing in the design stops it before the search is exhausted.
Does it fetch the full advert text?
Yes, when Fetch the full advert text is on. That costs one extra request per job and brings the advert body, the direct apply URL, contact details, street and postcode. Turn it off and the run is much faster and cheaper — the row keeps its shape, but the fields that only the detail record carries come back empty.
Why are some fields empty?
Because the employer did not fill them in. Empty means absent at the source, not
lost in transit. Numeric fields come back as null rather than an empty string,
so a typed consumer — Excel, BigQuery, pandas — accepts the column without a
cast.
Can I filter to only jobs that state a workload?
Set Workload from to 10. Any advert that states a percentage at all passes; adverts with no workload stated do not. The filter runs before billing, so the rows it removes cost you nothing.
How do I get only new jobs each morning?
Turn on Only jobs not seen before, then schedule the Actor daily from the Apify console. The first run fills the memory and bills for everything it delivers; every run after that bills only for new listings.
What does it cost to try?
Running with an empty input returns a 500-job sample of the newest listings — about fifty cents. There is no way to accidentally pull the whole board on a first click: without a search term and without a limit the Actor treats the run as a trial and stops.
Can I limit a run to one canton?
Not by canton name — jobs.ch exposes regions as numeric ids, not as labels, and inventing the mapping would mean inventing data. Two honest routes exist. Put the main town into Location, which reaches the commuter belt around it, or pass the raw region id into Region ids if you already know it from the site's own URL. The run report prints which region ids a split used, so one wide run tells you the ids for every narrow run after it.
Does it cover French-speaking Switzerland?
Yes, and the whole of Ticino too — the endpoint is the same for all three
language regions. The catch is spelling, not coverage: an advert published in
Geneva is filed under Genève, and a search for the English spelling misses about
two hundred of them. Every row carries url_de and url_fr for the same
listing, so a bilingual board can link readers to the version they read. There is no Italian link field, so Italian-language URLs are not offered.
Can I find part-time work only?
Yes, and this is where the Swiss workload percentage earns its place. Set Workload up to to 60 and Workload from to 10: you get adverts that state a Pensum at or below 60%, and adverts with no percentage at all stay out. Asking the other way round — Workload from 80 — gives you full-time and near full-time roles. The range is compared as a range, so a 60–80% advert is kept by a request for at most 80% and dropped by a request for at least 90%.
Is jobs.ch blocking scrapers?
This Actor uses the same public JSON endpoint the site's own search page calls, with no proxy and no browser. It keeps one shared per-host pace and backs off when the server asks it to. If the source ever starts refusing, the run report names the failure instead of returning a silently short result.
A run succeeded but the dataset is empty — is that a failure?
Not necessarily, and the Actor always says which case it is. The run report
carries empty_reason and the log carries a warning naming it:
nothing-changed (the incremental mode working as asked — everything had
already been delivered by an earlier run, so nothing was written and nothing
was charged), filtered-out (your filters removed everything that was
collected), source-returned-nothing (the search matched nothing at all), or
charge-limit-reached (the run's spending limit was already used up). A silent
empty dataset is treated as a defect, not as a normal outcome.
maxItems counts what you are given, not what was looked at: the Actor
keeps walking the source until your filters have passed that many rows. And
withDescriptionOnly needs the advert body, so it turns descriptions on for
that run by itself rather than quietly returning nothing.
Jobs outside Switzerland
jobs.ch is a national board: it carries Swiss vacancies and nothing else. Three
sibling Actors cover the rest of the same job data, and each stamps its rows with
a source field so their datasets can be concatenated and still be told apart:
- ATS Jobs Scraper — the employer's own careers page across 20 applicant tracking systems, any country, including the Swiss companies that run Greenhouse or Workday.
- SEEK, JobStreet & JobsDB Scraper — Australia, New Zealand and six Asian markets.
- The Muse Jobs Scraper — one curated board across employers, remote roles included.
Reproducing the numbers
python3 tests/run_all.py --live
test_live.py asserts the 2,000 wall against the live API and then proves the
walk gets past it. Every number in this README comes from that run.