# Jobs.ch & Jobup.ch Scraper (Switzerland) (`hexagone-data/jobs-ch-jobup-scraper`) Actor

Scrape jobs.ch and jobup.ch: the same list as the site page, both sites in one run, each job charged once. Full job pages included. Detects and refuses the different list the site silently serves to some addresses. Pay per job.

- **URL**: https://apify.com/hexagone-data/jobs-ch-jobup-scraper.md
- **Developed by:** [Hexagone Data](https://apify.com/hexagone-data) (community)
- **Categories:** Jobs, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.33 / 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.
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 & Jobup.ch Scraper (Switzerland)

Scrape job offers from **jobs.ch** and **jobup.ch**, the two big Swiss job boards of JobCloud, in one run. Paste the search URL your browser
shows, filters included, or a job URL, or just type search terms, and get one record per job with its full job page: title, company, places,
workload, contract, dates, ad text, translation, skills, languages, contacts published on the ad, apply link, employer salary and company data.

What makes it different:

- **The same list as the site's page.** The site silently serves a different list to some addresses it classifies as suspect, without any message. Usually shorter, not always: measured on
  2026-11-03, `notar` on jobs.ch shows **213** jobs on the page and **41** to an address classified suspect; `notar` on jobup.ch 41 and 22; `vendeur` on jobup.ch 1,213 and 895; but `pflegefachperson` in Basel 173 and 180.
  Datacenter addresses are often in that case (measured: 3 sessions of 8, then 9 of 14). This Actor reads the classification on every list page, refuses such a page, reads it again from
  another address, and says so in the run summary (`suspectPagesRetried`, `otherListSeen`). Such a page is never delivered, never counted, never charged.
- **Two sites in one run, each job charged once.** Real run on `notar`: 208 on jobs.ch + 41 on jobup.ch − 14 in common = **235 jobs delivered** (`crossSiteDuplicates` = 14). Promoted rows that
  the site repeats on other pages are delivered once too (measured: 1,554 rows for 1,494 jobs on `engineer`).
- **Measured against other Actors of this Store** (2026-11-03: one or two searches each, lists only, job pages not compared, so a small sample). `notar` in Basel on jobs.ch, 15 jobs on the site's page: one Actor delivered 15, two others delivered 0 in a run that ended as succeeded. `notaire` in Sion on jobup.ch, 14 on the page: another Actor delivered 1. The same two searches with this Actor, later that day: 10 of 10 and 13 of 13, compared job by job with the site's list read just after (the lists had moved).
- **Full job pages included in the price**, read by default.
- **Every list is checked against the site's total**: distinct jobs seen vs jobs announced, both numbers in the summary. Anything missing beyond a tolerance is reported as
  `partial_collection`, never as `done` (no tolerance on a list of one page; 2 jobs or 0.5% on a longer one, always shown under **Checks**).

### Try it with 20 jobs

1. Keep the prefilled start URL (`https://www.jobs.ch/en/vacancies/?term=engineer`) and **Maximum number of jobs = 20**.
2. Click **Start**. You pay for 20 jobs: $0.043 (full job pages included, start fee included).
3. Open **Jobs overview** for the table, **Run summary** for what was collected and why anything is missing.

### What to paste

| You paste | Example | What you get |
|---|---|---|
| A search on jobs.ch, in English, German or French | `https://www.jobs.ch/en/vacancies/?term=engineer`, `https://www.jobs.ch/de/stellenangebote/?term=pflege`, `https://www.jobs.ch/fr/offres-emplois/?term=vendeur` | the search, checked against the site's total |
| A search on jobup.ch, in English or French | `https://www.jobup.ch/en/jobs/?term=manager`, `https://www.jobup.ch/fr/emplois/?term=notaire&location=Sion` | same |
| A search with filters | `https://www.jobs.ch/en/vacancies/?term=developer&location=Lausanne&employment-grade-min=60&employment-grade-max=80`, `...&publication-date=7`, `...&category=102&category=103` | filters kept exactly as in the URL, repeated values included |
| A category page of jobs.ch | `https://www.jobs.ch/en/vacancies/admin-hr-consulting-ceo/legal-attorneys-court/` | the category, checked against the site's total |
| One company on jobup.ch | `https://www.jobup.ch/en/jobs/?company=1177` | its jobs |
| A job | `https://www.jobs.ch/en/vacancies/detail/<id>/` | that job with its details |

Filters seen working: `term`, `location`, `category`, `region`, `employment-type`, `position`, `language-skill`, `industry`, `publication-date`, `employment-grade-min`, `employment-grade-max`,
`company`, `benefit`. `page=`, and `jobid=` when you clicked a job on the page, are ignored: the run reads the whole search.

On jobs.ch a filter alone (`?location=Basel`) is read with an empty `term=`, as the site needs it. A remote list of URLs (`requestsFromUrl`) is not read: it is rejected with the reason, paste the URLs themselves.

Rejected with the reason, no job charged: home pages, company pages (paste a search, or `?company=<id>` on jobup.ch), `/de/` on jobup.ch (it has no German pages), other sites.
A search page without any term or filter (`https://www.jobs.ch/en/vacancies/`) and jobup.ch category paths show no list on the site itself: status `no_listing`, not an empty search.

**No URL?** Fill **Search terms**: each one is searched on the chosen **Sites** (both by default), with **Location**, **Language** (`de` becomes `fr` on jobup.ch, and the run says so), **Published in the last
N days** (1, 3, 7, 30), **Minimum/Maximum workload %** and **Contract type**. URLs and search terms can be combined. A job URL only works on the site that publishes it: the same id on the other site is a 404.

### Output

One record per job and run (`id` is the key: a job found by two of your URLs or on both sites is delivered once). Unknown values are `null`, never `""`. Real record from an Apify run of this Actor (search `controller` on jobs.ch, 2026-11-04), **abridged**: only the ad texts are shortened. Every field is listed in the
dataset schema:

```json
{
  "id": "4b94317f-b4aa-4940-af41-6615cd97256f",
  "site": "jobs.ch",
  "url": "https://www.jobs.ch/en/vacancies/detail/4b94317f-b4aa-4940-af41-6615cd97256f/",
  "language": "en",
  "title": "Controller (m/w/d)",
  "companyName": "Glutz AG",
  "companyId": "ac026824-252f-4ee1-a901-05cadfe519ab",
  "companyLogo": null,
  "place": "Solothurn",
  "locations": [
    {
      "street": null,
      "postalCode": "4502",
      "city": "Solothurn",
      "canton": "SO",
      "country": "CH",
      "latitude": 47.21054,
      "longitude": 7.53692
    }
  ],
  "canton": "SO",
  "workloadMin": 80,
  "workloadMax": 100,
  "contractTypes": [
    "permanent"
  ],
  "positionLevels": [
    "specialist"
  ],
  "publishedAt": "2026-10-26T08:28:53+01:00",
  "firstPublishedAt": "2026-10-26T07:28:53+00:00",
  "isNew": false,
  "isPromoted": false,
  "easyApply": false,
  "benefits": [],
  "sourceUrl": "https://www.jobs.ch/en/vacancies/?term=controller",
  "scrapedAt": "2026-11-04T01:23:20Z",
  "detailsStatus": "ok",
  "descriptionHtml": "<p></p><p><p> </p> <p>Deine Analysen schaffen Entscheidungsgrundlagen!</p> <p>Als wertvolle Ergänzung in unser...",
  "descriptionText": "Deine Analysen schaffen Entscheidungsgrundlagen!\nAls wertvolle Ergänzung in unserem Controllingteam übernimmst...",
  "originalLanguage": "de",
  "translationLanguage": "en",
  "translatedTitle": "Controller (m/f/d)",
  "translatedDescriptionText": "Your analyses create the basis for decisions!\nAs a valuable addition to our controlling team, you take on a st...",
  "expiresAt": "2026-12-11",
  "street": null,
  "postalCode": "4502",
  "languageSkills": [],
  "skills": [],
  "category": "Commercial / Finance / Trusts / Real Estate / Controlling",
  "contacts": [],
  "applicationMethod": "external",
  "applyUrl": "https://jobs.glutz.com/cvdropper/90673bfe6d6e4f2eaa29510162aac414/DE?src=w1zlliu0e3cinwsif5x43lsgn56uamnyvpwv1xaus8fdzozoxu634b58weydmlz3",
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryPeriod": null,
  "salaryEstimateMin": 85000,
  "salaryEstimateMax": 125000,
  "salaryEstimateScope": "canton",
  "salaryEstimateStatus": "ok",
  "adLink": null,
  "companyUrl": "https://www.jobs.ch/en/companies/ac026824-252f-4ee1-a901-05cadfe519ab-glutz-ag/",
  "companyWebsite": "https://www.glutz.com/jobs",
  "companyEmployees": "101 - 250",
  "companyJobCount": 3,
  "companyRating": 2.67,
  "companyRatingCount": 6
}
```

Good to know:

- `title` and `descriptionText` are the original text of the ad, as the employer wrote it, never translated. `translatedTitle` and `translatedDescriptionText` hold the site's translation into the language of the page, and are null when the ad is already in that language.
- `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`: what the **employer** published (measured: 4 job pages of 126). They are never mixed with the estimate below. `salaryEstimateMin`, `salaryEstimateMax`, `salaryEstimateScope`: the **site's estimate** as the site gives it (decimals included), for the canton or for Switzerland. The page shows the estimate to signed-in visitors only. `salaryEstimateStatus`: `ok` (estimate present), `none` (page read as a normal visitor, the site gives none on this site: a job listed on both can have an estimate on one and none on the other, measured on 9 of 51), `not_read` (the site hides it from addresses it classifies as suspect and none other could be found: the rest of the job is delivered and counted in `salaryEstimatesNotRead`), `null` when details were not asked for or the job page could not be read.
- `companyName` and `companyId` are null when the employer is not disclosed, never the site's "undisclosed employer" label. A few ads carry another label in that field (measured: `Company: Confidential`, 1 of 2,306).
- `isPromoted`: the row first seen for this job is a promoted one on the site (a promoted job also has its normal row elsewhere in the list; a promoted row carries no `locations`: they are filled from the job page when `includeDetails` is on, and stay `[]` otherwise). `contractTypes` is `[]` when the ad has none: never invented; an id this Actor does not know is delivered as `unknown:<id>` and counted.
- `contacts`: the contact block the page shows on the ad, as published: `firstName`, `lastName`, `role`, `phone`, each null when the ad does not give it; `[]` when the ad shows no name or phone (measured: 650 job pages of 3,281 have a contact, 420 of them with a phone number; 3 others show `-` as the number, delivered as such), and null when details were not asked for or could not be read. There is no email field: an address the employer wrote stays in the ad text or, rarely, in `role`.
- `companyRating` is given for 1 review or more, even though the page only shows it from a few reviews; null with 0 reviews.
- `locations` is `[]` and `canton` null on about 1 job in 4 (measured: 1,027 of 4,491), where the site gives only a place name: `place` is still filled. Keep it in mind if you filter by canton.
- `detailsStatus`: `ok`, `failed` (the job page could not be read, for example removed since the list was read: the job is still delivered, detail fields null, and counted in `detailsFailed`), or `null` when details were not asked for. A pasted job URL always reads its job page, even with `includeDetails` off.

### Run summary

Each run writes a readable **Run summary** (limited to the job count if the run is interrupted during a write) (and the same as JSON in `OUTPUT`). Per start URL: status, `searchVersion`, `jobsAnnouncedBySite` (last total seen), `jobsSeen` (distinct), `jobs` (newly delivered), `promotedRowsRepeated`, `missingVsAnnounced`, `toleranceJobs`, `detailsFailed`, `suspectPagesRetried`, `otherListSeen` (jobs of the different list served to a suspect address, refused), `failedPages`, reason. For the run: `crossSiteDuplicates`, `detailPages`, `detailsFailed`, `detailsSkipped`, `detailProbes` (single test requests while job pages are stopped, see Known limits), `detailPagesSuspectRetried`, `salaryEstimatesNotRead`, `detailPagesReadAsHuman` (0 with job pages read: none was read as a normal visitor, and the summary says so), `suspectPagesRetried`, `listingAccess`.

| Status | Meaning |
|---|---|
| `done` | every page read from the site's normal search, no page came back short before the last one, and the distinct jobs reach the last total: exactly on a list of one page (total and rows come from the same answer), within 2 jobs or 0.5% (the larger) on a longer one, then with the warning `JOBS_MISSING_WITHIN_TOLERANCE`. A repeated promoted row never makes up for a missing job. Job pages that could not be read do not change this status: the row gives their number (`detailsFailed`) and they are listed under **Checks** |
| `partial_collection` | something is missing: both numbers and the cause; warning `PARTIAL_COLLECTION`. Includes the case where every address tried was classified suspect |
| `stopped_at_limit` | your `maxItems` or maximum charge was reached |
| `no_results` | the site announced 0 jobs: the search is empty, the URL is wrong or the site is down (`NO_RESULTS` warning) |
| `no_listing` | the site shows no list on this URL (`NO_LISTING` warning): add a search term, or a filter this site knows |
| `no_new_jobs` | every job seen on this URL was really delivered by an earlier URL |
| `job_not_available` | a pasted job does not exist on this site; no job charged |
| `blocked_by_site`, `rate_limited`, `network_error`, `http_error`, `not_found`, `site_maintenance` | the site refused or did not answer even after new addresses: never reported as an empty search |
| `unrecognized_page` | the page has no job list the Actor can read; never reported as an empty search |
| `not_processed`, `interrupted`, `internal_error` | the URL was not reached or finished (limit reached before it, the last 5 of that kind on that site refused: warning `URLS_NOT_TRIED`, run stopped, unexpected error) |

A run where no URL delivers anything **fails** with the exact cause. Warnings (`PARTIAL_COLLECTION`, `JOBS_MISSING_WITHIN_TOLERANCE`, `TERM_NOT_IN_TITLES`, `UNEXPECTED_DATA`, `JOBS_SKIPPED`, `UNKNOWN_CONTRACT_ID`, `URLS_NOT_TRIED`, `SALARY_ESTIMATES_NOT_READ`, `DETAILS_NOT_READ`, `DETAILS_STOPPED`, `JOB_NOT_AVAILABLE`, `NO_LISTING`...) are listed under **Checks**, "Checks passed" is
shown only when there is none. Codes starting with `ANO_` come from the shared run checks: access verdicts, a field suddenly empty, an internal error.

### Pricing

Pay per event: **$1.90 per 1,000 jobs** ($0.0019 per job delivered), **full job pages included**: the same price with or without `includeDetails`, and for a pasted job URL. Plus a **$0.005 start fee per run** (charged by Apify once per GB of memory; the
default is 1 GB).
Apify subscribers pay less automatically, on every event: 10% off on the Bronze plan, 20% on Silver, 30% on Gold and above ($1.71, $1.52 and $1.33 per 1,000 jobs).

| Jobs | With full job pages (default) | Without full job pages |
|---|---|---|
| 20 | $0.043 | $0.043 |
| 1,000 | $1.905 | $1.905 |

Only jobs written to your dataset are paid, each one once, even when both sites or several of your URLs show it. A refused page, a repeated row or a job already delivered is not charged. A run that delivers nothing costs the start fee only. Your maximum charge per run is
respected: the run stops cleanly before the job that would exceed it, and no job page is read for a job that could not be paid.

### Known limits

- **The site's search is semantic**: a rare or absurd word returns jobs without any link to it (`hufschmied`: 318 jobs; `xqzwvkjh`: 48). They are delivered because they are the site's own list, with the warning `TERM_NOT_IN_TITLES` when no title read holds the word. Some jobs of ordinary searches are looser matches too: the same `notar` is 213 on the page, 41 in the strict search.
- **The site's list is semantic, and this Actor delivers the list the page shows.** Part of the jobs its exact-word list holds are not in its semantic list (measured on two exact-word lists: of 20 jobs, 13 are in the semantic list; of 20 others, 10).
- **There is no exact-match mode**: this Actor reads what the page shows to a normal visitor. The stricter list served to suspect addresses is refused on purpose.
- **AI translation on the page**: on some ads the page shows the AI translation straight away, title included. `title` and `descriptionText` stay the original, `translatedTitle` and `translatedDescriptionText` hold what the page shows.
- **Salary estimates** depend on the address: if no address the site treats as normal is found after 10 sessions, the job is still delivered, `salaryEstimateStatus: not_read`, with a warning that gives the number.
- **Ad texts are delivered as published**: when an employer writes a contact person's name, phone number or email address inside the ad, it stays in `descriptionHtml` and `descriptionText`. `contacts` holds only the contact block of the page, not what the ad text says.
- The site's **total moves by a few jobs during a read** (measured: 1,495 to 1,497): the last total is the one compared, within the tolerance above; the summary says when the total did not move.
- **Largest list read in full: 1,509 jobs** (76 pages). The largest one announced and read in part: 6,497 jobs (325 pages, pages 100 and 200 answered). Not measured beyond.
- If every address tried is classified suspect, the page is refused and the URL ends `partial_collection`: the different list is never delivered instead. With the proxy turned off or your own proxy URLs, no Swiss residential fallback is used.
- A job listed on both sites is delivered once, with the fields and the link of the first site read. Measured on 43 jobs in common: 0 job page failed. A job listed on one site only is a 404 on the other.
- **Time measured on Apify**: with full job pages, 0.7 to 1 s per job on the longest lists (910 jobs in 662 s, 756 in 726 s), 1.5 to 2.6 s per job on smaller ones (393 jobs in 588 s, 59 in 155 s), more on very small lists (7 jobs in 46 s); without job pages, 2.7 to 5.7 s per page (29 pages in 77 s; 59 pages in 336 s; 1,485 jobs in 264 s).
- **If the site refuses job pages**: after 5 refused or unreadable in a row, job pages stop being read. Single test requests are then sent before some of the next result pages, from the address in use, which can be the refused one; job pages resume only when a test request is answered. A test request that lands on a removed job proves nothing, so that result page also goes without job pages. Until then jobs are still delivered and charged at the normal price, with `detailsStatus: failed` and empty detail fields, the URL can still end `done`, and a whole run can end with no job page read even if the site works again from other addresses. The warning `DETAILS_STOPPED` gives the numbers. A new run on the Apify proxy uses new addresses: run those URLs again.
- **If the site refuses or fails on 5 start URLs in a row** (blocks, rate limits, no answer, 5xx errors; a 4xx error does not count), counted apart for job URLs and for lists on each site, the other URLs of that kind on that site are not tried in this run, even if the site works again minutes later: status `not_processed`, warning `URLS_NOT_TRIED`, nothing charged for them. Run them again later.
- Default run timeout: 4 hours. `maxItems` defaults to 20: set 0 to read everything.

### Support

Questions, a URL that does not work, a field you need: open an issue on the Actor's **Issues** tab with the run link.

This Actor is not affiliated with, endorsed or sponsored by JobCloud, jobs.ch or jobup.ch. These names are trademarks of their owners, used here only to describe the websites the data comes from. Job ads are collected from the pages served to anonymous visitors, without any account.

# Actor input Schema

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

jobs.ch or jobup.ch search results URLs exactly as your browser shows them, filters included (https://www.jobs.ch/en/vacancies/?term=engineer, https://www.jobs.ch/de/stellenangebote/?term=pflege\&location=Bern, https://www.jobup.ch/fr/emplois/?term=vendeur, a category page such as https://www.jobs.ch/en/vacancies/admin-hr-consulting-ceo/legal-attorneys-court/), or job URLs (.../detail/<id>/). You can combine them with search terms.

## `searchTerms` (type: `array`):

Search words, one per line, e.g. engineer, notar, vendeur. Each one is searched on every site chosen below.

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

City or region as you would type it on the site, e.g. Basel, Lausanne, Zürich. Applies to search terms only: a pasted URL keeps its own filters.

## `sites` (type: `array`):

Which sites the search terms are read on. A job published on both is delivered and charged once.

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

Language of the pages read. jobup.ch has no German pages: with German it is read in French, and the run says so. It changes the translated fields, never the original ad text.

## `publishedWithinDays` (type: `integer`):

Leave empty for any time. Accepted: 1, 3, 7 or 30, like the site's filter.

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

For example 60: the site's minimum workload filter.

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

For example 80: the site's maximum workload filter.

## `contractType` (type: `string`):

Leave empty for any contract.

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

Stops after this many unique jobs across all start URLs and both sites. 0 = no limit. You pay per delivered job.

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

On by default and included in the price per job. Adds the ad text, translation, expiry date, address, languages and skills asked for, contacts published on the ad, apply method and link, employer salary, the site's salary estimate and company data. A job page read by an address the site classifies as suspect has no estimate: it is read again from another address. Off: faster, only the fields of the list (a pasted job URL still reads its job page).

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

Apify datacenter proxy by default, one sticky session kept while the site treats it as a normal visitor. A session the site classifies as suspect is replaced; after 5 in a row, the listing pages move to a Swiss residential proxy; the job pages never do, they try up to 10 sessions, then the job is delivered with salaryEstimateStatus not_read. If you turn the proxy off or give your own proxy URLs, your choice is respected and no fallback is used.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.jobs.ch/en/vacancies/?term=engineer"
    }
  ],
  "sites": [
    "jobs.ch",
    "jobup.ch"
  ],
  "language": "en",
  "maxItems": 20,
  "includeDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All job records, one per job offer, with every field.

## `jobsOverview` (type: `string`):

Title, company, place, canton, workload, contract, employer salary, date, site, link.

## `summary` (type: `string`):

Readable per-URL summary: status, search version, jobs announced and seen, repeated rows, suspect pages refused, reason for anything missing.

## `summaryJson` (type: `string`):

The same summary as JSON (OUTPUT record).

# 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=engineer"
        }
    ],
    "maxItems": 20,
    "includeDetails": true,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("hexagone-data/jobs-ch-jobup-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=engineer" }],
    "maxItems": 20,
    "includeDetails": True,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("hexagone-data/jobs-ch-jobup-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 '{
  "startUrls": [
    {
      "url": "https://www.jobs.ch/en/vacancies/?term=engineer"
    }
  ],
  "maxItems": 20,
  "includeDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call hexagone-data/jobs-ch-jobup-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hexagone-data/jobs-ch-jobup-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/2WjYuxy96EVd5Oxdr/builds/ArVu7E8D4NlnKMqyG/openapi.json
