# jobs.ch & jobup.ch Scraper - Swiss Jobs, Salary & Pensum (`aidnurs/jobs-ch-scraper`) Actor

- **URL**: https://apify.com/aidnurs/jobs-ch-scraper.md
- **Developed by:** [Nursultan Imanov](https://apify.com/aidnurs) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 jobs

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### jobs.ch scraper and jobup.ch scraper in one API

**jobs.ch & jobup.ch Scraper** extracts job listings from [jobs.ch](https://www.jobs.ch) and [jobup.ch](https://www.jobup.ch), the two largest job boards in Switzerland (both run by JobCloud). jobs.ch covers German-speaking Switzerland, jobup.ch the Romandie. Search one board or both at once and get clean, structured data: **title, company, Pensum (workload %) as numbers, salary in CHF (min / max / period), employment type, canton, address, apply link, company rating and the full job description**.

Use it as a **jobs.ch API** or **jobup.ch API** for job market research, salary benchmarking, recruiting and staffing agencies, lead generation for HR services, or your own Swiss job alerts.

### Why use this jobs.ch scraper?

- 🇨🇭 **Two boards, one run, no double billing.** Many jobs are published on both jobs.ch and jobup.ch with the same ID. With *Job board = Both* every job is saved once, and the copy on the other board is skipped for free.
- 📊 **Pensum as numbers.** `80–100%` becomes `pensumMin: 80`, `pensumMax: 100`, plus `isFullTime`. Filter by workload right in the input (e.g. part-time 40–60%).
- 💰 **Salary in CHF as numbers.** When the employer states a salary (`CHF 110'000 - 117'000`, `Fr. 28.– pro Stunde`, `6'500 CHF par mois`) you get `salaryMin`, `salaryMax`, `salaryPeriod` (year / month / hour). Otherwise you get the salary estimate the board shows for the job, marked `salarySource: "estimate"`.
- 🔔 **Only new jobs since the last run.** Turn on *Only new jobs* and schedule the Actor: each run returns only postings you have not received yet. Already-seen jobs are free.
- 🏢 **Company data.** Website, size, rating and number of reviews from the company profile, plus industry and job category.
- 🏷️ **Sponsored jobs flagged** with `isSponsored`, easy-apply jobs with `easyApply`.
- 🛡️ **GDPR / FADP-friendly by default.** Phone numbers and personal e-mails in job texts are masked, contact persons are not saved (role mailboxes like `jobs@` stay). Turn on *Include personal data* only if you have a legal basis.
- ⚡ **Fast and light.** Plain HTTP, no browser, no proxy needed. 200 jobs with full descriptions take about 40 seconds.

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

**$1.50 per 1,000 jobs**, full description and salary included. You pay only for unique jobs saved to the dataset: duplicates between the two boards, jobs already delivered in *Only new jobs* mode and empty searches cost nothing. Cap spending with *Max jobs* and the run's maximum cost.

### How to scrape jobs.ch and jobup.ch

1. Enter **Keywords**, e.g. `Data Engineer`, `Pflegefachfrau`, `comptable`.
2. Enter a **Location** (`Zürich`, `Genève`, `Bern`, `8001`) or leave it empty for all of Switzerland.
3. Choose the **Job board**: both (default), jobs.ch only or jobup.ch only.
4. Optionally set **Pensum** from/to, *Posted within* and the page language.
5. Set **Max jobs** and click **Start**. Download the results as JSON, CSV, Excel or HTML, or read them via the Apify API.

To browse a whole category without a keyword, set the filters on jobs.ch or jobup.ch, copy the page address and paste it into **Start URLs**, e.g. `https://www.jobs.ch/de/stellenangebote/informatik-telekommunikation/?location=Zürich`.

### Input example

```json
{
  "keywords": "python",
  "location": "Zürich",
  "site": "both",
  "pensumMin": 80,
  "postedWithinDays": "7",
  "maxItems": 100,
  "includeDetails": true,
  "onlyNewJobs": false
}
```

### Output example

```json
{
  "id": "76e9fab0-6b95-4242-9773-24ab97f11990",
  "site": "jobs.ch",
  "url": "https://www.jobs.ch/de/stellenangebote/detail/76e9fab0-6b95-4242-9773-24ab97f11990/",
  "title": "Wirtschaftsinformatiker/-in Professional (1253)",
  "company": "Bundesamt für Informatik und Telekommunikation BIT",
  "companyUrl": "https://www.jobs.ch/de/firmen/70645-bundesamt-fur-informatik-und-telekommunikation-bit/",
  "place": "Zollikofen",
  "pensumMin": 80,
  "pensumMax": 100,
  "pensum": "80–100%",
  "isFullTime": true,
  "publishedAt": "2026-10-03T04:02:21+02:00",
  "isSponsored": true,
  "easyApply": false,
  "salaryMin": 72138,
  "salaryMax": 112138,
  "salaryCurrency": "CHF",
  "salaryPeriod": "year",
  "salarySource": "estimate",
  "descriptionText": "Bern, Schweiz (und Homeoffice) | 80-100%\nDiesen Beitrag kannst du leisten\n\n• Service Asset and Configuration Manager/-in für BIT-DP ...",
  "employmentType": "Festanstellung",
  "workHours": "33.6 - 42 hours/week",
  "position": "Fachverantwortung",
  "industry": "Öffentl. Verwaltung / Verbände",
  "category": "Technisch / Informatik / Telekommunikation / Beratung / Wirtschaftsinformatik",
  "street": "Eichenweg 3",
  "postalCode": "3052",
  "city": "Zollikofen",
  "canton": "BE",
  "latitude": 46.9976,
  "longitude": 7.46294,
  "validThrough": "2026-11-03",
  "applyUrl": "https://ohws.prospective.ch/public/v1/jobs/3a440760-1987-4ebd-b639-7a72efcc0476",
  "applicationMethod": "external",
  "companyWebsite": "https://www.admin.ch",
  "companyEmployees": "> 1 000",
  "companyRating": 2.91,
  "companyReviews": 33,
  "source": "jobs.ch",
  "scrapedAt": "2026-10-12T21:07:42.115251+00:00"
}
```

### Fields you get

| Field | Description |
|---|---|
| `id`, `url`, `site`, `source` | Job ID (the same on both boards), job page and the board it was taken from |
| `title`, `company`, `companyUrl`, `companyLogo` | Job title and employer |
| `place`, `street`, `postalCode`, `city`, `canton`, `latitude`, `longitude` | Workplace (address and coordinates when the employer gives them) |
| `pensumMin`, `pensumMax`, `pensum`, `isFullTime`, `workHours` | Workload in percent and hours per week |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText`, `salarySource` | Salary stated in the job text, or the board's estimate (`salarySource: "estimate"`) |
| `salaryEstimateMin`, `salaryEstimateMax` | The board's yearly salary estimate in CHF, when shown |
| `employmentType`, `position`, `industry`, `category`, `languageSkills` | Contract type, seniority, industry, job category, required languages |
| `publishedAt`, `firstPublishedAt`, `validThrough`, `isNew` | Dates |
| `applyUrl`, `applicationMethod`, `easyApply`, `noCoverLetter` | How to apply |
| `companyWebsite`, `companyEmployees`, `companyRating`, `companyReviews` | Company profile |
| `descriptionText`, `descriptionHtml` | Full job description (contacts masked by default) |
| `isSponsored`, `searchUrl`, `scrapedAt` | Promoted job flag, the search page it came from, time of scraping |

With *Include full job description* turned off you get the fields from the search page only (title, company, place, Pensum, dates, flags), which is faster.

### FAQ

**Is it legal to scrape jobs.ch and jobup.ch?**
The Actor reads only public job pages, the same ones anyone sees without logging in, and follows the sites' robots.txt. Personal data of contact persons is masked or left out by default. You are responsible for how you use the data, especially if you turn on *Include personal data* (GDPR, Swiss FADP).

**Do I need a proxy?**
No. The Actor works without proxy. If the site starts blocking requests during a run, it switches to Apify Proxy on its own.

**How do I get only new jobs every day?**
Turn on *Only new jobs since last run*, give the search a *Monitoring key* (e.g. `python-zurich`) and create a schedule. Each run returns only jobs not delivered before under that key.

**Why do I get jobs that don't contain my exact keyword?**
jobs.ch and jobup.ch use semantic search: they also return related jobs (synonyms, similar roles). The Actor returns what the site shows for your search.

**Why is Keywords required?**
The boards show search results only for a search term or a category page. To browse all jobs of a category or region, paste the page address into *Start URLs*.

**Something broke or a field is missing?**
Open an issue on the Actor page. We usually answer within 24–48 hours.

### Known gaps

- Salary: when the employer states no salary, the board's estimate is used (`salarySource: "estimate"`); some jobs have neither.
- Street and coordinates are published only by some employers.

# Actor input Schema

## `keywords` (type: `string`):

What to search for: job title, skill or company ("Data Engineer", "Pflege", "comptable"). jobs.ch and jobup.ch show results only for a search term, so to browse a whole category use Start URLs instead.

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

City, canton or postcode in Switzerland, e.g. "Zürich", "Genève", "Bern", "8001". Leave empty for all of Switzerland.

## `site` (type: `string`):

jobs.ch (German-speaking Switzerland), jobup.ch (French-speaking Switzerland) or both. With both, a job posted on both boards is saved and charged once.

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

Maximum number of jobs to save. You only pay for unique jobs saved to the dataset.

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

Paste search or category pages from jobs.ch or jobup.ch with filters already set on the site, e.g. https://www.jobs.ch/de/stellenangebote/informatik-telekommunikation/?location=Z%C3%BCrich. When set, Keywords, Location and the filters below are ignored.

## `pensumMin` (type: `integer`):

Minimum workload in percent, e.g. 60 for part-time jobs of 60% and more. Leave empty for any.

## `pensumMax` (type: `integer`):

Maximum workload in percent, e.g. 80 to find part-time jobs only. Leave empty for any.

## `postedWithinDays` (type: `string`):

Only jobs published in the last N days.

## `language` (type: `string`):

Language of the job board pages and links (de, fr, en). jobup.ch has no German version and uses French instead. The job text stays in the language the employer wrote it in.

## `includeDetails` (type: `boolean`):

Open every job page to add the full description, salary (stated by the employer or the jobs.ch estimate), employment type, address, canton, apply link, industry and company rating.

## `includePersonalData` (type: `boolean`):

Off by default: phone numbers and personal e-mails inside job texts are masked (\[phone removed], \[email removed]; role mailboxes like jobs@ or rh@ stay), and contact persons are not saved.<br><br>Turn it on to get everything the site shows publicly, including contact persons where the employer published them.<br><br>⚠️ This returns personal data. You're responsible for having a legal basis to collect and use it under privacy laws (e.g. GDPR, Swiss FADP).

## `onlyNewJobs` (type: `boolean`):

Remember job IDs between runs and return only jobs you have not received yet. Ideal for scheduled runs and job alerts. Already-seen jobs are never charged.

## `stateKey` (type: `string`):

Name of the memory used by 'Only new jobs'. Use a different key for each saved search.

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

jobs.ch and jobup.ch work without proxy. If the site starts blocking, the actor switches to Apify Proxy on its own.

## Actor input object example

```json
{
  "keywords": "python",
  "location": "Zürich",
  "site": "both",
  "maxItems": 20,
  "postedWithinDays": "",
  "language": "",
  "includeDetails": true,
  "includePersonalData": false,
  "onlyNewJobs": false,
  "stateKey": "default",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Scraped jobs (table view).

## `jobsFull` (type: `string`):

All fields including description, salary and company data.

# 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 = {
    "keywords": "python",
    "location": "Zürich",
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("aidnurs/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 = {
    "keywords": "python",
    "location": "Zürich",
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

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

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

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

```

## CLI example

```bash
echo '{
  "keywords": "python",
  "location": "Zürich",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call aidnurs/jobs-ch-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aidnurs/jobs-ch-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/8fKnesCMfxxfkR6vo/builds/rxtqhJKastPS59ksI/openapi.json
