# Job-Room.ch Jobs Scraper (`parsebird/job-room-ch-jobs-scraper`) Actor

Scrape job listings from Job-Room.ch (arbeit.swiss), Switzerland's official public employment service. Search by keyword and canton, or check specific job URLs. Export JSON, CSV, Excel.

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

## Pricing

from $0.79 / 1,000 job scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

### Job-Room.ch Jobs Scraper

Extract **job listings from Job-Room.ch (arbeit.swiss)** — Switzerland's official public employment service portal, run by SECO. **Job-Room.ch Jobs Scraper** returns clean, structured JSON for every listing: title, company, location, canton, workload percentage, full description, contact details, and how to apply.

<table><tr>
<td style="border-left:4px solid #D8232A;padding:12px 16px;font-weight:600">
Search by keyword and Swiss canton, filter by workload percentage and posting recency, or check a specific job URL directly — every listing is enriched from its own detail page for the full description and contact/apply details, with an optional statistics mode for cheap labour-market snapshots and workflow resume state.
</td>
</tr></table>

##### Copy to your AI assistant

Copy this block into ChatGPT, Claude, Cursor, or any LLM to start using this actor.

```
Job-Room.ch Jobs Scraper (parsebird/job-room-ch-jobs-scraper) scrapes job listings from job-room.ch (arbeit.swiss), the Swiss public employment service. Inputs: searchQueries (array of strings — one search per keyword, deduplicated), directUrls (array of job-room.ch job URLs or plain IDs — skips search mode), cantonCodes (array of Swiss canton codes like ["ZH","BE","SG"], default all cantons), sortBy (date_desc / date_asc / "" default), workloadMin/workloadMax (integers, default 10/100 — Pensum percentage), onlineSinceDays (integer, default 60), maxResultsPerQuery (integer, default 100, 0 = all), maxResults (integer, default 0 = unlimited total cap across all queries), statsMode (off default / include / only — see below), proxyConfiguration (Swiss residential, set by default). Output per job: id, title, company, location, canton, job_status (direct-URL mode only), employment_type, workload_min, workload_max, description_snippet, description_full, posted_at, expires_at, source_url, source_platform, contact_salutation/firstname/lastname/email/phone, apply_url, apply_email, company_url, search_query, scraped_at. statsMode adds type:"stats" (per query: totalAvailable from X-Total-Count, pagesFetched, resultsEmitted, stopReason, resume) and type:"run_summary" items; a STATS record is always written to the run's key-value store. Pricing: pay-per-event, $0.00099 per job on Free down to $0.00079 on Gold; stats items are never charged. API: https://apify.com/parsebird/job-room-ch-jobs-scraper/api - Token: https://console.apify.com/account/integrations
```

### What is Job-Room.ch Jobs Scraper?

