# jobs.ch Scraper 🇨🇭 Swiss Jobs (Search + Detail) (`crowley/jobs-ch-scraper`) Actor

Scrape jobs.ch — Switzerland's #1 job board. Search + full job detail: title, company, location, workload % (80-100%), contract type, salary (CHF), full description, posted date. Fast JSON-API scraping on a Swiss residential proxy. Pay-per-result.

- **URL**: https://apify.com/crowley/jobs-ch-scraper.md
- **Developed by:** [Crow Lee](https://apify.com/crowley) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## jobs.ch Scraper 🇨🇭 — Scrape Swiss Jobs (no login, no API key)

Scrape job postings from **jobs.ch**, Switzerland's #1 job board, into clean structured data — title, company, company logo, location, canton, workload percentage (the Swiss `80-100%` convention), contract type, salary in CHF when published, full job description, remote-work flag, and ISO posted date. A practical **jobs.ch API alternative**: no login and no API key required; export to JSON, CSV, Excel (XLSX), XML, or an HTML table, or pull results straight from the Apify API. **Your $5 free Apify credit ≈ 3,300 full-detail jobs** — enough to test seriously before spending a cent.

### 🔍 What does the jobs.ch Scraper do?

You give it a search (a keyword like `software engineer` plus a location like `Zurich`, or any jobs.ch search URL) and it returns every matching job posting as a structured record. Built-in filters narrow the search server-side: **publication date** (last 24h / 3 / 7 / 30 days), **employment types** (permanent, temporary, freelance, internship, supplementary income, apprenticeship), and **workload range** (e.g. only 80-100% roles). It runs in two layers:

- **Search cards** — fast list of jobs from the results pages (title, company, location, workload %, contract type, snippet, posted date).
- **Full detail** (default) — fetches each job's detail record for the complete description, published salary (CHF), and the direct application URL.

Under the hood it talks to jobs.ch's own public JSON API over plain HTTP through a Swiss residential proxy — no browser rendering, so runs are fast and cheap. It paginates page by page and stops cleanly at your item budget or the API's 2,000-results-per-search ceiling.

### 👤 Who is it for?

- **Recruiters & staffing agencies** — track open roles, hiring companies, and workloads across Swiss cities; build outreach lists by role, canton, and pensum.
- **Labour-market & HR analysts** — measure demand by skill, contract type (permanent vs. temporary), and region; benchmark published CHF salaries.
- **Job boards & aggregators** — supplement your own inventory with Swiss coverage from the market leader.
- **Founders & sales teams** — find companies that are actively hiring (a strong buying/growth signal) in a target city or sector.

### ⭐ Why use this scraper?

- **Switzerland-focused** — jobs.ch coverage with Swiss conventions built in: workload as `80-100%`, salaries in CHF, locations across all cantons and language regions (searches work in English, German, French, or Italian).
- **API-grade data quality** — records come from jobs.ch's own JSON API, not brittle HTML parsing: stable IDs, exact timestamps with timezone, structured salary ranges. Since jobs.ch offers no public developer API, this actor is the pragmatic **jobs.ch API alternative** for programmatic access.
- **Server-side filters** — publication date (24h/3d/7d/30d), employment types, and workload (pensum) range are applied in the jobs.ch API query itself, so you only pay for jobs that match.
- **No login, no API key** — just a search or a URL.
- **Full job descriptions** — the complete job-ad text as clean plain text, not just the snippet.
- **Budget-safe** — `maxItems` cap, dedup (you never pay twice for the same job), anti-runaway pagination caps.
- **All export formats** — JSON, CSV, Excel, XML, HTML table, plus API and webhooks.

### 📊 What data can you extract from jobs.ch?

| Data point | Example |
| --- | --- |
| `title` | `Software Engineer` |
| `company` | `Ammann Schweiz AG` |
| `companyUrl` | `https://www.jobs.ch/en/companies/3812-ammann-schweiz-ag/` |
| `companyLogo` | `https://media.jobs.ch/images/44c3746c-…/320x320.png` |
| `location` | `Langenthal` |
| `canton` (detail mode) | `BE` |
| `isRemote` | `true` when the title/description mentions remote / home office / Homeoffice |
| **`workloadPercent`** | `80-100%` |
| `contractType` | `Permanent position` |
| `salaryRaw` | `CHF 5200+ per month` |
| `salaryMin` / `salaryMax` | `5200` / `6500` |
| `salaryCurrency` | `CHF` |
| `salaryPeriod` | `month` |
| `descriptionSnippet` | `Ammann ist ein weltweit führender Lieferant von Mischanlagen…` |
| `description` (detail mode) | Full job-ad text as clean plain text |
| `applicationUrl` (detail mode) | `https://ammann-schweiz-ag.contactrh.com/jobs/4473/…` |
| `postedDate` | `2026-06-20T08:32:21.000Z` |
| `publishedRaw` | `2026-06-20T10:32:21+02:00` |
| `jobKey` | `0e233528-63b7-4b59-a477-0e02f3035ba3` |
| `url` | `https://www.jobs.ch/en/vacancies/detail/0e233528-…/` |

> Note: `salaryRaw`/`salaryMin`/`salaryMax` appear only when the employer publishes pay — most Swiss postings don't (salary transparency is not mandatory in Switzerland). `description` and `applicationUrl` are only populated when **Scrape full detail pages** is on.

### 💰 How much does it cost to scrape jobs.ch?

This actor uses Apify's **pay-per-event** pricing:

| Event | When it fires | Price (USD) | Per 1,000 |
| --- | --- | --- | --- |
| Actor start | Once per run | $0.02 | — |
| Job (search card) | Per unique job, when **Scrape full detail pages = off** | $0.0008 | $0.80 |
| Job (full detail) | Per unique job, when **Scrape full detail pages = on** (full description + salary + application URL) | $0.0015 | $1.50 |

**Your first results are free**: the **$5 free credit** on Apify's free plan covers roughly **3,300 full-detail jobs** (or ~6,200 search cards) — no credit card needed to start. Duplicates are never charged. Real-world cost examples:

| Run | Items | Mode | Approx. cost |
| --- | --- | --- | --- |
| Quick scan | 100 jobs | search cards only | $0.02 + 100 × $0.0008 = **$0.10** |
| Standard | 100 jobs | full detail | $0.02 + 100 × $0.0015 = **$0.17** |
| Deep pull | 500 jobs | full detail | $0.02 + 500 × $0.0015 = **$0.77** |
| Max search | 2,000 jobs | full detail | $0.02 + 2,000 × $0.0015 = **$3.02** |

With the **$5/month free Apify plan** you can scrape roughly **3,300 full-detail jobs** or **6,200 search cards** per month at no cost. (Proxy traffic is included in your Apify plan's usage; the API-based approach keeps it minimal.)

### 🚀 How to scrape jobs.ch

1. Open the actor on Apify and click **Try for free**.
2. Enter a **Search query** (e.g. `software engineer`) and a **Location** (e.g. `Zurich`) — or paste one or more jobs.ch search URLs into **Search URLs**.
3. (Optional) Narrow with **Posted within**, **Employment types**, and **Min/Max workload**; set **Max items** and toggle **Scrape full detail pages**.
4. Leave the proxy on RESIDENTIAL / Switzerland.
5. Click **Start**, then **Export** results as JSON, CSV, Excel, XML, or HTML.

### 🧩 Input examples

Minimal — keyword + city:

```json
{
  "searchQuery": "software engineer",
  "location": "Zurich",
  "maxItems": 100,
  "scrapeDetails": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "CH" }
}
````

Filtered — fresh permanent 80-100% roles only:

```json
{
  "searchQuery": "data engineer",
  "location": "Zurich",
  "publicationDateFilter": "7d",
  "employmentTypes": ["5"],
  "workloadMin": 80,
  "workloadMax": 100,
  "maxItems": 200,
  "scrapeDetails": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "CH" }
}
```

Multiple filtered searches — paste jobs.ch URLs (any language; filters like employment type, region, or publication date are preserved):

```json
{
  "startUrls": [
    { "url": "https://www.jobs.ch/en/vacancies/?term=data%20engineer&location=Zurich&employment-types=5" },
    { "url": "https://www.jobs.ch/de/stellenangebote/?term=pflegefachfrau&region=2" }
  ],
  "maxItems": 300,
  "scrapeDetails": true,
  "maxConcurrency": 5,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "CH" }
}
```

### ⚙️ Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | array | – | jobs.ch search URLs (website or API form, any language). Overrides the query/location fields. All URL filters are preserved. |
| `searchQuery` | string | – | Job keyword (the jobs.ch `term`), e.g. `software engineer`. Empty with no `startUrls` = browse all jobs. |
| `location` | string | – | City, canton, or region, e.g. `Zurich`, `Geneva`. Empty = all of Switzerland. |
| `publicationDateFilter` | string | `any` | Only jobs posted within: `1d`, `3d`, `7d`, `30d`, or `any`. Applied server-side in the API query and re-checked on each record's `postedDate`. |
| `employmentTypes` | array | – | Contract-type filter: `"5"` Permanent, `"1"` Temporary, `"2"` Freelance, `"3"` Internship, `"4"` Supplementary income, `"6"` Apprenticeship. Empty = all. |
| `workloadMin` / `workloadMax` | integer | – | Workload (pensum) range 0-100, e.g. `80`/`100` for 80-100% roles. Maps to the jobs.ch employment-grade filter. |
| `maxItems` | integer | `100` | Stop after this many jobs (0 = unlimited, capped at 2,000 per search by the jobs.ch API). |
| `scrapeDetails` | boolean | `true` | Fetch each job's detail record for full description, salary, and application URL. |
| `maxConcurrency` | integer | `5` | Parallel requests (capped at 10 internally to stay polite). |
| `proxyConfiguration` | object | RESIDENTIAL / CH | **Required.** Apify residential proxy, country Switzerland. |

### 📤 Output example

A real record from `query=nurse` (full-detail mode):

```json
{
  "url": "https://www.jobs.ch/en/vacancies/detail/d9f74cbb-ff2b-4f58-be48-a6c2eb704651/",
  "jobKey": "d9f74cbb-ff2b-4f58-be48-a6c2eb704651",
  "title": "Pflegeexpertin / Pflegeexperte / Nurse Practitioner (w/m/d) Tumorpfad 80-100%",
  "company": "Universitätsspital Zürich",
  "companyUrl": "https://www.jobs.ch/en/companies/11393-universitaetsspital-zuerich/",
  "companyLogo": "https://media.jobs.ch/images/2c1f45b0-9c94-4bbf-a2a1-c19c04c34f9d/320x320.png",
  "location": "Zürich",
  "canton": "ZH",
  "isRemote": false,
  "workloadPercent": "80-100%",
  "contractType": "Permanent position",
  "salaryRaw": null,
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryPeriod": null,
  "descriptionSnippet": "Am Universitätsspital Zürich begleitest du Menschen mit onkologischen Erkrankungen in einem hochspezialisierten medizinischen Umfeld - im ...",
  "description": "Am Universitätsspital Zürich begleitest du Menschen mit onkologischen Erkrankungen in einem hochspezialisierten medizinischen Umfeld - im durchgängigen Patientenpfad, über Fachgrenzen hinweg und mit spürbarer Wirkung für Patientinnen und Patienten.…",
  "applicationUrl": "https://ohws.prospective.ch/public/v1/jobs/01d77c02-b241-4cd1-885f-440f56a08b83/",
  "postedDate": "2026-06-16T13:32:41.000Z",
  "publishedRaw": "2026-06-16T15:32:41+02:00",
  "scrapedAt": "2026-07-10T17:11:58.241Z",
  "source": "detail"
}
```

And a record with a published salary (detail mode):

```json
{
  "url": "https://www.jobs.ch/en/vacancies/detail/bba01a3d-0710-4865-96f5-5fe0090aadcf/",
  "jobKey": "bba01a3d-0710-4865-96f5-5fe0090aadcf",
  "title": "Reinigungsmitarbeiter/in (80–100%) – Büroreinigung",
  "company": "Efe's Reinigung",
  "location": "Urdorf",
  "workloadPercent": "80-100%",
  "contractType": "Permanent position",
  "salaryRaw": "CHF 5200+ per month",
  "salaryMin": 5200,
  "salaryMax": null,
  "salaryCurrency": "CHF",
  "salaryPeriod": "month",
  "postedDate": "2026-07-09T12:46:12.000Z",
  "source": "detail"
}
```

In search-card mode (`scrapeDetails: false`), `description` and `applicationUrl` are `null` and `source` is `"search-card"`.

### 💡 Tips for best results

- **Start small.** Run with `maxItems: 20` first to validate your query, then scale up.
- **Use `startUrls` for precise filters.** Build the exact search you want on jobs.ch (employment type, region, publication date, category…), then paste the URL — every filter is respected.
- **Beat the 2,000-result ceiling.** The API caps one search at 2,000 results. Split broad queries into several `startUrls` (per canton, per employment type) to cover more.
- **Schedule it.** Run daily with `publicationDateFilter: "1d"` to capture only postings from the last 24 hours — perfect for a fresh-jobs feed.
- **Search cards are half the requests.** If you don't need full descriptions, turn `scrapeDetails` off: same jobs at almost half the cost and double the speed.

### ⚠️ Error items

When a request permanently fails after all retries, the actor pushes an error item (no charge) so a run is never silently lossy:

```json
{ "error": "BLOCKED_OR_FAILED", "errorDescription": "Non-JSON response from https://www.jobs.ch/api/…", "url": "https://www.jobs.ch/api/v1/public/search?query=…", "scrapedAt": "2026-07-10T17:12:00.000Z" }
```

| Error code | Meaning |
| --- | --- |
| `BLOCKED_OR_FAILED` | The request failed after 5 retries (network error, rate limit, or an HTML error page instead of JSON). Re-run later or lower concurrency. |
| `RUN_ABORTED` | The whole run hit an unrecoverable error (e.g. proxy pool exhaustion). Results scraped before the abort are kept. |

### 🔌 Integrations

Push results anywhere Apify connects: **Google Sheets**, **Slack**, **Discord**, **Make**, **Zapier**, **BigQuery**, **Snowflake**, **Airbyte**, plain **webhooks**, and the **scheduler**. Configure these from the actor's Integrations tab.

### 🛠️ API usage

Run the actor and fetch the dataset via the Apify API.

**Node.js** (`apify-client`):

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('crowley/jobs-ch-scraper').call({
  searchQuery: 'software engineer',
  location: 'Zurich',
  maxItems: 100,
  scrapeDetails: true,
  proxyConfiguration: { useApifyProxy: true, apifyProxyGroups: ['RESIDENTIAL'], apifyProxyCountry: 'CH' },
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**Python** (`apify-client`):

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("crowley/jobs-ch-scraper").call(run_input={
    "searchQuery": "software engineer",
    "location": "Zurich",
    "maxItems": 100,
    "scrapeDetails": True,
    "proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "CH"},
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

**cURL**:

```bash
curl -X POST "https://api.apify.com/v2/acts/crowley~jobs-ch-scraper/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchQuery":"software engineer","location":"Zurich","maxItems":100,"scrapeDetails":true,"proxyConfiguration":{"useApifyProxy":true,"apifyProxyGroups":["RESIDENTIAL"],"apifyProxyCountry":"CH"}}'
```

### 🤖 Use with AI agents via MCP

This actor is callable from AI agents through the Apify MCP server. Example setup for Claude Desktop / Claude Code / Cursor:

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": ["-y", "@apify/actors-mcp-server", "--actors", "crowley/jobs-ch-scraper"],
      "env": { "APIFY_TOKEN": "YOUR_APIFY_TOKEN" }
    }
  }
}
```

