# jobs.ch Jobs Scraper (Switzerland: jobup, jobscout24, job-room) (`swissalpinedata/swiss-jobs-scraper`) Actor

Search jobs.ch, jobup.ch, JobScout24.ch and the Swiss federal job-room.ch platform in one query with a unified output schema.

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

## Pricing

$3.00 / 1,000 job listings

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

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

Swiss Jobs Scraper searches **four major Swiss job boards in one run**: [jobs.ch](https://www.jobs.ch), [jobup.ch](https://www.jobup.ch), [JobScout24.ch](https://www.jobscout24.ch) and the Swiss federal job platform [job-room.ch](https://www.job-room.ch). You enter a keyword and an optional location, and you get back one flat dataset with the same fields for every site: title, company, place, canton, workload, employment type, publication date, description and a link to the original ad.

It uses the sites' public search pages and JSON endpoints over plain HTTP, no browser, so a run with 100 results typically finishes in under a minute. Run it from the Apify Console, schedule it, or call it through the Apify API and connect the results to Google Sheets, Slack, Make, Zapier or your own code.

### Why this actor

- Public data only: the public JSON search APIs of jobs.ch and jobup.ch, the official federal job-room.ch API of the SECO, and the public JobScout24 result pages.
- No login, no cookies, no browser and no proxy. Plain HTTP with polite delays, so runs are fast and stable.
- Structured, flat fields with the same schema for all four boards: title, company, place, canton, workload, employment type, date, description and URL.

### Why use Swiss Jobs Scraper?

Anyone who wants a full picture of the Swiss job market has to check several sites, each with its own layout, filters and language. This Actor merges them into one dataset so you can:

- Track new openings for a role, skill or company across all four boards and get alerted through scheduled runs and webhooks.
- Build salary, skill or hiring trend research on top of a clean, deduplicated dataset instead of copying listings by hand.
- Feed job data into a recruiting CRM, a dashboard or a custom job board for a region or industry.
- Compare how the same search looks on the German speaking (jobs.ch, JobScout24) and French speaking (jobup.ch) boards and on the official federal platform.

### How to use Swiss Jobs Scraper

1. Open the Actor page and go to the Input tab.
2. Enter a search query, for example a job title or skill such as "python", "pflegefachfrau" or "comptable".
3. Optionally enter a location such as "Zürich", "Bern" or "Genève". You can also enter a two letter canton code such as "BE" or "ZH".
4. Pick which of the four sources to search, or leave all four selected.
5. Set the maximum number of items and decide whether to fetch full descriptions.
6. Click Start. Results appear in the Output tab as they are collected and can be exported when the run finishes.

### Input

| Field | Type | Description |
| --- | --- | --- |
| `query` | string | Required. Keyword(s) to search for. |
| `location` | string | Optional place, region or canton code to filter results by. Each site applies its own location matching, see the FAQ. |
| `sources` | array | Which job boards to search. Defaults to all four. |
| `fetchDetails` | boolean | When true, fetches every job's detail page or detail endpoint to get the full plain text description, plus the canton (jobs.ch, jobup.ch) and the exact publication date (JobScout24). Slower, one extra request per job. Default false. |
| `maxItems` | integer | Maximum number of records to return in total, spread evenly across the selected sources. If one source has fewer results, the remaining budget goes to the others. Default 100, maximum 5000. |

Example input:

```json
{
  "query": "python",
  "location": "Bern",
  "sources": ["jobs.ch", "jobup.ch", "jobscout24.ch", "job-room.ch"],
  "fetchDetails": false,
  "maxItems": 100
}
```

### Output

Each dataset item is a flat JSON record with the same fields for all four sources. This is a real record from a run with `fetchDetails` enabled, description shortened:

```json
{
  "source": "jobs.ch",
  "job_id": "384bbefd-1193-4545-b36f-72cdbd1ffa94",
  "url": "https://www.jobs.ch/de/stellenangebote/detail/384bbefd-1193-4545-b36f-72cdbd1ffa94/",
  "title": "Senior Fullstack Engineer (C#, Angular, Python) (a)",
  "company": "ERNI Schweiz AG",
  "location": "Zürich",
  "canton": "ZH",
  "workload": "100%",
  "employment_type": "permanent",
  "published_at": "2026-06-25T15:20:58+02:00",
  "description": "Wir sind ERNI. Seit 1994 beraten und unterstützen wir Unternehmen in den Bereichen Innovation und Technologie mit dem Ziel, ihre Leistungsfähigkeit nachhaltig zu steigern und den Weg in eine digitale Zukunft zu ebnen.\n\nWas Dich erwartet:\n\nEntwicklung moderner Fullstack-Anwendungen mit C# im Backend und Angular im Frontend ...",
  "language": null,
  "scraped_at": "2026-09-22T20:33:55.857353+00:00"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

#### Data table

| Field | Description |
| --- | --- |
| `source` | Which job board the record came from: `jobs.ch`, `jobup.ch`, `jobscout24.ch` or `job-room.ch`. |
| `job_id` | The job's identifier on its source site. |
| `url` | Public link to the job posting on the source site. |
| `title` | Job title, plain text. |
| `company` | Employer or agency name, null if the ad is anonymous. |
| `location` | City or place exactly as given by the source, for example "Zürich" or "Bern, Fribourg, Neuchâtel". |
| `canton` | Two letter canton code. Always set for job-room.ch and for jobs.ch and jobup.ch when `fetchDetails` is on. Otherwise derived from the place name or postcode with a built in table of Swiss towns, for example "Zürich" or "8004 Zürich" become ZH. Null when the place is unknown or lists several cantons. |
| `workload` | Workload range such as "100%" or "80-100%". |
| `employment_type` | One or more of `permanent`, `temporary`, `freelance`, `internship`, `side_job`, `apprenticeship` (jobs.ch, jobup.ch), `permanent` or `temporary` (job-room.ch), null for JobScout24. |
| `published_at` | Publication date in ISO 8601. Always set for jobs.ch, jobup.ch and job-room.ch. For JobScout24 the listing only shows a relative age such as "2 W", so this is null unless `fetchDetails` is on. |
| `description` | Plain text. Without `fetchDetails` it is the short preview the site shows in its search results (jobs.ch, jobup.ch, job-room.ch) or null (JobScout24). With `fetchDetails` it is the full ad text. |
| `language` | ISO language code of the ad. Only job-room.ch exposes it, the other sources leave it null. |
| `scraped_at` | When this Actor fetched the record, ISO 8601 UTC. |

Records are deduplicated by URL. jobs.ch and jobup.ch belong to the same company and often list the same ad, so an ad that appears on both is returned once, under whichever source ran first.

### Pricing

This Actor uses pay per result. You pay **USD 0.003 per job record**, which is **USD 3.00 per 1,000 results**. There is no charge per run or per request, and a run that returns no results produces no result charges.

| Results | Price |
| --- | --- |
| 100 | USD 0.30 |
| 1,000 | USD 3.00 |
| 10,000 | USD 30.00 |

A daily alert with 50 new jobs costs USD 0.15 per run, about USD 4.50 per month. Set `maxItems` to cap the cost of a run.

### Legal

This Actor only reads publicly available job listings and the public search endpoints the sites use for their own pages. It does not log in, does not bypass any paywall or bot protection and keeps a polite delay between requests. Job ads can contain personal data such as the name or e-mail address of a recruiter. What you do with the scraped data, including storing or processing such personal data, is your own responsibility. Review the terms of use of each source site before large scale or commercial use.

### FAQ

**How does the location filter work?**
Each site applies its own matching. jobs.ch, jobup.ch and JobScout24 receive the location as a search parameter and return jobs in and around that place, so a search for "Bern" can include nearby towns such as Zollikofen. job-room.ch is filtered by canton code when you enter one (for example "BE"), otherwise by a case insensitive match on the city name.

**Why are some fields null?**
The Actor does not invent values. If a source does not expose a value it stays null: JobScout24 has no employment type or language in its listings, and jobs.ch and jobup.ch do not expose the ad language. The one derived field is `canton`: when a source gives only a place name, the Actor looks the town or postcode up in a table of Swiss towns and leaves the canton null if the place is unknown or ambiguous.

**Why is the description so short?**
Without `fetchDetails` the description is the short preview or search snippet the site itself shows in its result list. Turn on `fetchDetails` to get the full ad text at the cost of one extra request per job.

**How many results can I get?**
`maxItems` is capped at 5000 per run. The sites themselves usually return a few hundred to a few thousand results for a keyword, and the Actor stops when a source has no more pages.

**Does the Actor use a browser or proxies?**
No. It uses plain HTTP requests without proxies. The sites have not blocked this at normal volumes, but if a source starts returning errors the run logs a warning for that source and continues with the others.

**Something looks wrong or a site changed its layout. What do I do?**
Open an issue on the Actor's Issues tab with the input you used and what you expected. Job board layouts and endpoints change occasionally and the Actor will be updated.

### Changelog

- 0.2: README with concrete pricing. `canton` is now also filled for jobs.ch, jobup.ch and JobScout24 records without `fetchDetails`, derived from the place name or postcode through a built in lookup table (for example "Zürich" becomes ZH). Places that are unknown or span several cantons stay null.
- 0.1: Initial release covering jobs.ch, jobup.ch, JobScout24.ch and job-room.ch, with employment type labels, deduplication across jobs.ch and jobup.ch, and optional full descriptions for all four sources.

# Actor input Schema

## `query` (type: `string`):

Keyword(s) to search for, e.g. a job title or skill.

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

Optional place, canton or region to filter results by, e.g. "Zürich". Left empty, results are not filtered by location.

## `sources` (type: `array`):

Which job boards to search. Defaults to all four.

## `fetchDetails` (type: `boolean`):

When enabled, fetches each job's detail page or detail API to get the full plain-text description. Slower and makes one extra request per job.

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

Maximum number of job records to return in total, spread roughly evenly across the selected sources.

## Actor input object example

```json
{
  "query": "python",
  "sources": [
    "jobs.ch",
    "jobup.ch",
    "jobscout24.ch",
    "job-room.ch"
  ],
  "fetchDetails": false,
  "maxItems": 100
}
```

# Actor output Schema

## `results` (type: `string`):

All records returned by this run, as JSON.

# 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 = {
    "query": "python"
};

// Run the Actor and wait for it to finish
const run = await client.actor("swissalpinedata/swiss-jobs-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 = { "query": "python" }

# Run the Actor and wait for it to finish
run = client.actor("swissalpinedata/swiss-jobs-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 '{
  "query": "python"
}' |
apify call swissalpinedata/swiss-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,swissalpinedata/swiss-jobs-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/EbolPc3NjAPIAdf5O/builds/gbg6VeMnwzeeaXJBN/openapi.json
