# 🇨🇭 Jobs.ch $0.99/1K: Swiss Job Board, 46K Swiss Job Listings (`snow_leo_data/jobs-ch-scraper-swiss-switzerland-jobs`) Actor

$0.99/1K | 46,576 Swiss jobs in one run — the public jobs.ch API stops at 2,000. Full advert text, apply URL, workload %, coordinates. Incremental mode pays only for what appeared since yesterday. A Switzerland job board of Switzerland job listings data. Sion jobs, Biel jobs, Lucerne jobs.

- **URL**: https://apify.com/snow\_leo\_data/jobs-ch-scraper-swiss-switzerland-jobs.md
- **Developed by:** [Snow Leo Data](https://apify.com/snow_leo_data) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.99 / 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?

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

## 🇨🇭 Jobs.ch Scraper — every Swiss job, past the 2,000 cap

**$0.99 per 1,000 jobs.** Swiss job data with the full advert text, the direct
apply URL, the Swiss workload percentage and coordinates — from jobs.ch, the
largest Swiss job board.

|  |  |
|---|---|
| **46,121 jobs live on jobs.ch** | measured today from the site's own counter |
| **The public API stops at 2,000 per query** | page 101 returns HTTP 422, and 20 rows per page is the hard maximum |
| **This actor returns more than that in one run** | measured: 2,400 unique jobs in 64 s, zero duplicates |
| **38 fields** | the search endpoint alone gives 20; the rest come from the detail record |
| **$0.99 / 1,000** | the other jobs.ch actor on this store charges $2.00 |

### The 2,000 wall, and how this gets past it

Ask jobs.ch for page 101 and it answers **HTTP 422**. Ask for 50 rows instead of
20 — **HTTP 422** again. So one query can never hand you more than **2,000**
jobs, no matter what limit you set. With 46,121 jobs on the site, a scraper that
simply pages through a search leaves **44,121 of them unreachable**. This one
reaches the Switzerland vacancies the search endpoint will not hand over.

This actor splits a wide search automatically: first by category, then by region,
then by industry, employment type and finally by workload band, until every slice
fits under the wall. Slices overlap, so every job is deduplicated by its id —
the run report shows how the query was split.

```
walk: 2400 unique jobs in 64 s, 12 queries
```

### Who this is for

**Recruiters and staffing firms** watching the Swiss market. Turn on
*Only jobs not seen before*, run it daily, and you are charged only for what
appeared since yesterday.

**Job boards and aggregators.** Every row carries the advert text and the direct
apply URL, so a listing can be republished or linked without a second fetch.

**Market research.** The Swiss workload percentage is a first-class field, not a
string to parse: a 60–80% advert comes out as `workload_min: 60`,
`workload_max: 80`, `workload: "60–80%"`.

### Quick start

Press **Start**. With an empty input you get a 500-job sample of the newest
listings — a trial, not an invoice.

For a real run, put a term into **Search term** and a town into **Location**.
The location list is generated from the live API, with the number of open jobs
next to each town.

```json
{
  "query": "software engineer",
  "location": "Zürich",
  "workloadMin": 80,
  "postedWithinDays": 7,
  "maxItems": 0
}
```

### The Swiss words for what you are looking for

Half of the confusion around Swiss job data is vocabulary, so here is the map.
What an English speaker calls a job advert is a Stelleninserat or a
Stellenanzeige on jobs.ch, and a list of them is Stellenangebote — this Actor
returns Stellenangebote data and jobs.ch data as rows. If you are searching for Stellen Schweiz,
Jobs Schweiz or Swiss vacancies, this Actor is the thing that returns them —
the whole set, not the first two thousand.

Pensum is the Swiss word for the workload percentage, broken out into numbers
further down: a Pensum of 80% is four days a week. Arbeitsort is the place of
work, which arrives split into place, street and zipcode rather than as one line
of text. In French
Switzerland the same advert is an offre d'emploi, and emploi Suisse searches land
on the same listings, which is why every row carries both a German and a French
link to the posting.

The search term goes to jobs.ch untranslated, so type it in the language the
advert was written in: pflegefachfrau pulls the healthcare jobs data that the
English word nurse would miss entirely, ingenieur pulls engineering jobs data,
and elektriker finds electricians. Most adverts here are written in German, so
German jobs data — Swiss listings in German, not listings from Germany — is the
bulk of any wide run.

### Where the Swiss jobs actually are

Switzerland is small, and the Swiss job market is not spread evenly across it:
jobs in Switzerland cluster in a handful of towns. These are the ones with the
most open adverts right now, counted from the site's own place facet on the day
this was written:

| Town | Open adverts | Town | Open adverts |
|---|---|---|---|
| Zürich | 4,478 | Frauenfeld | 540 |
| Bern | 1,860 | Chur | 433 |
| Basel | 1,131 | Aarau | 429 |
| Winterthur | 1,105 | Oftringen | 396 |
| Luzern | 1,082 | Thun | 369 |
| Sursee | 696 | Solothurn | 272 |
| Baden | 666 | Genève | 263 |
| St. Gallen | 586 | Schaffhausen | 263 |
| Lausanne | 556 | Zug | 555 |

The same list, with the counts refreshed from the live API, is the drop-down
behind the **Location** field, so you never have to guess whether a town is
worth a run. Basel jobs data and Bern jobs data need no second run: the town is
a filter on one search, not a separate Actor.

***

### Swiss job listings as data

Swiss job listings come back as rows rather than as a page: title, employer,
workload percentage, apply URL and coordinates. Full time jobs data separates
from part-time on the workload field — part time jobs data is the same column
read from the other end, `workload_max` under 100 — and job coordinates data comes from the
site, not from geocoding. Anyone measuring the Switzerland
job market gets Swiss employment data with the advert body attached, which is
where the language requirement is actually stated.
Employer contact data is on the row wherever the employer published one:
`contact_name` and `contact_address` rather than a form to fill in. And
`employment_types` carries jobs.ch's own type ids, so permanent jobs data
separates from temporary work on a field instead of on a keyword — the ids are
passed through raw, because jobs.ch publishes no dictionary for them.
Job ads Switzerland-wide come from the same run: jobs in Zurich, Geneva, Basel
and every smaller town, with no per-canton setup.

### Filters run before you are charged

Keywords, excluded keywords, town, workload range, posting age, employer
allow-list and block-list. A row removed by a filter never reaches the dataset
and never appears on your bill. The run report counts what each filter dropped.

Dataset views can only choose columns — no view on Apify can drop rows — so the
filtering lives in the input, where it is also cheaper.

### Incremental runs

`onlyNew: true` remembers delivered jobs in a **named** key-value store that
survives between runs, so a schedule pays only for new listings. Memory is
written **after** a batch is delivered, never before: a job that did not reach
you is never marked as delivered.

### Honest limits

- **jobs.ch publishes no dictionary for its facet ids.** Category, region and
  industry ids are passed through raw rather than given invented labels.
- **No salary field.** Swiss adverts rarely state pay, and jobs.ch does not
  expose it, so this actor does not pretend to have it.
- Slices of a split query overlap; deduplication is by job id, so the same job
  is delivered — and charged — once.

### How the split actually works

#### Why a limit setting cannot help you

The cap is not a setting on your side. `maxItems` controls how many rows you
want; it cannot make jobs.ch hand over page 101. Any scraper that issues one
query and pages through it stops at 2,000 rows no matter what the input says.

#### What the Actor does instead

It asks jobs.ch how many jobs match your search. If the answer is under 2,000,
it simply pages through. If it is over, the search is cut into slices that each
fit under the wall, and every slice is walked separately:

1. **By category** — 165 buckets, the biggest holding about 10,000 jobs.
2. **By region** — 24 buckets, the largest canton holding around 38,000.
3. **By industry** — 25 buckets.
4. **By employment type** — 6 buckets.
5. **By workload band** — 10–39%, 40–59%, 60–79%, 80–89%, 90–100%.

Each step is applied only when the previous one leaves a slice still too large,
so a narrow search never pays for splitting it does not need.

#### What happens to jobs that appear in two slices

They are delivered once. Every job carries an id, and the walk keeps the ids it
has already handed over. The run report tells you how many queries and pages it
took and which dimensions were used, so the cost is never a mystery.

### Fields

Every row has 38 fields. The ones that matter most:

| Field | What it is |
|---|---|
| `title`, `profession`, `synonym` | the advert headline, the employer's own job name, and the job family jobs.ch files it under |
| `company`, `company_logo` | employer and logo URL |
| `place`, `street`, `zipcode`, `latitude`, `longitude` | where the work is, down to coordinates |
| `workload`, `workload_min`, `workload_max` | Swiss workload percentage, parsed into numbers |
| `description_text`, `description_html` | the full advert, as text and as HTML |
| `apply_url`, `external_url` | straight to the application, and to the employer's own page |
| `url`, `url_de`, `url_fr` | the listing in English, German and French |
| `published_at`, `posted_days_ago`, `expires_at` | when it appeared and when the advert comes down |
| `contact_name`, `contact_address` | named contact, when the employer published one |

Empty means the employer did not publish it. Nothing here is inferred.

### Fields in detail

#### Workload, the Swiss particularity

Swiss adverts state a percentage instead of full-time or part-time. An 80–100%
role means the employer will take someone at four days a week or five. This
Actor gives you `workload_min: 80`, `workload_max: 100` and `workload: "80–100%"`
— numbers you can filter on, plus the label for display.

Filtering respects the range rather than a single number: asking for at least
80% keeps a 60–80% advert out, and asking for at most 60% keeps a 40–60% advert
in. A scraper that compares one number gets both of those wrong.

#### Why the umlaut changes the answer

jobs.ch matches the location string you give it, and it does not fold accents
for you. Searching the location **Zürich** returns 11,198 adverts; the ASCII
spelling **Zurich** returns 11,190 — close, but not the same set. In French
Switzerland the gap is wider: **Genève** returns 881 and **Geneva** returns 687,
because roughly two hundred adverts spell the city only in French. Basel returns
2,683 and Lausanne 925 with either spelling, since neither carries an accent.

Two consequences worth knowing before you schedule anything. First, a location
search is wider than the town itself: the place facet counts 4,478 adverts whose
address is literally Zürich, while a location search for Zürich returns 11,198,
because it reaches the commuter belt around the city. Second, if you want every
Geneva advert, run the French spelling — or leave the location empty, keep the
search term, and filter on `place` afterwards.

#### Addresses and coordinates

`place`, `street` and `zipcode` come straight from the employer's entry, and
`latitude`/`longitude` are the coordinates jobs.ch itself publishes. Plotting a
map or running a radius search of your own needs no geocoding step.

#### Three languages, one job

Switzerland posts in German, French and English. Every row carries `url`,
`url_de` and `url_fr` for the same listing, so a board can link readers to the
version they read.

#### What is deliberately missing

There is **no salary field**. Swiss adverts rarely state pay and jobs.ch does
not expose it, so this Actor offers no parsed pay figures at all and does not invent them. Facet ids for category, region and industry are passed through raw: the
site publishes no public dictionary for them, and inventing labels would mean
inventing data.

### Running it on a schedule

#### Paying only for what is new

Turn on **Only jobs not seen before** and set a schedule. The Actor keeps the
ids it has delivered in a named key-value store that survives between runs, so
tomorrow's run returns only what appeared since today — and bills only for
those rows.

The memory holds up to 150,000 ids and drops the oldest first when it fills.
Ids are written **after** a batch reaches your dataset, never before, so a run
that dies mid-way never marks undelivered jobs as delivered.

#### Putting the output somewhere useful

The run page hands you the dataset as JSON, CSV, XML or Excel, which is the
whole workflow for a one-off list. Anything repeatable goes through the API:

```
https://api.apify.com/v2/datasets/<datasetId>/items?format=csv&clean=true
```

You do not have to track dataset ids for a nightly job. This address always
answers with the last finished run of the Actor:

```
https://api.apify.com/v2/acts/<actorId>/runs/last/dataset/items?format=json
```

#### No-code pipelines, and the webhook underneath them

Apify maintains a connector for the tools most buyers already run —
[Make](https://docs.apify.com/platform/integrations/make),
[n8n](https://docs.apify.com/platform/integrations/n8n),
[Zapier](https://docs.apify.com/platform/integrations/zapier) and
[Slack](https://docs.apify.com/platform/integrations/slack) — and a
[webhook](https://docs.apify.com/platform/integrations/webhooks) for everything
else: when a run finishes, Apify calls your URL with the run id and the dataset
id in the payload.

In Make and n8n the scenario is the same two blocks every time: run the Actor,
then read its dataset. Name the storage if the pipeline depends on it — unnamed
storages on Apify are temporary by design, named ones stay, and the delta memory
described above is a named store for exactly that reason.

#### Three things people build with this

**A morning feed of new Swiss adverts.** Delta switch on, daily schedule, output
into Slack or a sheet. Each row already carries the apply URL, so a recruiter
acts without opening jobs.ch.

**A local market report.** Leave the search term empty, pick a town, and count
adverts by profession and by workload band. The Pensum arrives as two numbers,
so part-time share is a calculation and not a guess.

**A Swiss job board.** The advert body comes as text and as HTML, the address is
split into place, street and postcode with coordinates attached, and every
listing carries both its German and its French URL.

### Questions people ask

#### How many jobs can one run return?

As many as your limit allows. The 2,000 ceiling belongs to a single query, not
to a run: the Actor issues as many queries as the search needs. A measured run
returned 2,400 unique jobs in 64 seconds, and nothing in the design stops it
before the search is exhausted.

#### Does it fetch the full advert text?

Yes, when **Fetch the full advert text** is on. That costs one extra request per
job and brings the advert body, the direct apply URL, contact details, street
and postcode. Turn it off and the run is much faster and cheaper — the row keeps its
shape, but the fields that only the detail record carries come back empty.

#### Why are some fields empty?

Because the employer did not fill them in. Empty means absent at the source, not
lost in transit. Numeric fields come back as `null` rather than an empty string,
so a typed consumer — Excel, BigQuery, pandas — accepts the column without a
cast.

#### Can I filter to only jobs that state a workload?

Set **Workload from** to 10. Any advert that states a percentage at all passes;
adverts with no workload stated do not. The filter runs before billing, so the
rows it removes cost you nothing.

#### How do I get only new jobs each morning?

Turn on **Only jobs not seen before**, then schedule the Actor daily from the
Apify console. The first run fills the memory and bills for everything it
delivers; every run after that bills only for new listings.

#### What does it cost to try?

Running with an empty input returns a 500-job sample of the newest listings —
about fifty cents. There is no way to accidentally pull the whole board on a
first click: without a search term and without a limit the Actor treats the run
as a trial and stops.

#### Can I limit a run to one canton?

Not by canton name — jobs.ch exposes regions as numeric ids, not as labels, and
inventing the mapping would mean inventing data. Two honest routes exist. Put
the main town into **Location**, which reaches the commuter belt around it, or
pass the raw region id into **Region ids** if you already know it from the
site's own URL. The run report prints which region ids a split used, so one
wide run tells you the ids for every narrow run after it.

#### Does it cover French-speaking Switzerland?

Yes, and the whole of Ticino too — the endpoint is the same for all three
language regions. The catch is spelling, not coverage: an advert published in
Geneva is filed under Genève, and a search for the English spelling misses about
two hundred of them. Every row carries `url_de` and `url_fr` for the same
listing, so a bilingual board can link readers to the version they read. There is no Italian link field, so Italian-language URLs are not offered.

#### Can I find part-time work only?

Yes, and this is where the Swiss workload percentage earns its place. Set
**Workload up to** to 60 and **Workload from** to 10: you get adverts that state
a Pensum at or below 60%, and adverts with no percentage at all stay out. Asking
the other way round — **Workload from** 80 — gives you full-time and near
full-time roles. The range is compared as a range, so a 60–80% advert is kept by
a request for at most 80% and dropped by a request for at least 90%.

#### Is jobs.ch blocking scrapers?

This Actor uses the same public JSON endpoint the site's own search page calls,
with no proxy and no browser. It keeps one shared per-host pace and backs off
when the server asks it to. If the source ever starts refusing, the run report
names the failure instead of returning a silently short result.

#### A run succeeded but the dataset is empty — is that a failure?

Not necessarily, and the Actor always says which case it is. The run report
carries `empty_reason` and the log carries a warning naming it:
`nothing-changed` (the incremental mode working as asked — everything had
already been delivered by an earlier run, so nothing was written and nothing
was charged), `filtered-out` (your filters removed everything that was
collected), `source-returned-nothing` (the search matched nothing at all), or
`charge-limit-reached` (the run's spending limit was already used up). A silent
empty dataset is treated as a defect, not as a normal outcome.

`maxItems` counts what you are **given**, not what was looked at: the Actor
keeps walking the source until your filters have passed that many rows. And
`withDescriptionOnly` needs the advert body, so it turns descriptions on for
that run by itself rather than quietly returning nothing.

### Jobs outside Switzerland

jobs.ch is a national board: it carries Swiss vacancies and nothing else. Three
sibling Actors cover the rest of the same job data, and each stamps its rows with
a `source` field so their datasets can be concatenated and still be told apart:

- [ATS Jobs Scraper](https://apify.com/snow_leo_data/greenhouse-workday-lever-ashby-ats-jobs-scraper)
  — the employer's own careers page across 20 applicant tracking systems, any
  country, including the Swiss companies that run Greenhouse or Workday.
- [SEEK, JobStreet & JobsDB Scraper](https://apify.com/snow_leo_data/seek-jobstreet-jobsdb-australia-jobs-scraper)
  — Australia, New Zealand and six Asian markets.
- [The Muse Jobs Scraper](https://apify.com/snow_leo_data/the-muse-remote-company-jobs-scraper)
  — one curated board across employers, remote roles included.

### Reproducing the numbers

```
python3 tests/run_all.py --live
```

`test_live.py` asserts the 2,000 wall against the live API and then proves the
walk gets past it. Every number in this README comes from that run.

# Actor input Schema

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

Keyword as you would type it on the site — `java`, `pflegefachfrau`, `controller`. Leave empty to take everything.

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

Pick a Swiss town or type any other. jobs.ch matches the town itself; it publishes no radius search. The number after each name is how many jobs are open there right now.

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

Swiss adverts state a workload percentage instead of full/part time. 80 keeps everything that can be worked at 80% or more.

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

Use with the field above to select part-time work, e.g. 40 to 60.

## `postedWithinDays` (type: `integer`):

0 = no date filter.

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

Checked against job titles, the profession field and the full advert text.

## `excludeKeywords` (type: `array`):

Drops a job if any of these appears in its title or text.

## `placeContains` (type: `string`):

Extra text match on town, postcode or street — narrower than the Location field above.

## `companies` (type: `array`):

Substring match on the company name.

## `excludeCompanies` (type: `array`):

Substring match. Useful for leaving out staffing agencies that repost the same role.

## `withDescriptionOnly` (type: `boolean`):

Drops listings whose advert body is empty at the source.

## `includeDescription` (type: `boolean`):

On: one extra request per job brings the full text, the apply URL, contact details, street and postcode — 38 fields instead of the 20 the search endpoint returns. Off: much faster and cheaper.

## `onlyNew` (type: `boolean`):

For schedules. The Actor remembers what it already delivered in a named store that survives between runs, so a daily run returns only what appeared since yesterday — and you pay only for those.

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

0 = no limit. The form starts at 300 so a first run is quick and cheap; raise it or set 0 once you know the search is right. A run with no search and no limit is treated as a trial and stops at 500.

## `categoryIds` (type: `array`):

Raw jobs.ch category ids, if you already know them. Leave empty for all.

## `regionIds` (type: `array`):

Raw jobs.ch region ids.

## `industryIds` (type: `array`):

Raw jobs.ch industry ids.

## `employmentTypeIds` (type: `array`):

Raw jobs.ch employment type ids.

## Actor input object example

```json
{
  "query": "software engineer",
  "location": "",
  "workloadMin": 0,
  "workloadMax": 0,
  "postedWithinDays": 0,
  "keywords": [],
  "excludeKeywords": [],
  "placeContains": "",
  "companies": [],
  "excludeCompanies": [],
  "withDescriptionOnly": false,
  "includeDescription": true,
  "onlyNew": false,
  "maxItems": 300,
  "categoryIds": [],
  "regionIds": [],
  "industryIds": [],
  "employmentTypeIds": []
}
```

# Actor output Schema

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

Every job collected in this run. Open the Apply links view to go straight to applications.

## `report` (type: `string`):

How the query was split past the 2,000 cap, what the filters removed before billing, and what was skipped as already seen.

# 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": "software engineer",
    "maxItems": 300
};

// Run the Actor and wait for it to finish
const run = await client.actor("snow_leo_data/jobs-ch-scraper-swiss-switzerland-jobs").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": "software engineer",
    "maxItems": 300,
}

# Run the Actor and wait for it to finish
run = client.actor("snow_leo_data/jobs-ch-scraper-swiss-switzerland-jobs").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": "software engineer",
  "maxItems": 300
}' |
apify call snow_leo_data/jobs-ch-scraper-swiss-switzerland-jobs --silent --output-dataset

```

## MCP server setup

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

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/3zdLS97B8Bmol6fdQ/builds/iyQENK8SgfXxAnNrV/openapi.json