Example prompts: *"Find software engineering jobs in Zurich with at least an 80% workload"* or *"List nursing jobs in Bern posted this week, with company and application link."*

### ❓ FAQ

**Do I need a jobs.ch login or API key?** No. Just a search or URL and an Apify residential proxy.

**Where does the data come from?** From jobs.ch's own public JSON API — the same one the jobs.ch website itself uses. That means stable job IDs, exact timestamps, and structured fields instead of fragile HTML scraping.

**What does `workloadPercent` mean?** Swiss job ads state the *pensum* — the employment percentage. `100%` is full-time, `80-100%` means the employer accepts anything from 4 days a week to full-time. The scraper collapses the API's grade list (e.g. `[80, 85, 90, 95, 100]`) into that familiar range.

**Why are most salaries empty?** Swiss employers rarely publish pay — there is no salary-transparency mandate in Switzerland. When a posting does include it, you get `salaryRaw`, `salaryMin`/`salaryMax`, `salaryCurrency` (CHF), and `salaryPeriod`.

**Can I search in German or French?** Yes. The search index covers all of jobs.ch, and queries work in any of the site's languages (`pflegefachfrau`, `comptable`, `software engineer`…). You can also paste `/de/stellenangebote/` or `/fr/offres-emplois/` URLs as `startUrls`.

