# Jobs.cz - Czech Jobs, Salary, Benefits & GPS (`abotapi/jobs-cz-scraper`) Actor

Scrape Czech job listings from Jobs.cz. Search builder (keyword, location, field, salary, employment type) or paste any Jobs.cz URL. Title, company, salary range with currency and period, benefits, GPS coordinates, required education and languages, employer contact info, remote/hybrid flag,.

- **URL**: https://apify.com/abotapi/jobs-cz-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 job results

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.cz Scraper: Czech Job Listings with Salary, GPS & Employer Contact

Jobs.cz Scraper turns Jobs.cz, the largest job board in the Czech Republic, into structured hiring data. Build a search from Czech-language keywords, location, field, salary range and employment type, or paste any Jobs.cz link, and get one row per job with title, company, location, salary, benefits, GPS coordinates, required education and languages, employer contact info and the full description. Run it once, resume a large crawl, or schedule it to monitor a search and get only what changed, then export the results to JSON, CSV or Excel, read them through the API, or pipe them straight into the apps you already use.

### Why This Scraper?

- **Search builder and URL mode.** Build a search from a Czech-language keyword, location, field, salary range and employment type, or paste any Jobs.cz listing, search or direct position URL, mixed freely in one run.
- **Rich structured output.** Salary range with currency and period, GPS coordinates, benefits, required education and languages, employment and contract type, suitability flags, employer phone, email and contact, remote or hybrid flag, posting date, and the full description in HTML and plain text, on a pasted single position link or with Fetch full job details on.
- **Czech-language aware.** Field labels and descriptions are extracted and normalized, and salary text such as "150 000 - 185 000 Kč" is parsed into structured `salary.min` / `salary.max`.
- **Incremental monitoring, with a real limit.** Schedule a recurring run against a search short enough to be read in full each time, and get only what changed. A search spanning more than a handful of pages is not currently guaranteed to be revisited in full on a scheduled run; see "Resume and recurring updates" below.
- **Honest about blocked runs.** A run that cannot read a single page fails loudly with a clear message instead of finishing green with an empty dataset, and nothing is ever marked as gone while a page could not be read.

### Use Cases

- **Recruiters and staffing agencies:** track competing postings across sectors, regions and salary bands to benchmark your own openings.
- **Compensation and workforce researchers:** build salary datasets by role, region and employment type straight from live listings.
- **Job aggregators and alert bots:** mirror a whole professional field or a narrow keyword search and get notified only when something changes.
- **Talent sourcing tools:** pull employer phone, email and contact name straight from a job posting to reach out directly.
- **Market and content teams:** track new-posting volume and remote/hybrid trends per field or region over time.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live posting.

| Field | Example |
| --- | --- |
| `jobId` | `"3300000001"` |
| `jobUrl` | `"https://www.jobs.cz/rpd/3300000001/"` |
| `title` | `"Senior Python Engineer, Cloud Platform"` |
| `companyName` | `"Vzorová firma s.r.o."` |
| `location` | `"Praha 4, Hlavní město Praha"` |
| `coordinates` | `{ "latitude": 50.041, "longitude": 14.437 }` (needs Fetch full job details) |
| `salary` | `{ "currency": "CZK", "min": 90000, "max": 120000, "unit": "měsíc" }` (needs Fetch full job details) |
| `salaryRaw` | `"90 000 - 120 000 Kč / měsíc"` |
| `employmentTypes` | `["Práce na plný úvazek"]` |
| `contractType` / `contractDuration` | `"Pracovní smlouva"` / `"Na dobu neurčitou"` |
| `requiredEducation` | `"Vysokoškolské / univerzitní"` (needs Fetch full job details) |
| `requiredLanguages` | `["Angličtina, úroveň Pokročilá"]` (needs Fetch full job details) |
| `benefits` | `["Stravenky", "Možnost občasné práce z domova"]` (needs Fetch full job details) |
| `remote` / `arrangement` | `true` / `"remote"` (derived from the benefits list) |
| `postedDate` | `"2026-06-01T09:00:00+02:00"` |
| `descriptionText` | `"Hledáme zkušeného vývojáře, který posílí náš tým..."` (needs Fetch full job details) |
| `employerPhone` / `employerEmail` | `"+420 200 300 400"` / `"kariera@example.com"` (employer-site jobs only) |
| `contactName` / `contactCompanyName` | `"Personální oddělení"` / `"Vzorová firma s.r.o."` (employer-site jobs only) |
| `applyUrl` / `applyType` | `"https://www.jobs.cz/jof/3300000001/"` / `"internal"` (job-page jobs only) |
| `detailSource` | `"employer-site"` (also `"job-page"`, or `null` when Fetch full job details is off) |
| `scrapedAt` | `"2026-06-12T08:00:00.000Z"` |