**Job-Room.ch Jobs Scraper** is an Apify Actor that extracts job listings from [Job-Room.ch](https://www.job-room.ch) (branded **arbeit.swiss**), the official job board of Switzerland's public employment service (SECO / RAV). Give it a keyword — `"Pflege"`, `"Informatik"`, `"Buchhaltung"` — and it returns every matching listing as structured JSON, with title, company, canton, workload, full description, and contact/apply details.

It works as a practical **arbeit.swiss API alternative**, since job-room.ch does not expose a public search API of its own (its documented API is an employer-only publishing channel that requires registered credentials). This Actor calls the same JSON endpoint the job-room.ch website itself uses to render search results.

### What can Job-Room.ch Jobs Scraper do?

- 🔍 **Keyword search** — run multiple search queries in one job, deduplicated automatically.
- 🇨🇭 **Canton filtering** — restrict results to one or more of Switzerland's 26 cantons (full code table below).
- 🎚️ **Workload & recency filters** — minimum/maximum workload percentage (Pensum) and how many days back to look.
- 🔗 **Direct URL / ID mode** — paste specific job URLs or IDs to check whether they're still live, skipping search entirely.
- 📄 **Full listing detail** — every result is enriched from its own detail page for the complete description, contact person, and apply channel — not just the short search-result snippet.
- 🔀 **Sorting** — newest first, oldest first, or the platform's default relevance order.
- 📊 **Statistics mode** — get per-query totals and a run summary instead of (or alongside) job data, for cheap market snapshots and workflow resume state.
- ⚡ Runs on Apify with scheduling, API access, integrations, and export to JSON, CSV, Excel, HTML, or XML.

### What data can you extract from Job-Room.ch?

| Field | Description |
|-------|-------------|
| `id` / `source_url` | Job-Room.ch listing ID and canonical URL |
| `title` / `company` | Job title and hiring company |
| `location` / `canton` | City and canton code |
| `employment_type` | `full-time` or `part-time`, derived from workload |
| `workload_min` / `workload_max` | Workload percentage range (Pensum) |
| `description_snippet` / `description_full` | Short preview and complete job description |
| `posted_at` / `expires_at` | Publication start and end dates |
| `job_status` | Listing status (`PUBLISHED_PUBLIC`, etc.) — direct URL mode only |
| `contact_salutation` / `contact_firstname` / `contact_lastname` / `contact_email` / `contact_phone` | Named contact, when published |
| `apply_url` / `apply_email` | How to apply |
| `company_url` | Employer's website, when published |

Not every field is always available: **salary is not exposed** by the arbeit.swiss data at all (always `null`), and detailed **requirements** aren't reliably separable from the description (always an empty array).

### How to scrape Job-Room.ch

1. Open [Job-Room.ch Jobs Scraper](https://apify.com/parsebird/job-room-ch-jobs-scraper) on Apify Store and click **Try for free**.
2. Enter one or more **Search queries**.
3. (Optional) Restrict to specific **Canton codes**, set a workload range, or a posting-recency window.
4. (Optional) Use **Direct job URLs** instead, to check specific listings.
5. Click **Start**, then open the **Dataset** tab. Export as JSON, CSV, Excel, HTML, or XML.

#### Input example — multi-query search

```json
{
    "searchQueries": ["Pflege", "IT", "Buchhaltung"],
    "cantonCodes": ["ZH", "BE"],
    "maxResultsPerQuery": 50,
    "sortBy": "date_desc"
}
```

#### Input example — single search

```json
{
    "searchQueries": ["Software Engineer"],
    "cantonCodes": ["ZH"],
    "maxResultsPerQuery": 200
}
```

#### Input example — direct URLs (still-alive checks)

```json
{
    "directUrls": [
        "https://www.job-room.ch/job-search/abc123-def456",
        "https://www.job-room.ch/job-search/xyz789-uvw012"
    ]
}
```

### Output example

```json
{
    "id": "abc123-def456",
    "title": "Dipl. Pflegefachperson HF/FH",
    "company": "Universitätsspital Zürich",
    "location": "Zürich, ZH",
    "canton": "ZH",
    "job_status": null,
    "employment_type": "full-time",
    "workload_min": 80,
    "workload_max": 100,
    "description_snippet": "Wir suchen eine engagierte Pflegefachperson...",
    "description_full": "Wir suchen eine engagierte Pflegefachperson für unsere Abteilung...",
    "requirements": [],
    "posted_at": "2026-08-15T00:00:00.000Z",
    "expires_at": "2026-09-14T00:00:00.000Z",
    "source_url": "https://www.job-room.ch/job-search/abc123-def456",
    "source_platform": "arbeit.swiss",
    "contact_salutation": "Frau",
    "contact_firstname": "Maria",
    "contact_lastname": "Müller",
    "contact_email": "jobs@usz.ch",
    "contact_phone": "+41442551111",
    "apply_url": "https://careers.usz.ch/apply/123",
    "apply_email": "bewerbung@usz.ch",
    "company_url": "https://www.usz.ch",
    "search_query": "Pflege",
    "scraped_at": "2026-09-05T12:00:00.000Z"
}
```

Download the dataset in JSON, CSV, Excel, HTML, or XML from the [Dataset tab](https://docs.apify.com/platform/storage/dataset).

### Statistics mode

Set `statsMode` to get per-query statistics alongside or instead of job data — useful for workflow control (resume state, "was it worth a full scrape?") and cheap labour-market snapshots.

| Mode | Dataset output | Cost |
|------|-----------------|------|
| `off` (default) | Jobs only, no stats items — unchanged behaviour | Standard per-result pricing |
| `include` | Jobs **and** one `type: "stats"` item per query **and** one `type: "run_summary"` item | Standard per-result pricing — stats items are free |
| `only` | Stats **and** `run_summary` only — no job data, no detail-page fetches | One page fetch per query, no per-result charges |

Each `stats` item carries the platform-reported total (from arbeit.swiss's `X-Total-Count` header — the same number the job-room.ch UI shows), how many pages were fetched, how many results were emitted, why the query stopped (`stopReason`), and — if it stopped early — the page/offset to resume from. A `STATS` record (`{queries: [...], summary: {...}}`) is **always** written to the run's key-value store, regardless of `statsMode`, so a calling workflow can read resume state without parsing the dataset.

### Swiss canton codes

| Code | Canton | Code | Canton |
|------|--------|------|--------|
| AG | Aargau | NW | Nidwalden |
| AI | Appenzell Innerrhoden | OW | Obwalden |
| AR | Appenzell Ausserrhoden | SG | St. Gallen |
| BE | Bern | SH | Schaffhausen |
| BL | Basel-Landschaft | SO | Solothurn |
| BS | Basel-Stadt | SZ | Schwyz |
| FR | Fribourg | TG | Thurgau |
| GE | Genève | TI | Ticino |
| GL | Glarus | UR | Uri |
| GR | Graubünden | VD | Vaud |
| JU | Jura | VS | Valais |
| LU | Luzern | ZG | Zug |
| NE | Neuchâtel | ZH | Zürich |

Pass these in `cantonCodes`, e.g. `["ZH", "BE", "SG"]`. Leave empty to search all cantons.

### Use cases

- 🧲 **Recruitment sourcing** — pull Swiss job openings by keyword and canton for a talent pipeline.
- 📊 **Labour-market research** — use `statsMode: "only"` to track how many openings exist for a keyword or region over time, without paying for job data.
- 🏢 **Employer monitoring** — track a company's open roles on the official Swiss public job board.
- 🔁 **Still-alive checks** — feed listing URLs from your own database into `directUrls` on a schedule to detect closed or removed postings.
- 🤖 **AI & data pipelines** — feed structured job data into LLM agents through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp).

### Turn one scrape into a recurring job monitor

Save an input and run it on an [Apify Schedule](https://docs.apify.com/platform/schedules):

- **Daily/weekly keyword tracking**: `sortBy: "date_desc"`, a modest `maxResultsPerQuery`, and `onlineSinceDays` matching your schedule interval so each run only reflects genuinely new postings.
- **Labour-market snapshots**: `statsMode: "only"` on a batch of keywords — one cheap page fetch per keyword, no per-result charges.
- **Still-alive checks**: batch known listing IDs into `directUrls` to detect closures.

If you also need fresh **listing detail refreshes at scale for your own tracked jobs**, run this Actor's `directUrls` mode on a schedule against your saved job IDs.

### How it works

1. The Actor builds a search request per query, with your canton, workload, and recency filters.
2. It calls job-room.ch's own search endpoint (the same one the website uses) and paginates through results up to `maxResultsPerQuery` / `maxResults`.
3. Each result from search carries a short, highlighted excerpt; the Actor fetches that listing's own detail page to get the complete description and contact/apply information.
4. In `directUrls` mode, each URL/ID is resolved straight to its detail page — no search step.
5. Each job is billed as one pay-per-event result; `stats`/`run_summary` items are never charged.

### How much does it cost to scrape Job-Room.ch?

Job-Room.ch Jobs Scraper uses **pay-per-event** pricing. You are charged **once per job listing**.

| Plan | Price per job | Price per 1,000 jobs |
|------|-----------------|--------------------------|
| Free | $0.00099 | **$0.99** |
| Bronze | $0.00089 | **$0.89** |
| Silver | $0.00089 | **$0.89** |
| Gold | $0.00079 | **$0.79** |

Scraping 1,000 jobs costs about **$0.99** on the Free plan. `statsMode: "only"` runs cost nothing per result — only Apify platform usage (a handful of small requests). Apify platform usage (compute and proxy) is billed separately. See the [Apify pricing page](https://apify.com/pricing).

### API usage examples

Run Job-Room.ch Jobs Scraper with the [Apify API](https://docs.apify.com/api/v2).

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run_input = {
    "searchQueries": ["Pflege"],
    "cantonCodes": ["ZH", "BE"],
    "maxResultsPerQuery": 50,
    "sortBy": "date_desc",
}

run = client.actor("parsebird/job-room-ch-jobs-scraper").call(run_input=run_input)

for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], "-", job["company"], "-", job["canton"])
```

#### JavaScript

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });

const run = await client.actor('parsebird/job-room-ch-jobs-scraper').call({
    searchQueries: ['Software Engineer'],
    cantonCodes: ['ZH'],
    maxResultsPerQuery: 100,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((j) => `${j.title} @ ${j.company}`));
```

Connect finished datasets to [Google Sheets](https://docs.apify.com/platform/integrations/google-sheets), [Slack](https://docs.apify.com/platform/integrations/slack), [Zapier](https://docs.apify.com/platform/integrations/zapier), [Make](https://docs.apify.com/platform/integrations/make), webhooks, or your own pipeline.

### Is it legal to scrape Job-Room.ch?

Job-Room.ch Jobs Scraper reads only publicly visible data — the same job listings any visitor sees on [job-room.ch](https://www.job-room.ch), a public Swiss government employment service. It does not log in or access private account data. You should still review job-room.ch's Terms of Use and make sure your use case complies with applicable laws. For background, read Apify's guide: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/).

### Related Actors

- [Indeed Jobs Scraper](https://apify.com/parsebird/indeed-jobs-scraper) — Jobs from Indeed in 60+ countries.
- [Jobs.ie Scraper](https://apify.com/parsebird/jobs-ie-scraper) — Irish job listings from Jobs.ie.
- [Karriere.at Jobs Scraper](https://apify.com/parsebird/karriere-at-jobs-scraper) — Austrian job listings.
- [Website Contact Finder](https://apify.com/parsebird/website-contact-finder) — Emails, phones, and socials from any list of websites.

### FAQ

**Do I need a job-room.ch or arbeit.swiss account?**
No. The Actor uses job-room.ch's own public search data — no login or API key.

**Why does the input mention residential proxies?**
job-room.ch's search backend is a Swiss government service that can restrict traffic from cloud/datacenter IP ranges. A Swiss residential proxy is set by default and recommended for reliable, sustained use — see the proxy configuration in **Advanced**.

**Why is salary always `null`?**
arbeit.swiss's job data doesn't include salary information at all — it isn't hidden by the Actor, it simply isn't published on the platform.

**What's in `requirements`?**
It's always an empty array. Requirements text isn't published as a separate structured field by arbeit.swiss — it's part of the free-text `description_full`.

**How do I get only new postings on a repeated run?**
Set `onlineSinceDays` to match how often you run the Actor (e.g. `7` for a weekly schedule) and `sortBy: "date_desc"`, or use `statsMode` to check the total count cheaply before deciding whether to run a full scrape.

**Can I check whether specific jobs are still active?**
Yes — put their URLs or IDs in `directUrls`. Removed or invalid listings are skipped with a warning in the log rather than causing an error.

**Can I schedule recurring runs?**
Yes. Use Apify [schedules](https://docs.apify.com/platform/schedules) — see "Turn one scrape into a recurring job monitor" above.

**Can I use this as an arbeit.swiss API?**
Yes. Call the Actor through the [Apify API](https://docs.apify.com/api/v2), use webhooks, or connect it to AI assistants with the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp).

**Something looks wrong or missing.**
Report it on the [Issues tab](https://apify.com/parsebird/job-room-ch-jobs-scraper/issues) and it will be looked at quickly.

# Actor input Schema

## `searchQueries` (type: `array`):

One or more search keywords. Each runs as a separate search; results are deduplicated.

## `directUrls` (type: `array`):

Direct Job-Room.ch job URLs (or plain IDs) to scrape. Skips search mode entirely — ideal for "is this job still live" checks.

## `cantonCodes` (type: `array`):

Swiss canton codes to filter by, e.g. \["ZH", "BE", "SG"]. Leave empty for all cantons. See the README for the full code table.

## `sortBy` (type: `string`):

Result order.

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

Minimum workload percentage (Pensum).

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

Maximum workload percentage (Pensum).

## `onlineSinceDays` (type: `integer`):

Only include jobs posted within this many days.

## `maxResultsPerQuery` (type: `integer`):

Maximum number of jobs to return per search keyword. 0 = all.

## `maxResults` (type: `integer`):

Total cap across all queries combined. 0 = unlimited.

## `statsMode` (type: `string`):

Per-query statistics output — useful for workflow control (resume state, "was it worth a full scrape?") and cheap labour-market snapshots. 'off': jobs only. 'include': jobs plus free stats/run\_summary items in the dataset. 'only': stats and a run\_summary only — one page fetch per query, no job data, no per-result charges.

## `keywords` (type: `array`):

Legacy alias for Search queries. Use Search queries instead — both are merged and deduplicated.

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

Proxy for the job-room.ch requests. job-room.ch blocks cloud/datacenter IPs — Swiss residential proxies are required and set by default.

## Actor input object example

```json
{
  "searchQueries": [
    "Informatik"
  ],
  "directUrls": [],
  "cantonCodes": [],
  "sortBy": "",
  "workloadMin": 10,
  "workloadMax": 100,
  "onlineSinceDays": 60,
  "maxResultsPerQuery": 20,
  "maxResults": 0,
  "statsMode": "off",
  "keywords": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CH"
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# 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 = {
    "searchQueries": [
        "Informatik"
    ],
    "maxResultsPerQuery": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "CH"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("parsebird/job-room-ch-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 = {
    "searchQueries": ["Informatik"],
    "maxResultsPerQuery": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "CH",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("parsebird/job-room-ch-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 '{
  "searchQueries": [
    "Informatik"
  ],
  "maxResultsPerQuery": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "CH"
  }
}' |
apify call parsebird/job-room-ch-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,parsebird/job-room-ch-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/v366zBBGPFTvHpcL7/builds/a0HI6Be57B2rFaXjY/openapi.json