**How many results can I get per search?** The jobs.ch API caps a single search at 2,000 results. Use more specific queries/locations or several `startUrls` to cover more.

**Can I get only new jobs?** Yes — set `publicationDateFilter` to `1d` (last 24 hours), `3d`, `7d`, or `30d` and schedule the actor. Website URLs with `publication-date=N` pasted as `startUrls` are translated to the equivalent API filter too.

**Can I find remote jobs?** jobs.ch has no structured remote field, so the actor sets an `isRemote` heuristic flag when the title or description mentions *remote*, *home office*, or *Homeoffice*. Filter your export on `isRemote = true`.

**Can I use this for salary benchmarking?** Yes, with the caveat that only a minority of Swiss postings publish pay. Aggregate `salaryMin`/`salaryMax`/`salaryPeriod` (already normalized to CHF) across a role and region for directional benchmarks.

**Is it legal?** See the Legality section below.

### ⚖️ Legality

This scraper collects **publicly available** job-posting data only. It does not log in, bypass paywalls, or collect personal data of job applicants. You are responsible for using it in compliance with jobs.ch's Terms of Service and `robots.txt`, the EU **GDPR**, and the Swiss **Federal Act on Data Protection (FADP/nDSG)**. Scrape responsibly: keep concurrency modest, do not overload the site, and use the data lawfully (e.g. recruiting and market research, not spam). Job postings may contain a contact name — treat any personal data per the FADP and GDPR (lawful basis, minimisation, retention limits). When in doubt, consult a lawyer. Background reading: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