In incremental mode, every row also carries `changeType` (`NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED`), `changedFields`, `firstSeenAt` and `lastSeenAt`. Jobs read from an employer's own careers micro-site additionally carry the suitability flags `suitableForGraduate`, `suitableForDisabled`, `suitableForPensioner` and `suitableForMaternityLeave`, plus `companyAddress` and `employerProfileUrl`; jobs hosted on Jobs.cz itself instead carry `categories`.

### How to Use

1. Pick a **mode**: `search` (build a search from keyword, location, field, salary and employment type) or `url` (paste Jobs.cz links).
2. Fill in the fields for that mode. In search mode, leave everything blank to list every current opening.
3. Set **Max items** (and, in url mode, **Max pages**) to control run size and cost, and turn on **Fetch full job details** if you need the full description, benefits, GPS and employer contact.
4. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it through the API.

**Search by keyword and location:**

```json
{
  "mode": "search",
  "keyword": "python",
  "location": "Praha",
  "maxItems": 20
}
```

**Search one professional field, narrowed by location and employment type:**

```json
{
  "mode": "search",
  "field": "IS/IT",
  "location": "Brno",
  "employmentType": "full",
  "maxItems": 15
}
```

**URL shapes (url mode).** The `urls` field now ships with one real example per supported shape instead of being empty, so switching to url mode without editing the field scrapes all of them (capped by Max items). Clear the list and paste your own links to change this.

| Shape | Example | Support |
|---|---|---|
| Keyword search | `https://www.jobs.cz/prace/?q%5B%5D=python` | Walked page by page |
| Locality | `https://www.jobs.cz/prace/praha/` | Walked page by page |
| Profession | `https://www.jobs.cz/prace/programator/` | Walked page by page |
| Locality and profession | `https://www.jobs.cz/prace/praha/programator/` | Walked page by page |
| Part-time hub | `https://www.jobs.cz/brigady/` | Walked page by page |
| Graduate hub | `https://www.jobs.cz/absolvent/` | Walked page by page |
| Sector hub | `https://www.jobs.cz/it/` | Walked page by page |
| Single position | `https://www.jobs.cz/rpd/{id}/` | One job, full detail |
| Tracking parameters (`utm_*`, `searchId`) | any of the above with extra parameters | Accepted |
| Company gallery and profiles (`/spolecnosti/`, `/hledani-dle-firem/`, `/fi/`), employer micro-site home pages | | Not supported: skipped with a warning in the log naming the URL |

**Paste a listing URL and fetch full details:**

```json
{
  "mode": "url",
  "urls": ["https://www.jobs.cz/prace/praha/"],
  "maxItems": 10,
  "fetchDetails": true
}
```

**Monitor a search on a schedule:**

