# Jobs.ch Swiss Job Listings by Region & Industry (`vexaloom-labs/jobs-ch-swiss-job-listings-by-region-and-industry`) Actor

Scrape job listings from jobs.ch, Switzerland's largest job board. Search by keyword or browse by canton, industry, occupational field, or contract type, with filters for workload and seniority. Returns title, company, location, and job URL.

- **URL**: https://apify.com/vexaloom-labs/jobs-ch-swiss-job-listings-by-region-and-industry.md
- **Developed by:** [vexaloom-labs](https://apify.com/vexaloom-labs) (community)
- **Categories:** Jobs, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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?

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 Swiss Job Listings by Region & Industry

Scrape **jobs.ch**, Switzerland's largest job board, through its public search and company APIs. Run classic keyword searches, browse by filters, resolve employers by name or company ID, or paste jobs.ch search/job URLs. Listings can be enriched with the full description, apply URL and method, jobs.ch salary estimate, contacts, language requirements, occupational categories, regions, locations, application deadline, benefits and employer profile/ratings.

No login, cookies, API key or proxy is needed.

### Modes

| Mode | What it does |
|---|---|
| `search` | Classic keyword search across one or more queries. With `searchMode: "semantic"` it uses jobs.ch's fuzzy, cross-language search page instead. |
| `browse` | Filter-only browsing with no keyword. |
| `byCompany` | Resolves company names through jobs.ch's company directory and searches all openings. Company IDs are also accepted and are the most reliable option. |
| `byUrls` | Accepts jobs.ch search URLs (their filters are preserved) and job-detail URLs. Detail URLs report `jobStatus`: `online`, `offline`, `expired` or `unknown`. |
| `byRegion` | Legacy mode: requires at least one value in `regions`; filters only. |
| `byIndustry` | Legacy mode: requires at least one value in `industries`; filters only. |
| `byEmploymentType` | Legacy mode: requires at least one value in `employmentTypes`; filters only. |
| `byCategory` | Legacy mode: requires at least one value in `categories`; filters only. |

### Filters (all multi-value)

- `regions` — 24 Swiss canton/language-region values.
- `industries` — 24 employer-industry values.
- `categories` — all **165** jobs.ch occupational categories (job function, distinct from employer industry).
- `employmentTypes` — temporary, freelance, internship, supplementary income, permanent, apprenticeship.
- `positions` — executive, specialist, employee.
- `languageSkills` — German, English, French, Italian.
- `companySegments` — large company, SME, personnel-service provider.
- `location` — free-text city/place.
- `employmentGradeMin` / `employmentGradeMax` — workload range in percent.
- `publishedSince` — last 1, 3, 7 or 30 days.
- `sortBy` — relevance or newest-first (classic search).

Multiple values in one filter are combined with OR; different filters are combined with AND. Jobs.ch caps every search at **2,000 rows / 100 pages of 20**. Use narrower filters, date windows or several queries to get past the cap.

### Output fields

Every record includes the job identity and listing fields:

- `jobId`, `title`, `searchQuery` (when a keyword was used), `jobStatus` (URL mode only)
- `companyId`, `companyName`, `companySlug`, `companyUrl`, `companyLogoUrl`
- `location`, `locationDetails[]` (street, city, postal-code, canton, country, latitude, longitude)
- `workloadMin`, `workloadMax`, `workloadText`
- `contractType`, `contractTypes[]`, `seniorityLevel`
- `tags[]`, `benefitTags[]`, `easyApply`, `quickApply`, `homeOfficeOffered`
- `postedDate`, `initialPostedDate`, `postedRelative`, `initialPostedRelative`
- `isNew`, `isBoosted`, `isPaid`, `isActive`, `headhunterApplicationAllowed`
- `jobUrl`, `recordType`, `scrapedAt`

With `includeDetails: true`:

- `description` (plain text), `descriptionHtml` (original HTML), `descriptionSnippet`
- `applyUrl`, `applyMethod` (`external` or `form`), `applicationDeadline`
- `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryIsEstimate`
- `contacts[]` (`name`, `firstName`, `lastName`, `role`, `phone`), plus `contactName` / `contactPhone`
- `languageSkills[]` (`language`, `level`), `skills[]`, `benefits[]`
- `category` (full taxonomy path), `categories[]`, `regions[]`, `cantonCode`
- `roleSummary`, `industry`, `workHoursText`, `totalOpenings`, `startDate`, `jobImmediateStart`
- `companyWebsite`, `companyOverview`, `companyDescription`
- `companySize`, `companySegment`, `companyIndustry`, `companyRating`, `companyReviewCount`,
  `companyOpenJobs`, `companySocialLinks[]`, `companyAddress`
- `applicantLocationRequirement`, `streetAddress`, `jobImageUrl`, `directApply`

Empty values are omitted at every depth. `0` and `false` are preserved when they are real values.

### Input reference

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `search` | See modes above. |
| `searchMode` | select | `classic` | `classic` or `semantic`. |
| `searchQueries` | string list | `[]` | One or more keyword queries; deduplicated by job ID. |
| `companyNames` | string list | `[]` | Company names for `byCompany`. |
| `companyIds` | string list | `[]` | jobs.ch company IDs for `byCompany`. |
| `startUrls` | URL list | `[]` | jobs.ch search or job-detail URLs for `byUrls`. |
| `regions` | string list | `[]` | Region/canton IDs. |
| `industries` | string list | `[]` | Industry IDs. |
| `categories` | string list | `[]` | Occupational category IDs (all 165). |
| `employmentTypes` | string list | `[]` | Contract-type IDs. |
| `positions` | string list | `[]` | Seniority IDs. |
| `languageSkills` | string list | `[]` | `de`, `en`, `fr`, `it`. |
| `companySegments` | string list | `[]` | `gu`, `kmu`, `pdl`. |
| `location` | text | `""` | City/place refinement. |
| `employmentGradeMin` | integer | `0` | Minimum workload %. |
| `employmentGradeMax` | integer | `100` | Maximum workload %. |
| `publishedSince` | select | `""` | `1`, `3`, `7`, `30` days. |
| `sortBy` | select | `relevance` | `relevance` or `date`. |
| `includeDetails` | boolean | `false` | Fetch job detail and company profile. |
| `descriptionFormat` | select | `text` | `text`, `html` or `both`. |
| `maxDescriptionLength` | integer | `0` | Truncate descriptions; `0` = no limit. |
| `maxItems` | integer | `20` | 1–2,000. |
| `maxPages` | integer | `100` | 1–100 pages per query/filter combination. |
| `locale` | select | `en` | `en`, `de`, `fr`. |
| `incrementalMode` | boolean | `false` | Push only new/changed jobs. |
| `stateKey` | text | `""` | Key-value store key for incremental state. |

### Examples

#### Classic keyword search

```json
{
  "mode": "search",
  "searchQueries": ["software engineer", "data engineer"],
  "regions": ["19"],
  "employmentGradeMin": 80,
  "maxItems": 100
}
```

#### Semantic, cross-language search

```json
{
  "mode": "search",
  "searchMode": "semantic",
  "searchQueries": ["pflege"],
  "location": "Genève",
  "maxItems": 50
}
```

#### Company openings with full profiles

```json
{
  "mode": "byCompany",
  "companyNames": ["Swiss Medical Network"],
  "includeDetails": true,
  "maxItems": 25
}
```

#### Paste search and detail URLs

```json
{
  "mode": "byUrls",
  "startUrls": [
    {"url": "https://www.jobs.ch/en/vacancies/?term=pflege&region=19"},
    {"url": "https://www.jobs.ch/en/vacancies/detail/e5ce43b5-b97a-46cc-a783-93eff2bf064f/"}
  ],
  "includeDetails": true
}
```

#### Browse by occupational field, newest first

```json
{
  "mode": "browse",
  "categories": ["175", "176"],
  "publishedSince": "7",
  "sortBy": "date",
  "maxItems": 200
}
```

### Use cases

- Recruiting and labour-market monitoring by canton, industry or occupational field.
- Company hiring-tracker dashboards built on stable `jobId` values.
- Job-board aggregation with full descriptions and apply links.
- Salary research using jobs.ch's own annual CHF estimates (always labelled as estimates).
- Incremental monitoring of new or changed postings.

### Data source

The actor reads jobs.ch's public search API, its server-rendered search pages for semantic mode, its company directory/profile API, and the job-detail pages' embedded React Query state plus schema.org `JobPosting` JSON-LD. All requests are anonymous, cookieless and direct.

### Limitations

- **2,000-row search ceiling.** jobs.ch returns at most 100 pages of 20 rows per search. Partition by filters, date windows or queries to collect more.
- **Salary is an estimate.** The salary fields come from jobs.ch's own salary-estimation endpoint, are annual CHF, and are marked `salaryIsEstimate: true`. They are not employer-disclosed salaries.
- **Contacts are partial.** Job-level contacts appear on a minority of postings (about 7 of 48 in testing); phone numbers are rarer and email addresses are not exposed.
- **Apply links are external only.** About three-quarters of sampled jobs had an external apply URL. The rest use jobs.ch's on-site form; for those, `applyMethod` is `form` and `jobUrl` is the apply entry point.
- **Company matching is best-effort.** Exact company names resolve first; otherwise prefix/contains matching is used. Company IDs are the reliable option.
- **Semantic search is fuzzy by design.** It mirrors jobs.ch's own relevance engine, does not support date sorting, and can return off-topic rows for an unmatched query.

### FAQ

**Do I need a proxy, cookie or API key?** No. Every request is anonymous and direct. Existing tasks that still send a proxy object keep running; the value is ignored.

**What is the difference between `industry` and `category`?** `industry` is the employer's business sector; `category` is the job's occupational function. A bank can post an IT-category job, so the two taxonomies are independent.

**Why did I get fewer rows than `maxItems`?** Either jobs.ch had no more matches, the 2,000-row cap was reached, or `maxPages` stopped the crawl. The run's status message says which.

**Why are some fields missing on some jobs?** jobs.ch does not include every field on every posting, and the actor omits empty values rather than inventing placeholders. Detail fields require `includeDetails: true`.

**Are the salary figures employer-disclosed?** No. They are jobs.ch's annual CHF estimate and are always marked `salaryIsEstimate: true`.

**How do I get past 2,000 rows?** Split the search into narrower slices — one region, category, company or publication window at a time — and combine the datasets. Deduplicate on `jobId`.

**Does semantic search support sorting?** No. jobs.ch's semantic results page is relevance-only. Use classic search with `sortBy: "date"` for newest-first ordering.

**What does incremental mode do?** With `incrementalMode: true`, the actor stores a signature per `jobId` under `stateKey` and pushes only new or changed records. Disable it for a full snapshot.

**Can I use job URLs from any jobs.ch language?** Yes. English, German and French detail/search URLs are accepted, and the record URLs follow the URL's language.

**Is this affiliated with jobs.ch?** No. It is an independent third-party actor that reads publicly available jobs.ch data.

# Actor input Schema

## `mode` (type: `string`):

What to fetch. `search` runs classic keyword search (or semantic with `searchMode=semantic`). `browse` uses filters only. `byCompany` resolves company names/IDs. `byUrls` accepts jobs.ch search or job-detail URLs. The `byRegion`/`byIndustry`/`byEmploymentType`/`byCategory` modes are kept for backward compatibility and require their matching filter.

## `searchMode` (type: `string`):

`classic` uses jobs.ch's exact keyword API with all filters and sort. `semantic` uses the site's fuzzy, cross-language search page (filters still apply; sort is relevance-only).

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

One or more free-text keywords (mode=search). Multiple queries are run in order and deduplicated by job ID. Leave empty to browse all jobs matching the filters.

## `companyNames` (type: `array`):

Company names to resolve against jobs.ch's company directory (mode=byCompany). Exact names are preferred; unmatched names fall back to a keyword search filtered by company name.

## `companyIds` (type: `array`):

jobs.ch company IDs (mode=byCompany). IDs are the most reliable company filter and take priority over names.

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

jobs.ch search URLs or job-detail URLs (mode=byUrls). Search URLs carry their own filters; invalid or non-jobs.ch URLs are skipped.

## `regions` (type: `array`):

Filter by one or more Swiss canton/language-regions (multi-value OR). Allowed IDs: 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25. Required for mode=byRegion; optional elsewhere.

## `industries` (type: `array`):

Filter by one or more employer industries (multi-value OR). Allowed IDs: 1-24. Required for mode=byIndustry; optional elsewhere.

## `categories` (type: `array`):

Filter by one or more occupational fields / job functions (multi-value OR). Allowed IDs: 97-261 (165 jobs.ch categories; labels in README). Required for mode=byCategory.

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

Filter by one or more contract types (multi-value OR). Allowed IDs: 1 Temporary, 2 Freelance, 3 Internship, 4 Supplementary income, 5 Permanent position, 6 Apprenticeship. Required for mode=byEmploymentType.

## `positions` (type: `array`):

Filter by one or more seniority levels (multi-value OR). Allowed IDs: 1 Executive position, 2 Specialist, 3 Employee.

## `languageSkills` (type: `array`):

Filter by one or more required working languages (multi-value OR). Allowed values: de, en, fr, it.

## `companySegments` (type: `array`):

Filter by employer segment (multi-value OR). Allowed values: gu (large company), kmu (SME), pdl (personnel-service provider).

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

Optional free-text city/place refinement (e.g. Zürich, Geneva, Basel).

## `employmentGradeMin` (type: `integer`):

Lower bound of the workload range in percent.

## `employmentGradeMax` (type: `integer`):

Upper bound of the workload range in percent.

## `publishedSince` (type: `string`):

Only jobs first published within this time window.

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

Classic search sort order: relevance or newest-first. Semantic search always uses relevance.

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

Fetch each job's detail page for full description, apply URL/method, salary estimate, contacts, languages, categories, regions, deadline, benefits and company profile/ratings. Adds one request per job plus one cached request per company.

## `descriptionFormat` (type: `string`):

Format of the enriched description: plain text, original HTML, or both.

## `maxDescriptionLength` (type: `integer`):

Truncate descriptions to this many characters (0 = no truncation).

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

Maximum number of jobs to return. jobs.ch serves 20 rows per page and caps each search at 2,000 rows.

## `maxPages` (type: `integer`):

Maximum result pages to request per query/filter combination (jobs.ch caps searches at 100 pages).

## `locale` (type: `string`):

jobs.ch site language for URLs and translated labels.

## `incrementalMode` (type: `boolean`):

When enabled, only new or changed jobs are pushed. State is stored under `stateKey` in the key-value store.

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

Key-value store key for incremental state (default `jobs_ch_incremental_state`). Only used when `incrementalMode` is true.

## Actor input object example

```json
{
  "mode": "search",
  "searchMode": "classic",
  "searchQueries": [
    "software engineer"
  ],
  "companyNames": [],
  "companyIds": [],
  "startUrls": [],
  "regions": [],
  "industries": [],
  "categories": [],
  "employmentTypes": [],
  "positions": [],
  "languageSkills": [],
  "companySegments": [],
  "location": "",
  "employmentGradeMin": 0,
  "employmentGradeMax": 100,
  "publishedSince": "",
  "sortBy": "relevance",
  "includeDetails": false,
  "descriptionFormat": "text",
  "maxDescriptionLength": 0,
  "maxItems": 3,
  "maxPages": 1,
  "locale": "en",
  "incrementalMode": false,
  "stateKey": ""
}
```

# Actor output Schema

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

Dataset containing all scraped jobs.ch job listings.

# 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 = {
    "mode": "search",
    "searchMode": "classic",
    "searchQueries": [
        "software engineer"
    ],
    "companyNames": [],
    "companyIds": [],
    "startUrls": [],
    "regions": [],
    "industries": [],
    "categories": [],
    "employmentTypes": [],
    "positions": [],
    "languageSkills": [],
    "companySegments": [],
    "location": "",
    "employmentGradeMin": 0,
    "employmentGradeMax": 100,
    "publishedSince": "",
    "sortBy": "relevance",
    "includeDetails": false,
    "descriptionFormat": "text",
    "maxDescriptionLength": 0,
    "maxItems": 3,
    "maxPages": 1,
    "locale": "en",
    "incrementalMode": false,
    "stateKey": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("vexaloom-labs/jobs-ch-swiss-job-listings-by-region-and-industry").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 = {
    "mode": "search",
    "searchMode": "classic",
    "searchQueries": ["software engineer"],
    "companyNames": [],
    "companyIds": [],
    "startUrls": [],
    "regions": [],
    "industries": [],
    "categories": [],
    "employmentTypes": [],
    "positions": [],
    "languageSkills": [],
    "companySegments": [],
    "location": "",
    "employmentGradeMin": 0,
    "employmentGradeMax": 100,
    "publishedSince": "",
    "sortBy": "relevance",
    "includeDetails": False,
    "descriptionFormat": "text",
    "maxDescriptionLength": 0,
    "maxItems": 3,
    "maxPages": 1,
    "locale": "en",
    "incrementalMode": False,
    "stateKey": "",
}

# Run the Actor and wait for it to finish
run = client.actor("vexaloom-labs/jobs-ch-swiss-job-listings-by-region-and-industry").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 '{
  "mode": "search",
  "searchMode": "classic",
  "searchQueries": [
    "software engineer"
  ],
  "companyNames": [],
  "companyIds": [],
  "startUrls": [],
  "regions": [],
  "industries": [],
  "categories": [],
  "employmentTypes": [],
  "positions": [],
  "languageSkills": [],
  "companySegments": [],
  "location": "",
  "employmentGradeMin": 0,
  "employmentGradeMax": 100,
  "publishedSince": "",
  "sortBy": "relevance",
  "includeDetails": false,
  "descriptionFormat": "text",
  "maxDescriptionLength": 0,
  "maxItems": 3,
  "maxPages": 1,
  "locale": "en",
  "incrementalMode": false,
  "stateKey": ""
}' |
apify call vexaloom-labs/jobs-ch-swiss-job-listings-by-region-and-industry --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vexaloom-labs/jobs-ch-swiss-job-listings-by-region-and-industry"
        }
    }
}
```

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/s0z4uLgc6jZVLtD28/builds/9evTfDGOOXMYXba9u/openapi.json
