🇨🇭 Jobs.ch $0.99/1K: Swiss Job Board, 46K Swiss Job Listings avatar

🇨🇭 Jobs.ch $0.99/1K: Swiss Job Board, 46K Swiss Job Listings

Pricing

from $0.99 / 1,000 jobs

Go to Apify Store
🇨🇭 Jobs.ch $0.99/1K: Swiss Job Board, 46K Swiss Job Listings

🇨🇭 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

Snow Leo Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

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.chmeasured today from the site's own counter
The public API stops at 2,000 per querypage 101 returns HTTP 422, and 20 rows per page is the hard maximum
This actor returns more than that in one runmeasured: 2,400 unique jobs in 64 s, zero duplicates
38 fieldsthe search endpoint alone gives 20; the rest come from the detail record
$0.99 / 1,000the 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:

TownOpen advertsTownOpen adverts
Zürich4,478Frauenfeld540
Bern1,860Chur433
Basel1,131Aarau429
Winterthur1,105Oftringen396
Luzern1,082Thun369
Sursee696Solothurn272
Baden666Genève263
St. Gallen586Schaffhausen263
Lausanne556Zug555

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:

  1. By category — 165 buckets, the biggest holding about 10,000 jobs.
  2. By region — 24 buckets, the largest canton holding around 38,000.
  3. By industry — 25 buckets.
  4. By employment type — 6 buckets.
  5. 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:

FieldWhat it is
title, profession, synonymthe advert headline, the employer's own job name, and the job family jobs.ch files it under
company, company_logoemployer and logo URL
place, street, zipcode, latitude, longitudewhere the work is, down to coordinates
workload, workload_min, workload_maxSwiss workload percentage, parsed into numbers
description_text, description_htmlthe full advert, as text and as HTML
apply_url, external_urlstraight to the application, and to the employer's own page
url, url_de, url_frthe listing in English, German and French
published_at, posted_days_ago, expires_atwhen it appeared and when the advert comes down
contact_name, contact_addressnamed 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:

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.