```json
{
  "mode": "search",
  "keyword": "python",
  "location": "Praha",
  "incrementalMode": true,
  "emitExpired": true,
  "maxItems": 20
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/jobs-cz-scraper").call(run_input={"mode": "search", "keyword": "python", "location": "Praha"})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], job["companyName"], job.get("salaryRaw"))
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/jobs-cz-scraper').call({ mode: 'search', keyword: 'python', location: 'Praha' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Max items, Max pages and detail enrichment

`maxItems` is the run's overall cap (default 20; each of your searches or URLs gets a share of it). `maxPages` limits how many result pages are walked per search (about 20 to 30 jobs per page) and is unlimited by default, so `maxItems` alone decides where a run stops. Turning on **Fetch full job details** (`fetchDetails`) adds one request per job and is billed as a separate detail-enrichment charge on top of the base per-job price; leave it off to get only the search-result fields (title, company, location, salary text, employment type). Pasting a single position link (`/rpd/{id}/`) always fetches and bills its full detail regardless of this toggle, since the job's own page has to be read to return it at all.

#### Resume and recurring updates

- **A safety guard stops paging a search after three pages in a row that added no new job to the dataset**, whether or not Resume or Incremental mode is involved. On a plain, non-recurring run this simply means you never crawl deep past a run of duplicates, which is harmless. On a **recurring or resumed** run it is a real limitation: since Incremental mode's own repeat visits deliberately produce runs of unchanged (held-back) pages, a stable search longer than a few pages can trip this guard on ordinary scheduled runs, not just on Resume, and stop before reaching pages further down that hold new or updated jobs. Keep your search narrow enough that its full result set fits within a handful of pages if you rely on Incremental mode or Resume for it; a broad, multi-page search is not currently guaranteed to be fully revisited by either feature.
- **Resume** (`resumeFromRunId`) continues one interrupted crawl: paste its run or dataset ID and every job it already returned is skipped rather than re-fetched or re-billed. The crawl restarts from the first search page and walks forward again, so it hits the same three-page guard described above; if your earlier run already collected more than about three pages, a plain Resume on the same search will read pages 1 to 3, find them all already collected, and stop there rather than reaching new jobs deeper in. Paste the search as a URL-mode link with a `page` number that picks up where the earlier run stopped instead, if you need to keep extending a large crawl.
- **Incremental mode** (`incrementalMode`) is for recurring monitoring of the same search: turn it on and schedule the run, and each run's state (what it saw last time) is remembered between runs, keyed on the search setup, or on `stateKey` if you set one. For a search short enough to be read in full every run, later runs return only NEW and UPDATED jobs (plus UNCHANGED ones if you turn on `emitUnchanged`). For a longer search, the same three-page guard above can end the walk early on a run where most pages are unchanged, which means some new or updated jobs deeper in the search can be silently missed that run, without the run reporting any error.
- **`EXPIRED`** is only ever produced after a run that read every search page it asked for: never when `maxItems`/`maxPages` capped the run, never when `resumeFromRunId` was used, never when a page could not be read, and never when the three-page guard above ended the walk early. In practice this means `EXPIRED` (and, later, `REAPPEARED`) essentially never fires for a search wider than a handful of pages, since a stable search of that size tends to trip the guard on its own unchanged pages before reaching the end. Turning on **`emitExpired`** is what makes a job trackable as **`REAPPEARED`** later: only a job that a previous complete scan has already marked `EXPIRED` can come back as `REAPPEARED`, so with `emitExpired` left off, or with a search too wide to ever complete a scan, `REAPPEARED` never happens either.

#### Output views

The dataset has three tabs in the Apify Console: **Job listings** (the overview table), **Job details** (description, benefits, GPS, education, languages and employer contact; filled when Fetch full job details is on or for pasted position links) and **Changes (incremental mode)** (`changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`). All three show the same rows; the full record is always in the dataset.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each job, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where job pages should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (build a search) or `url` (paste Jobs.cz links). |
| `keyword` | string | (none; prefill `python`) | Free-text search, e.g. python, programátor, účetní. Leave blank to list everything (search mode). |
| `location` | string | (none; prefill `Praha`) | Free-text location, e.g. Praha, Brno, Plzeň, resolved to a Jobs.cz locality automatically (search mode). |
| `field` | string | (none) | Professional field (obor) as Jobs.cz names it, e.g. IS/IT, Doprava. Widens the results rather than narrowing them (search mode). |
| `minSalary` | integer | `0` | Minimum gross monthly salary in CZK; `0` = no salary filter (search mode). |
| `employmentType` | string | `""` (all types) | `full`, `part`, `employment_contract`, `identification_number`, `agreement` or `internships` (search mode). |
| `education` | string | `""` (any) | `primary`, `high` or `uni` (search mode). |
| `arrangement` | string | `""` (any) | `partial-work-from-home`, `work-mostly-from-home` or `flexible-hours`; this is also where the remote/hybrid flag comes from (search mode). |
| `datePosted` | string | `""` (any date) | `24h`, `3d` or `7d`: only jobs published within that window (search mode). Jobs.cz offers no other date or sort options. |
| `urls` | array | one example per supported listing shape (see URL shapes below) | Jobs.cz listing, search or direct `/rpd/{id}/` URLs to crawl (url mode). |
| `maxItems` | integer | `20` | Stop after this many jobs across all searches/URLs. |
| `maxPages` | integer | `0` (unlimited) | Result pages to walk per search; `0` defers entirely to Max items. |
| `fetchDetails` | boolean | `false` | Open each job's detail page. Costs an extra detail-enrichment charge per delivered, actually-read row. |
| `resumeFromRunId` | string | (none) | Continue one interrupted run without re-returning or re-billing jobs it already collected. |
| `incrementalMode` | boolean | `false` | Recurring monitoring: return only NEW, UPDATED (and, once Emit expired jobs is also on, REAPPEARED/EXPIRED) jobs on later runs. |
| `stateKey` | string | (none) | Name or share the incremental-mode state; otherwise derived automatically from the search setup. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) jobs that have not changed since the last run (incremental mode). |
| `emitExpired` | boolean | `false` | Also return (and bill) jobs no longer found after a complete scan (incremental mode); also what makes a later REAPPEARED possible. |
| `ignoreFieldsForChanges` | array | `[]` | Incremental mode: field names (dot paths for nested fields) whose changes alone never make a job UPDATED. They stay in the output. `status` and `companyLogo` are always ignored. |
| `proxy` | object | `{ "useApifyProxy": true }` | Connection settings. The default works on paid plans; residential (optionally pinned to the Czech Republic) is supported but not required. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each job to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which job pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on jobs exported to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live posting.

```json
{
  "jobId": "3300000001",
  "jobUrl": "https://www.jobs.cz/rpd/3300000001/",
  "title": "Senior Python Engineer, Cloud Platform",
  "companyName": "Vzorová firma s.r.o.",
  "companyLogo": "https://img.jobs.cz/logo/3300000001.png",
  "location": "Praha 4, Hlavní město Praha",
  "addressLocality": "Praha",
  "addressRegion": "Hlavní město Praha",
  "postcode": "14000",
  "country": "Česká republika",
  "coordinates": { "latitude": 50.041, "longitude": 14.437 },
  "salary": { "currency": "CZK", "min": 90000, "max": 120000, "unit": "měsíc", "raw": "90 000 - 120 000 Kč / měsíc" },
  "salaryRaw": "90 000 - 120 000 Kč / měsíc",
  "employmentTypes": ["Práce na plný úvazek"],
  "contractType": "Pracovní smlouva",
  "contractDuration": "Na dobu neurčitou",
  "hoursPerWeek": 40,
  "requiredEducation": "Vysokoškolské / univerzitní",
  "requiredLanguages": ["Angličtina, úroveň Pokročilá"],
  "benefits": ["Stravenky", "Možnost občasné práce z domova", "Příspěvek na vzdělávání"],
  "arrangement": "remote",
  "remote": true,
  "suitableForGraduate": false,
  "suitableForDisabled": false,
  "suitableForPensioner": false,
  "suitableForMaternityLeave": false,
  "postedDate": "2026-06-01T09:00:00+02:00",
  "descriptionText": "Hledáme zkušeného vývojáře, který posílí náš tým...",
  "contactCompanyName": "Vzorová firma s.r.o.",
  "contactName": "Personální oddělení",
  "employerPhone": "+420 200 300 400",
  "employerEmail": "kariera@example.com",
  "companyAddress": "Vzorová 12, Praha 4, 140 00, Česká republika",
  "employerProfileUrl": "https://zetacompass.jobs.cz",
  "detailSource": "employer-site",
  "status": "Přidáno",
  "scrapedAt": "2026-06-12T08:00:00.000Z"
}
```

### Plan Requirement

This actor is pay per event: a small run-start fee, then a per-job charge for every job written to the dataset, plus an optional detail-enrichment charge only for jobs whose detail page was actually fetched and delivered. The **Pricing** tab on the actor page shows the current rates for each of these. The three charged events are the run start, each delivered job (dataset item) and the optional detail enrichment. The default Apify proxy connection works out of the box; Jobs.cz serves data over both datacenter and residential proxies, so residential is optional. If the selected connection is refused for your account, the run continues on a backup connection and says so in the log; it never reads pages without a proxy. If you enter your own proxy URLs, only those are used.

### FAQ

#### How much does it cost?

You pay a small run-start fee, then a per-job charge for every job written to the dataset. Turning on Fetch full job details adds a separate detail-enrichment charge, but only for jobs whose detail page was actually fetched and delivered: a job with no readable detail page, or one that incremental mode suppresses because nothing changed, is never charged that surcharge even though its detail page still had to be read to check. The Pricing tab on the actor page has the current rates.

#### Is it legal to scrape Jobs.cz?

This actor is an independent tool and is not affiliated with or endorsed by Jobs.cz, Alma Career, or LMC s.r.o. It reads only publicly available job-listing pages. You are responsible for how you use the data: follow Jobs.cz's terms of service and the laws that apply to you, and get legal advice if you plan commercial redistribution.

#### How do I get only new jobs on a schedule?

Turn on Incremental mode and schedule the run against the same search. The first run returns everything as NEW; later runs return only NEW and UPDATED jobs. REAPPEARED and EXPIRED are both off unless you also turn on Emit expired jobs, and even then EXPIRED only fires after a run that read every page of the search, so a job can only ever come back as REAPPEARED once a previous complete scan has already marked it EXPIRED.

#### Why did my run fail instead of returning an empty dataset?

If Jobs.cz refuses every request for a search page, or, for a single pasted job link, its detail page, the run stops with a clear error message instead of finishing with an empty dataset, and nothing is marked EXPIRED or removed from the incremental baseline. If some pages were read but not all, the run finishes with a partial-results warning: you get what was found, again with nothing marked EXPIRED. Re-run, ideally with the residential proxy group and country set to CZ.

If the selected connection is refused (for example on a free Apify plan, which does not include residential proxies), the log shows `The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable.` and, once pages load, `Served over the backup connection (the default connection was unavailable).` If no page can be read at all, the run fails with one of these messages:

- `The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid.`
- `No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Jobs.cz pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again.`
- `Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later.`

#### Does it return salary and GPS for every job?

No. Many Czech listings do not publish a salary; when the site shows one, it is parsed into `salary.min` / `salary.max` with its currency and period. GPS coordinates come exact for jobs hosted on an employer's own careers micro-site, and are geocoded best-effort from the address for jobs hosted on Jobs.cz itself, only when Fetch full job details is on; not every job publishes an address to geocode.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable as they are scraped.

### 🔗 Want more Jobs data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>💼 <a href="https://apify.com/abotapi/jobs-nhs-uk-scraper"><b>NHS UK</b></a><br>Scrape nhs.uk Jobs listings into a flat dataset. Extract 50+ fields, including pay band...</td><td>💼 <a href="https://apify.com/abotapi/jobsite-co-uk-scraper"><b>Jobsite UK</b></a><br>Collect UK job listings from Jobsite.co.uk at scale. Search by keyword, location...</td></tr>
<tr><td>💼 <a href="https://apify.com/abotapi/ziprecruiter-com-scraper"><b>ZipRecruiter Scraper</b></a><br>Scrape ZipRecruiter jobs with titles, companies, salaries, locations, benefits, apply...</td><td>💼 <a href="https://apify.com/abotapi/cwjobs-scraper"><b>Cwjobs UK</b></a><br>Scrape tech and IT jobs from CWJobs.co.uk into clean, structured data. Search by keyword...</td></tr>
<tr><td>💼 <a href="https://apify.com/abotapi/we-work-remotely-jobs-scraper"><b>We Work Remotely Scraper</b></a><br>Extract current remote job listings from weworkremotely.com. Use keyword search or WWR...</td><td>🏷️ <a href="https://apify.com/abotapi/finn-no-jobs-scraper"><b>FINN.no Jobs Scraper</b></a><br>Scrape active FINN.no job listings by search filters or URL. Extract 30+ fields including...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/jobs-cz-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

search = build a Jobs.cz search from keywords, location, field, salary and employment type. url = paste one or more Jobs.cz listing/search or direct position URLs and walk them forward.

## `keyword` (type: `string`):

Free-text search, e.g. "python", "programátor", "účetní" (Czech text works — Jobs.cz normalizes it). Leave blank to list everything.

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

Free-text location, e.g. "Praha", "Brno", "Plzeň". Matches Jobs.cz localities (regions, cities, districts). Leave blank for all of Czechia.

## `field` (type: `string`):

Professional field (obor) as Jobs.cz names it, e.g. "IS/IT", "Doprava", "Ekonomika a podnikové finance". The Actor looks the name up in the Jobs.cz field list for you. Note this ADDS every job in that field to the results alongside your keyword — Jobs.cz treats keyword and field as alternatives, not as a combined filter — so use it on its own to sweep a whole field. Leave blank for all fields.

## `minSalary` (type: `integer`):

Only jobs advertising at least this monthly gross salary. 0/empty = no salary filter.

## `employmentType` (type: `string`):

Filter by employment type (typ úvazku). Empty = all types. All six values narrow the results (measured live: on a 247-job baseline, full 239 · part 13 · internships 7 · employment contract 222 · trade licence 20 · agreement 7).

## `education` (type: `string`):

Filter by required education. Empty = all.

## `arrangement` (type: `string`):

Filter by work arrangement — this is where the remote/hybrid flag comes from. Empty = all.

## `datePosted` (type: `string`):

Only jobs published within this window (Jobs.cz date filter). Empty = any date. Narrows whatever keyword/field matched. Jobs.cz offers no other sort or date options.

## `urls` (type: `array`):

Full Jobs.cz URLs to crawl. Listing pages are walked forward page by page; direct /rpd/{id}/ URLs are scraped as single jobs. To build a search URL, run your query on jobs.cz and copy the address bar URL.

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

Stop after this many jobs across all searches/URLs. This is the run's cap.

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

How many result pages to walk per search (Jobs.cz shows about 20 to 30 results per page). Leave empty / 0 for unlimited, in which case the run stops at Max items instead.

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

COSTS EXTRA. Off by default. Turning it on adds one request per job and is billed as a "detail-enrichment" surcharge on top of the base result price. You are charged only for rows you actually receive whose detail page was really read, and never more than once per job. Off = SERP-card fields only (title, company, location, teaser).

## `proxy` (type: `object`):

Jobs.cz works over both the Apify datacenter and residential proxy groups. Leave the default for the cheapest runs; switch to Residential with country CZ for larger runs where a Czech exit improves reliability. If the selected connection is refused for your account, the run continues on a backup connection and says so in the log.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large crawl without returning or charging for jobs already collected there. For recurring daily monitoring of the same search, use Incremental mode below instead.

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

Turn on for daily/recurring monitoring. The first run returns all matching jobs as NEW. Later runs return only NEW, UPDATED, and REAPPEARED jobs, plus EXPIRED ones when you enable Emit expired. State is kept separately for each keyword/location/field/salary/employment-type/URL setup; use State key to name or share a campaign.

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

Optional. Name this monitoring campaign to keep its state stable, or to share state across differently-configured runs. Leave empty to derive a key automatically from the search setup — different searches then never mix state.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return jobs that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return jobs that were present in a previous run but are no longer found, marked EXPIRED. This returns — and bills — extra rows. Only produced once a run fully scanned the tracked search: never when Max items or Max pages capped it, never when Resume was used, and never when a search page could not be read, so a temporary block cannot mark your live jobs as gone.

## `ignoreFieldsForChanges` (type: `array`):

Field names that must NOT make a job count as UPDATED when only they change, e.g. teaser or benefits. Nested paths use dots (salary.raw). The ignored fields still appear in the output. The ad-promotion label (status) and the company logo URL are always ignored. Changing this list never causes a one-time wave of UPDATED rows.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion, Linear, Airtable, Apify.

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "keyword": "python",
  "location": "Praha",
  "minSalary": 0,
  "employmentType": "",
  "education": "",
  "arrangement": "",
  "datePosted": "",
  "urls": [
    "https://www.jobs.cz/prace/?q%5B%5D=python",
    "https://www.jobs.cz/prace/praha/",
    "https://www.jobs.cz/prace/programator/",
    "https://www.jobs.cz/prace/praha/programator/",
    "https://www.jobs.cz/brigady/",
    "https://www.jobs.cz/absolvent/",
    "https://www.jobs.cz/it/"
  ],
  "maxItems": 20,
  "maxPages": 0,
  "fetchDetails": false,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

One row per job position: identity, employer, location and GPS, structured salary, classification, suitability flags, dates, description and apply link. In incremental mode each row also carries changeType (NEW / UPDATED / REAPPEARED / EXPIRED), changedFields, firstSeenAt and lastSeenAt.

## `jobsDetail` (type: `string`):

Salary range, benefits, required education and languages, employer contact, GPS coordinates and the full description.

## `jobsChanges` (type: `string`):

Incremental mode: change type, changed fields and first/last seen dates per job.

## `output` (type: `string`):

Run summary: jobs returned, how many of them were charged the detail-enrichment surcharge, searches walked, whether any page could not be read, whether the incremental baseline was advanced, how many exits the run had to open, and — if detail enrichment had to be switched off mid-run because the positions had no readable detail page — why, and how many jobs were returned with search-page fields only.

# 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",
    "keyword": "python",
    "location": "Praha",
    "minSalary": 0,
    "employmentType": "",
    "education": "",
    "arrangement": "",
    "datePosted": "",
    "urls": [
        "https://www.jobs.cz/prace/?q%5B%5D=python",
        "https://www.jobs.cz/prace/praha/",
        "https://www.jobs.cz/prace/programator/",
        "https://www.jobs.cz/prace/praha/programator/",
        "https://www.jobs.cz/brigady/",
        "https://www.jobs.cz/absolvent/",
        "https://www.jobs.cz/it/"
    ],
    "maxItems": 20,
    "maxPages": 0,
    "fetchDetails": false,
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/jobs-cz-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 = {
    "mode": "search",
    "keyword": "python",
    "location": "Praha",
    "minSalary": 0,
    "employmentType": "",
    "education": "",
    "arrangement": "",
    "datePosted": "",
    "urls": [
        "https://www.jobs.cz/prace/?q%5B%5D=python",
        "https://www.jobs.cz/prace/praha/",
        "https://www.jobs.cz/prace/programator/",
        "https://www.jobs.cz/prace/praha/programator/",
        "https://www.jobs.cz/brigady/",
        "https://www.jobs.cz/absolvent/",
        "https://www.jobs.cz/it/",
    ],
    "maxItems": 20,
    "maxPages": 0,
    "fetchDetails": False,
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/jobs-cz-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 '{
  "mode": "search",
  "keyword": "python",
  "location": "Praha",
  "minSalary": 0,
  "employmentType": "",
  "education": "",
  "arrangement": "",
  "datePosted": "",
  "urls": [
    "https://www.jobs.cz/prace/?q%5B%5D=python",
    "https://www.jobs.cz/prace/praha/",
    "https://www.jobs.cz/prace/programator/",
    "https://www.jobs.cz/prace/praha/programator/",
    "https://www.jobs.cz/brigady/",
    "https://www.jobs.cz/absolvent/",
    "https://www.jobs.cz/it/"
  ],
  "maxItems": 20,
  "maxPages": 0,
  "fetchDetails": false,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/jobs-cz-scraper --silent --output-dataset

```

## MCP server setup

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