### 💬 Your feedback

Found a bug or want a new field? Open an issue on the actor's **Issues** tab — we actively maintain it.

### 🔗 Related scrapers

Other Switzerland-focused scrapers from the same author:

- **[anibis.ch Scraper](https://apify.com/crowley/anibis-ch-scraper)** 🇨🇭 — Swiss classifieds marketplace (search + detail).
- **[tutti.ch Scraper](https://apify.com/crowley/tutti-ch-scraper)** 🇨🇭 — Swiss classifieds (search + detail).
- **[homegate.ch Scraper](https://apify.com/crowley/homegate-ch-scraper)** 🇨🇭 — Swiss real estate listings.
- **[immoscout24.ch Scraper](https://apify.com/crowley/immoscout24-ch-scraper)** 🇨🇭 — Swiss real estate (search + detail).
- **[autoscout24.ch Scraper](https://apify.com/crowley/autoscout24-ch-scraper)** 🇨🇭 — Swiss car listings.

### 📝 Changelog

#### 1.1 — 2026-07-10

- New **`publicationDateFilter`** input (`1d`/`3d`/`7d`/`30d`) — server-side publication-date window (mapped to the API's `publication-date-from`/`-to` range), plus a client-side `postedDate` double-check so re-published old ads never slip through.
- New **`employmentTypes`** input — filter by contract type (Permanent, Temporary, Freelance, Internship, Supplementary income, Apprenticeship) via the API's `employment-type-ids[]` filter.
- New **`workloadMin`**/**`workloadMax`** inputs — pensum range 0-100% via the API's employment-grade filter.
- New output fields: **`companyLogo`** (structured logo image URL), **`canton`** (e.g. `ZH`, `BE` — from the detail location block), **`isRemote`** (heuristic keyword flag: remote / home office / Homeoffice).
- Website `startUrls` with `publication-date=N` or `employment-types=N` are now translated to the equivalent API filters (the raw website params are silently ignored by the API).

#### 1.0 — 2026-07-10

- Initial release: jobs.ch search + full job-detail scraping via the public JSON API (no browser — fast, cheap HTTP).
- Swiss workload parsing (`80-100%`), contract-type mapping, CHF salary ranges (min/max/period), ISO posted dates, clean plain-text descriptions, direct application URLs.
- `startUrls` support for any jobs.ch search URL (en/de/fr/it) with all filters preserved.
- Pay-per-event pricing with dedup and error items; `maxItems` budget cap and anti-runaway pagination caps (2,000-result API ceiling, matchless-streak guard).

# Actor input Schema

## `startUrls` (type: `array`):

Raw jobs.ch search URLs to scrape. Paste any jobs.ch search page in any language (e.g. a filtered query with location, employment type or region) — filters are preserved. Leave empty to build a search from the Search query + Location fields below.

## `searchQuery` (type: `string`):

Role / keyword to search for (the jobs.ch 'term'), e.g. software engineer, Pflegefachfrau, comptable. Leave empty (with no startUrls) to browse ALL jobs on jobs.ch. Used only when no startUrls are given.

## `location` (type: `string`):

City, canton or region, e.g. Zurich, Geneva, Basel, Bern. Leave empty to search all of Switzerland. Used only when no startUrls are given.

## `publicationDateFilter` (type: `string`):

Only return jobs published within this window (same filter as the jobs.ch website). The scraper also double-checks each record's postedDate against the window before pushing it.

## `employmentTypes` (type: `array`):

Only return jobs with these contract types (jobs.ch employment-type filter). Leave empty for all types.

## `workloadMin` (type: `integer`):

Minimum employment grade (Swiss pensum), 0-100. E.g. 80 keeps only jobs offering at least an 80% workload. Leave empty / 0 for no lower bound.

## `workloadMax` (type: `integer`):

Maximum employment grade (Swiss pensum), 0-100. E.g. 60 keeps only part-time jobs up to 60%. Leave empty / 100 for no upper bound.

## `maxItems` (type: `integer`):

Stop after collecting this many jobs (0 = unlimited, capped at 2,000 per search by the jobs.ch API). Protects your budget.

## `scrapeDetails` (type: `boolean`):

If true, fetch each job's detail record for full data (full description, salary when published, application URL). If false, only the lighter data from search results — about half the requests.

## `maxConcurrency` (type: `integer`):

Parallel requests. The jobs.ch API is fast — 5 is a good default, capped at 10 internally to stay polite.

## `proxyConfiguration` (type: `object`):

REQUIRED. Use Apify RESIDENTIAL proxy with country CH (Switzerland) for reliable, sustained access to the jobs.ch API.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.jobs.ch/en/vacancies/?term=software%20engineer&location=Zurich"
    }
  ],
  "searchQuery": "software engineer",
  "location": "Zurich",
  "publicationDateFilter": "any",
  "maxItems": 100,
  "scrapeDetails": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CH"
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

Job postings scraped from jobs.ch

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.jobs.ch/en/vacancies/?term=software%20engineer&location=Zurich"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "CH"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("crowley/jobs-ch-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [{ "url": "https://www.jobs.ch/en/vacancies/?term=software%20engineer&location=Zurich" }],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "CH",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("crowley/jobs-ch-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.jobs.ch/en/vacancies/?term=software%20engineer&location=Zurich"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CH"
  }
}' |
apify call crowley/jobs-ch-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=crowley/jobs-ch-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "jobs.ch Scraper 🇨🇭 Swiss Jobs (Search + Detail)",
        "description": "Scrape jobs.ch — Switzerland's #1 job board. Search + full job detail: title, company, location, workload % (80-100%), contract type, salary (CHF), full description, posted date. Fast JSON-API scraping on a Swiss residential proxy. Pay-per-result.",
        "version": "1.1",
        "x-build-id": "0AMIKJO2Foa3VlqHa"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/crowley~jobs-ch-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-crowley-jobs-ch-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/crowley~jobs-ch-scraper/runs": {
            "post": {
                "operationId": "runs-sync-crowley-jobs-ch-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/crowley~jobs-ch-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-crowley-jobs-ch-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "proxyConfiguration"
                ],
                "properties": {
                    "startUrls": {
                        "title": "Search URLs",
                        "type": "array",
                        "description": "Raw jobs.ch search URLs to scrape. Paste any jobs.ch search page in any language (e.g. a filtered query with location, employment type or region) — filters are preserved. Leave empty to build a search from the Search query + Location fields below.",
                        "items": {
                            "type": "object",
                            "required": [
                                "url"
                            ],
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL of a web page",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "searchQuery": {
                        "title": "Search query (job keyword)",
                        "type": "string",
                        "description": "Role / keyword to search for (the jobs.ch 'term'), e.g. software engineer, Pflegefachfrau, comptable. Leave empty (with no startUrls) to browse ALL jobs on jobs.ch. Used only when no startUrls are given."
                    },
                    "location": {
                        "title": "Location",
                        "type": "string",
                        "description": "City, canton or region, e.g. Zurich, Geneva, Basel, Bern. Leave empty to search all of Switzerland. Used only when no startUrls are given."
                    },
                    "publicationDateFilter": {
                        "title": "Posted within",
                        "enum": [
                            "any",
                            "1d",
                            "3d",
                            "7d",
                            "30d"
                        ],
                        "type": "string",
                        "description": "Only return jobs published within this window (same filter as the jobs.ch website). The scraper also double-checks each record's postedDate against the window before pushing it.",
                        "default": "any"
                    },
                    "employmentTypes": {
                        "title": "Employment types",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Only return jobs with these contract types (jobs.ch employment-type filter). Leave empty for all types.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "5",
                                "1",
                                "2",
                                "3",
                                "4",
                                "6"
                            ],
                            "enumTitles": [
                                "Permanent position",
                                "Temporary",
                                "Freelance",
                                "Internship",
                                "Supplementary income",
                                "Apprenticeship"
                            ]
                        }
                    },
                    "workloadMin": {
                        "title": "Min workload (%)",
                        "minimum": 0,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Minimum employment grade (Swiss pensum), 0-100. E.g. 80 keeps only jobs offering at least an 80% workload. Leave empty / 0 for no lower bound."
                    },
                    "workloadMax": {
                        "title": "Max workload (%)",
                        "minimum": 0,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Maximum employment grade (Swiss pensum), 0-100. E.g. 60 keeps only part-time jobs up to 60%. Leave empty / 100 for no upper bound."
                    },
                    "maxItems": {
                        "title": "Max items",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Stop after collecting this many jobs (0 = unlimited, capped at 2,000 per search by the jobs.ch API). Protects your budget.",
                        "default": 100
                    },
                    "scrapeDetails": {
                        "title": "Scrape full detail pages",
                        "type": "boolean",
                        "description": "If true, fetch each job's detail record for full data (full description, salary when published, application URL). If false, only the lighter data from search results — about half the requests.",
                        "default": true
                    },
                    "maxConcurrency": {
                        "title": "Max concurrency",
                        "minimum": 1,
                        "maximum": 10,
                        "type": "integer",
                        "description": "Parallel requests. The jobs.ch API is fast — 5 is a good default, capped at 10 internally to stay polite.",
                        "default": 5
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "REQUIRED. Use Apify RESIDENTIAL proxy with country CH (Switzerland) for reliable, sustained access to the jobs.ch API.",
                        "default": {
                            "useApifyProxy": true,
                            "apifyProxyGroups": [
                                "RESIDENTIAL"
                            ],
                            "apifyProxyCountry": "CH"
                        }
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
