# Prace.cz Scraper: Czech Jobs, Salaries & Companies (`tsutseisoftware/prace-cz-scraper`) Actor

Scrape Prace.cz jobs with salary, company, region and remote work. Track new and removed jobs, get a salary summary and optional ARES company data. Unofficial.

- **URL**: https://apify.com/tsutseisoftware/prace-cz-scraper.md
- **Developed by:** [Tomáš Buchta](https://apify.com/tsutseisoftware) (community)
- **Categories:** Jobs
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 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

## Prace.cz Scraper: Czech Jobs, Salaries & Companies

Get job listings from **Prace.cz**, one of the largest Czech job boards, as clean structured data. Search by **keywords**, **location**, **regions** and the **website's own filters** (employment type, contract type, education, salary, remote work, languages, employer type, posting date), or paste any Prace.cz search URL. For each job you get the title, company, place of work with its **region (kraj) and district (okres)**, **salary when the employer shows it** (with monthly and hourly equivalents), employment type, remote/hybrid flag, posting and expiry dates and categories. Optionally the actor opens each job page and adds the **description** (with e-mails and phone numbers removed), benefits, required education and languages, contract type and whether the advertiser is a recruitment agency.

Built for **watching the job market**: schedule it daily in monitoring mode and you get, and pay for, only jobs that are new or changed, plus, if you want, the jobs that **disappeared** since the last run and how many days they were online. Every run also saves a free **market summary** (salary median and quartiles by region and category, top employers, remote share). Optional **company data from ARES**, the public Czech business register (company ID, legal form, size, main activity), for an extra charge.

### Why use this Prace.cz scraper?

- 🔎 **The website's filters, applied by the website.** Employment type (full-time, part-time, brigáda, internship), contract type (HPP, IČO, DPP/DPČ), education, minimum salary, work from home, language skills, "suitable for" groups, direct employers vs. agencies and posting date (24 h / 3 days / 7 days) are sent to Prace.cz in the URL, so the results match what you see on the website and filtered-out jobs cost nothing.
- 🗺️ **Region and district for almost every job**, worked out from the place of work with the official list of Czech municipalities (ČSÚ / RÚIAN), without extra requests. Filter by one or more **regions**.
- 💰 **Salaries you can compute with.** `salaryMin`, `salaryMax`, currency and period as numbers, plus monthly and hourly equivalents. Only salaries shown on the website are returned; when an employer hides the salary, the actor returns `null`.
- 📊 **Free market summary** after every run (`SUMMARY` record): median and quartiles of monthly salary overall, by region and by category, top 20 employers, share of remote/hybrid/onsite jobs, agencies and employment types.
- 📬 **Monitoring mode:** a daily feed of new and changed jobs (`changeType`), optionally with **removed jobs** (`removedAt`, `daysOnline`) to see how fast positions are filled.
- 🏢 **Company data from ARES** (optional, extra charge): IČO, legal form, registered office, employee count range, main activity (CZ-NACE), founding date.
- 🔒 **Privacy first.** Recruiter contacts only if you turn them on (off by default, see [Recruiter contacts and privacy](#recruiter-contacts-and-privacy-optional-personal-data)); e-mails and phone numbers inside descriptions are replaced with `[e-mail removed]` / `[phone removed]`. Employers that are private persons are never named.
- 🚀 **Optional fast mode:** up to 120 requests per minute instead of 30, with automatic slow-down when the website asks (small extra charge).
- ⚡ **Fast and light:** one request gives 40 jobs. No browser, no proxy needed.

### What data can you get?

| Field | Example |
|---|---|
| Title, company | Hledá se příjemný kolega/kolegyně do našeho týmu - Olomouc, Aurum Class s.r.o. |
| Location, region, district | Olomouc, Olomoucký kraj, Olomouc |
| Workplace (with **Add description and details**) | street, house number 371/1, city part Olomouc, postal code, GPS 49.594276, 17.249899 |
| Salary | 30 000 – 60 000 CZK per month (monthly 30 000 – 60 000, hourly 187.5 – 375) |
| Employment type, work arrangement | fullTime, onsite |
| Posted, valid through | 2026-10-06, 2026-11-06 |
| Categories, professions | Prodej a obchod, Obchodní zástupce |
| Suitable for | graduates, noExperience |
| Agency (with details or the Employer type filter) | false |
| With **Add description and details** | description, benefits, education, languages, contract types, workplace address and GPS |
| With **Add company data from ARES** | IČO 14205823, Společnost s ručením omezeným, 1-5 employees, 73110 Činnosti reklamních agentur, founded 2022-01-31 |
| With **Monitoring: include removed jobs** | changeType `removed` (removedAt, firstSeenAt, lastSeenAt, daysOnline) and `reappeared` |

See the **Output** tab for all fields. The dataset has views for **Overview**, **Salaries**, **Companies**, **Work arrangement**, **Details**, **Monitoring** and **Removed jobs**.

### How to scrape Prace.cz

1. Enter **Search queries** (e.g. `python`, `skladník`) and a **Location** (e.g. `praha`, `brno`, `jihomoravsky-kraj`), several **Locations** or **Regions**, or paste Prace.cz URLs into **Prace.cz URLs**: set your search and filters on the website and copy the URL, e.g. `https://www.prace.cz/nabidky/brno/?keywords[]=skladník`.
2. Optionally choose **Website filters** and **Filters**, set **Max jobs**, and turn on **Add description and details** or **Add company data from ARES** if you need them.
3. Click **Start** and download the results as JSON, CSV or Excel, or use the API. The market summary is in the **Storage → Key-value store** tab as `SUMMARY` (and in the **Output** tab).

### Use cases

#### New jobs by e-mail, Slack or Google Sheets every morning

Save this input as a task, add a **Schedule** (e.g. daily at 7:00) and connect an **integration** in Apify Console (e-mail, Slack, Google Sheets, Make, Zapier or a webhook) that sends the dataset after each run. Thanks to monitoring mode, each run returns only jobs that are new or changed, so the message contains just the news.

```json
{
  "searchQueries": ["python", "django"],
  "location": "praha",
  "maxItems": 0,
  "onlyNewOrChanged": true,
  "monitoringKey": "python-praha"
}
```

#### Salary research for a region and category

Read all jobs with a salary in two regions and open the free `SUMMARY` record: median and quartiles of the monthly salary by region and by category, and the share of jobs that show a salary. The **Salaries** view shows the individual jobs.

```json
{
  "searchQueries": ["účetní", "mzdová účetní"],
  "regions": ["jihomoravsky-kraj", "zlinsky-kraj"],
  "onlyWithSalary": true,
  "maxItems": 500
}
```

#### Employer insights: who is hiring, how big they are and what they do

Job market analysis with company context: add **company data from ARES** (extra charge, see below) to see the hiring employers' legal form, size (employee count range) and main activity, e.g. to compare which industries and company sizes hire in a region. Direct employers only, so agencies are left out.

```json
{
  "location": "brno",
  "jobTypes": ["fullTime"],
  "employerType": "directEmployer",
  "includeCompanyInfo": true,
  "maxItems": 200
}
```

#### Removed jobs and time to fill

Run the same search daily with removed jobs turned on. Jobs that are no longer on the website come back as rows with `changeType: "removed"`, `removedAt` and `daysOnline` (days from posting to the last run that still saw the job), a simple measure of how fast positions are filled. Keep **Max jobs** at 0 (no limit) so the whole search is read; see *Monitoring mode explained*.

```json
{
  "searchQueries": ["svářeč"],
  "regions": ["moravskoslezsky-kraj"],
  "maxItems": 0,
  "onlyNewOrChanged": true,
  "includeRemovedJobs": true,
  "monitoringKey": "svarec-msk"
}
```

#### Remote or hybrid jobs from direct employers with at least 50 000 CZK

```json
{
  "searchQueries": ["controller"],
  "workArrangement": ["hybrid", "remote"],
  "employerType": "directEmployer",
  "minSalary": 50000,
  "onlyWithSalary": true,
  "maxItems": 200
}
```

#### Side jobs (brigády) on a work agreement in South Moravia posted in the last 3 days

```json
{
  "location": "jihomoravsky-kraj",
  "jobTypes": ["temporary"],
  "contractTypes": ["workAgreement"],
  "postedWithinDays": 3,
  "maxItems": 100
}
```

#### Website filters (applied by Prace.cz)

| Input | What it does |
|---|---|
| **Employment type** | Full-time, part-time, temporary / side job (brigáda), internship. |
| **Contract type** | Employment contract, self-employed (IČO), work agreement (DPP/DPČ). |
| **Education** | Elementary, apprenticeship, high school with diploma, university. |
| **Salary from (CZK per month)** | Minimum monthly salary, e.g. 40000. The website also matches jobs whose salary is hidden on the page; these come without a salary. |
| **Work from home / remote** | On site, hybrid, remote. |
| **Language skills** | Czech, English, German, French, Russian, Polish, Italian, Spanish. |
| **Suitable for** | No experience, graduates, seniors, parents on leave, people with disabilities, refugees. |
| **Employer type** | Direct employers only (exclude agencies) or agencies only. |
| **Posted in the last N days** | 1, 3 and 7 days are applied by the website; other values are checked by the actor on the posting date. |
| **Regions** | Without a Location and without Prace.cz URLs, each search runs once per region on the website (`/nabidky/<region>/`). |
| **Locations** | Several places, one search each (e.g. `praha`, `brno`, `ostrava`), at most 20; can be combined with **Location**. |

The website filters are also added to your **Prace.cz URLs**, unless the URL already sets the same filter. **Location** and **Locations** apply to **Search queries** and website filters, not to pasted URLs. One run makes at most 280 searches (search queries × locations or regions).

#### Filters (applied by the actor, you don't pay for filtered-out jobs)

| Input | Example |
|---|---|
| **Categories** | `Prodej a obchod`, `Administrativa` (the website has no URL filter for most categories, so the actor checks them on each job; combine with a search or location) |
| **Regions** (with a Location, Locations or Prace.cz URLs) | the region of each job's place of work must be one of the chosen regions |
| **Only jobs with a salary** | skip jobs without a visible salary |
| **Title must contain**, **Title must not contain** | `junior`, not `senior` |
| **Company**, **Exclude companies** | `Škoda`, not `Manpower` |

Safety limits in **Advanced**: **Max pages per search** (40 jobs per page). **Max jobs per search** limits each query, region or URL separately.

#### Region and district

`region` (kraj) and `district` (okres) come from the place of work shown in the list (`location`), matched against the official list of Czech municipalities (ČSÚ code lists and RÚIAN links, embedded in the actor, no extra requests). Places like "Praha-Smíchov" or "Brno-Černovice" are parts of a municipality and count as Prague (`district: "Praha"`) or Brno (`Brno-město`). When a name belongs to several municipalities and the actor can't tell which one is meant (e.g. "Staré Město"), or the place is "Česko" or abroad, both fields are `null`. For jobs with several places, `region` and `district` describe the first place with a known region and `regions` lists the regions of all places. When the website itself limited the search to a region (**Regions**, or a region as **Location**), a place the list can't decide gets that region. With **Add description and details**, region and district come from the job page itself when it gives them, together with the workplace's house number, city part (`cityPart`) and GPS (`gpsLat`, `gpsLon`) of the first workplace; these are never returned for a private employer. In our samples about 98 % of places got a region.

#### Monitoring mode explained

With **Monitoring: only new or changed jobs** each run compares jobs with the previous run that used the same **Monitoring: key**. A job is returned as `changeType: "new"` when it wasn't seen before, or `"updated"` when its title, company, salary, place or type of work changed (`changedFields` says which of title, salary and work arrangement). Jobs not seen for 90 days are forgotten. Use one key per watched search, and keep the same search and filters for that key.

**Monitoring: include removed jobs** adds jobs from the previous run that the current run no longer finds, with their last known title, company, place, region, salary and URL, `changeType: "removed"`, `firstSeenAt`, `lastSeenAt`, `removedAt` and `daysOnline`. Each removed job is reported once; if it comes back to the website later, it is returned once as `changeType: "reappeared"` (with `removedAt` of the earlier report). A job counts as removed when it is no longer in your search on the website: usually it expired or was taken down, but it can also mean the employer changed it so that the **website filters** no longer match it (e.g. a lower salary with **Salary from**). The actor's own **Filters** never make a job "removed": a job that is still on the website but filtered out by them is not reported. To avoid false alarms, removed jobs are checked **only when the run read the whole search**: if it stopped at **Max jobs**, **Max jobs per search** or **Max pages per search**, at the 10 000-job limit of the website, at your maximum cost per run, or some pages could not be read or the site rejected requests, nothing is marked as removed and the status message says why. With **Posted in the last N days**, jobs that simply got older than N days are not reported as removed. **Removed rows are normal dataset rows and are charged like any other job.**

#### Market summary (SUMMARY)

Every successful run writes a free JSON record `SUMMARY` to the run's default key-value store (link in the **Output** tab, or `GET https://api.apify.com/v2/key-value-stores/<storeId>/records/SUMMARY`). It covers all jobs the run found that match your search and filters, each job once; in monitoring mode also the unchanged jobs that were not saved again.

| Key | Content |
|---|---|
| `counts` | jobs matched and saved; in monitoring mode new, updated, unchanged, removed and reappeared; jobs with company data |
| `salary` | number and share of jobs with a salary; monthly CZK median and quartiles (`p25`, `median`, `p75`) overall, `byRegion` and `byCategory` (groups with at least 5 jobs) |
| `topCompanies` | top 20 employers by number of jobs |
| `workArrangement`, `employmentTypes`, `regions` | number and share of jobs per value |
| `agencies` | share of agencies among jobs where it is known (with details or the Employer type filter), otherwise `null` |

The monthly figure of a job is the middle of its salary range; hourly and daily salaries are converted at 160 hours per month. "Yearly" salaries under 120 000 CZK are monthly amounts with a wrong period on the website and are left out of the statistics (the job itself still has them). The summary contains no personal data: companies only, no recruiter data and no descriptions.

#### Company data from ARES (optional, extra charge)

With **Add company data from ARES (extra charge)** the actor looks up each employer in **ARES** (Administrativní registr ekonomických subjektů), the public register of economic subjects run by the **Ministry of Finance of the Czech Republic** (ares.gov.cz), and adds:

| Field | Example | Note |
|---|---|---|
| `companyIco` | `14205823` | company ID (IČO) |
| `companyLegalForm` | `Společnost s ručením omezeným` | converted from the ARES code to its name |
| `companyAddress` | `28. října 2020/231, Mariánské Hory, 70900 Ostrava` | registered office, not the workplace |
| `companyEmployeeRange` | `1-5` | ČSÚ employee count category converted to a range (`0` = no employees, `10000+`); `null` when not stated |
| `companyNace` | `73110 Činnosti reklamních agentur` | main activity, CZ-NACE code and name (ČSÚ) |
| `companyFoundedAt` | `2022-01-31` | date of establishment |
| `companyInfoSource`, `companyInfoMatch`, `companyInfoRetrievedAt` | `ARES`, `exact`, `2026-10-13T10:00:00Z` | where and when the data come from |

How it works and what to expect:

- **Off by default.** Without this option the actor never contacts ARES.
- **Matching by the company name.** ARES is searched by the employer name from the job, and data are returned only for an **unambiguous match**: the same name, including the legal form (case, diacritics and punctuation don't matter, "spol. s r.o." = "s.r.o."), and exactly one active company. Brand names without a legal form ("IKEA Česká republika", "DATART") or several companies of the same name get no data. In our live samples 38 of 40 jobs in Brno and 25 of 30 sales jobs in Prague got company data (31 of 33 and 16 of 21 distinct employers); 5 company IDs checked by hand were all correct.
- **The advertiser, not its client.** The data describe the company in `company`. Jobs known to be posted by a recruitment agency (`isAgency: true`, known with details or the Employer type filter) are not looked up, because the agency is not the employer. When `isAgency` is unknown, an agency's job gets the agency's data.
- **No private persons.** Employers whose name looks like a person are never sent to ARES. When ARES says the employer is a natural person (a sole trader, legal forms 101–108, 424, 425), nothing from ARES is returned and the job's company name and address are removed as well.
- **ARES data are informative only**, not an official record, and can be up to about two weeks behind the source registers. Each name is looked up once per run, one request at a time (at most about 60 per minute), always directly. Results are kept for 30 days in a key-value store `prace-cz-ares-cache` in your account, so repeated runs don't ask again (sole traders are never stored). If ARES is unavailable or limits requests, jobs are saved without company data and the status message says so.
- **Price:** ARES is a free public service; the extra charge pays for the matching and integration. You pay the **Company info** event only for saved jobs that got company data. Jobs without a match, agencies, private persons and failed lookups cost nothing extra. Your **maximum cost per run** is respected: when it couldn't cover the next job with company data, the actor stops adding company data and keeps saving jobs without it while the budget allows.
- About 1–2 seconds per new company name, so a run with company data takes longer.

This actor is not affiliated with or endorsed by the Ministry of Finance of the Czech Republic.

#### Recruiter contacts and privacy (optional, personal data)

**Include recruiter contacts (personal data)** adds `recruiterName`, `recruiterPhone` and `recruiterEmail` to each job. Off by default; it needs **Add description and details**.

- **What and from where.** Only what the job page on Prace.cz shows in its "Kontaktní údaje" (contact details) block: the contact person's name and the phone number of its phone link (as `+420…`). An e-mail only when the block shows one, which the site almost never does, so `recruiterEmail` is nearly always `null`. Not the block's address or company. Nothing is guessed, derived or looked up elsewhere (no e-mail finding, no other websites), and e-mails and phone numbers inside descriptions stay removed.
- **When not.** Never for employers that look like a private person (also when ARES says so), for jobs on an employer's career site, for jobs whose page couldn't be read, or for people who asked to be left out. There is no list of recruiters, no count per recruiter and no contact in the `SUMMARY` record.
- **Nothing is kept by the actor.** Contacts go only into your run's dataset: never into the monitoring state, the ARES cache, the `SUMMARY` record or the log. A change of the contact person doesn't count as a change in monitoring mode. No extra charge for contacts.
- **You are the data controller (GDPR).** You need your own legal basis, typically a legitimate interest, and only for **contacting the person about that specific job ad** (e.g. as an applicant or on an applicant's behalf, or to clarify the offer).
- **No marketing.** In Czechia, commercial e-mails and SMS without prior consent are prohibited (Act No. 480/2004 Coll., § 7), also to business addresses, and marketing calls need consent (Act No. 127/2005 Coll., § 96), also to business numbers. Don't use the contacts for marketing or commercial offers, don't resell them and don't build contact databases from them.
- **Inform and delete.** Tell the person who you are, why you contact them, where you got the contact (Prace.cz job ad) and that they may object (Art. 14 GDPR), at the latest when you first contact them, otherwise within one month. Respect objections. Delete a dataset with contacts as soon as you no longer need it: named Apify storages are never deleted automatically.
- **To be left out of future results** (recruiters): open an issue in the **Issues** tab of this actor in Apify Store, or write to Apify's privacy contact privacy@apify.com. We remove the person from all future runs; data already in a user's dataset are that user's responsibility, so please contact them or the employer too.

#### Fast mode (optional, small extra charge)

**Fast mode** reads Prace.cz up to 4× faster: at most **120 requests per minute and 4 at a time** instead of 30 per minute and 2 at a time. It stays polite: the moment the website answers "too many requests", "service unavailable" or slows down (or shows a challenge page), the run switches back to the normal speed for the rest of the run, it waits when the website sends `Retry-After` (and stops with what it has saved when asked to wait more than 2 minutes), and it never changes IP address to get around a limit; with a proxy, the whole run uses one proxy session. The status message says when and why the run slowed down.

- **Price:** the **Fast mode** event, **$0.01 per 1 000 jobs** ($0.00001 per job), only for jobs saved while the fast speed was on. Jobs saved after the switch back to the normal speed have no fast-mode charge. It respects your maximum cost per run: when the budget doesn't cover the charge, the run continues at the normal speed.
- **Limits stay the same:** Max jobs, Max pages per search and the website's per-search limit. A listing page has 40 jobs, so the speed matters most with **Add description and details** (one request per job).
- **Measured** (2026-10-14, 256 MB, no proxy, 110 jobs with description and details from Praha): 78 requests in 40 seconds in fast mode, about 4× faster than at the normal speed (about 2 minutes 40 seconds).
- Please don't run several fast-mode runs at the same time.

### How much does it cost to scrape Prace.cz?

Pay per result, see the **Pricing** tab for the current prices and discounts for higher Apify plans:

| What | Price |
|---|---|
| Job (every saved row, including removed jobs in monitoring mode) | **$0.95 per 1 000 jobs** ($0.90 Bronze, $0.85 Silver, $0.80 Gold and higher Apify plans) |
| **+ Company info** (only jobs that got company data from ARES) | **+$1.00 per 1 000 jobs** |
| **+ Fast mode** (only jobs saved while the optional fast mode was on) | **+$0.01 per 1 000 jobs** |
| Recruiter contacts (optional) | no extra charge |
| Filtered-out jobs, unchanged jobs in monitoring mode, the `SUMMARY` record | free |

Platform usage is included; Prace.cz works without a proxy. The actor makes at most 2 requests at a time and 30 per minute, to keep the load on the website low (in the optional fast mode at most 4 at a time and 120 per minute, slowing down automatically when the website asks). Typical runs:

| Scenario | Requests to Prace.cz | Typical time |
|---|---|---|
| 20 jobs from a search | 1 | a few seconds |
| 1 000 jobs from searches | ~25 | ~1 minute |
| 100 jobs with description and details | up to ~103 | ~3–4 minutes |
| 40 jobs with company data | 1 (+ ~65 to ARES) | ~1 minute |

### Input example

```json
{
  "searchQueries": ["python"],
  "location": "praha",
  "maxItems": 100,
  "jobTypes": ["fullTime"],
  "includeDetails": false,
  "includeCompanyInfo": false
}
```

### Output example

```json
{
  "id": "prace:0bbd7aac-5cdc-48a3-9f1e-19aef9346d9c",
  "source": "prace.cz",
  "url": "https://www.prace.cz/nabidka/0bbd7aac-5cdc-48a3-9f1e-19aef9346d9c/",
  "jobId": "0bbd7aac-5cdc-48a3-9f1e-19aef9346d9c",
  "recruitmentId": "2001465340",
  "title": "Hledá se příjemný kolega/kolegyně do našeho týmu - Olomouc",
  "company": "Aurum Class s.r.o.",
  "companyId": "6b495q",
  "companyLogoUrl": null,
  "isAgency": false,
  "location": "Olomouc",
  "locations": ["Olomouc"],
  "region": "Olomoucký kraj",
  "district": "Olomouc",
  "regions": ["Olomoucký kraj"],
  "streetAddress": "Horní náměstí",
  "postalCode": "77900",
  "workArrangement": "onsite",
  "remote": false,
  "salaryMin": 30000,
  "salaryMax": 60000,
  "salaryCurrency": "CZK",
  "salaryPeriod": "month",
  "salaryMonthlyMin": 30000,
  "salaryMonthlyMax": 60000,
  "salaryHourlyMin": 187.5,
  "salaryHourlyMax": 375.0,
  "employmentType": ["fullTime"],
  "contractTypes": ["employee", "freelance"],
  "education": "odborné vyučení bez maturity",
  "languagesRequired": ["čeština (výborná)"],
  "language": "cs",
  "suitableFor": ["graduates", "noExperience"],
  "categories": ["Bankovnictví a finanční služby", "Marketing", "Prodej a obchod"],
  "professions": ["Finanční poradce", "Obchodní zástupce"],
  "benefits": ["Bonusy/prémie", "Provize z prodeje", "Firemní akce"],
  "postedAt": "2026-10-06T09:41:50Z",
  "updatedAt": "2026-10-13T07:37:17Z",
  "validThrough": "2026-11-06T22:59:59Z",
  "isPromoted": false,
  "hasCareerPage": false,
  "careerPageUrl": null,
  "atmoskopUrl": null,
  "atmoskopReviewCount": null,
  "companyIco": "14205823",
  "companyLegalForm": "Společnost s ručením omezeným",
  "companyAddress": "28. října 2020/231, Mariánské Hory, 70900 Ostrava",
  "companyEmployeeRange": "1-5",
  "companyNace": "73110 Činnosti reklamních agentur",
  "companyFoundedAt": "2022-01-31",
  "companyInfoSource": "ARES",
  "companyInfoMatch": "exact",
  "companyInfoRetrievedAt": "2026-10-13T10:00:05Z",
  "description": "Koho hledáme: Ambiciózní lidi (m/ž, na věku nám nezáleží), kteří chtějí nastartovat svou kariéru v oblasti obchodu…",
  "detailScraped": true,
  "changeType": null,
  "firstSeenAt": null,
  "changedFields": null,
  "lastSeenAt": null,
  "removedAt": null,
  "daysOnline": null,
  "sourceUrl": "https://www.prace.cz/nabidky/olomouc/?keywords%5B%5D=obchodn%C3%AD",
  "scrapedAt": "2026-10-13T10:00:00Z"
}
```

### Limitations

- **Salary:** shown only when the employer publishes it on the website. In our samples that was about 15 % of IT jobs in Prague, about half of warehouse jobs in Brno and about 60 % across all jobs. Hidden salaries are never returned.
- **Jobs on employer career sites:** roughly a third to a half of the jobs are published on the employer's own career site (`hasCareerPage: true`). For them you get the listing data (title, company, place, salary if shown, dates, categories) but no description, benefits or education; the actor doesn't read career sites. A job URL of such a job given as input is skipped with a warning.
- **At most 10 000 jobs per search:** Prace.cz shows at most 250 pages of 40 jobs for one search. The actor warns when a search has more; split it by location, regions or keywords.
- **Categories** are checked by the actor (the website has no URL filter for most of them), so a category filter alone reads more pages than the website filters.
- **Location** must exist on the website (as in its URLs, e.g. `praha`, `brno`, `jihomoravsky-kraj`, `zahranici`); a misspelled location stops the run with a clear message.
- **Region and district** are `null` for about 2 % of places (names shared by several municipalities, "Česko", abroad). With **Regions** and a Location or Prace.cz URLs, such jobs are skipped.
- **Company data** need an exact name match, so brand names without a legal form stay without them; agencies' jobs get the agency's data unless `isAgency` is known. See *Company data from ARES*.
- **Removed jobs** are checked only after a run that read the whole search; see *Monitoring mode explained*.
- **Descriptions** are written by employers and may mention a person's name (e.g. "contact Jana"); e-mails and phone numbers are removed, names in free text are not. **Description length limit** shortens the text if you only need a snippet. Descriptions can be protected by copyright; use them for analysis, not for republishing.
- **Employer names that look like a private person** (e.g. a self-employed courier) are returned as `null`, together with their address, GPS and profile. A name counts as a person when it has the shape of a personal name and one of its words is a common given name (Czech, Slovak, Ukrainian or Vietnamese, from Wikidata), or it has an academic title; an Atmoskop profile of a company with a legal form and the same name marks the employer as a company. In our sample of 1 258 jobs (737 employer names) from Prace.cz and Jobs.cz, no company was hidden (before this check 1.7 % of jobs, 10 company names, e.g. "Škoda Transportation", "Richter Gedeon"). A brand name made of a given name (e.g. "Hugo Boss") is still hidden when there is no such Atmoskop profile for it.
- **Job URLs given as input** have a few listing-only fields empty (`workArrangement`, `updatedAt`, `language`; `validThrough` comes from the job page). If a job page doesn't list categories, **Categories** is not checked for it (the job is kept and the status message says so).
- If the website rejects a request, a rejected page is asked once more after 30 seconds from the same IP address and session (never from another IP), which recovers short-lived rejections. If it rejects a few requests in a row even so, the run stops and says so. Jobs whose page couldn't be read are returned with the listing data (`detailScraped: false`).
- This is not a copy of the Prace.cz database: it returns the jobs matching your search and filters, up to **Max jobs**.

### For AI agents and API users

- **Minimal input:** `{"searchQueries": ["python"], "location": "praha", "maxItems": 20}`.
- One row = one job = one charge. `includeDetails: true` adds 1 request per job (except career-site jobs), same price per job. `includeCompanyInfo: true` adds the **Company info** charge only for rows with `companyInfoSource: "ARES"`.
- 40 jobs per listing page. `maxItems` counts saved jobs, `maxItemsPerStartUrl` limits each query, region or URL.
- Enum values: `employmentType` ⊂ `fullTime`, `partTime`, `temporary`, `internship`; `workArrangement` ∈ `onsite`, `hybrid`, `remote`; `salaryPeriod` ∈ `hour`, `day`, `week`, `month`, `year`; `changeType` ∈ `new`, `updated`, `removed`; `regions` input ⊂ `praha`, `stredocesky-kraj`, `jihocesky-kraj`, `plzensky-kraj`, `karlovarsky-kraj`, `ustecky-kraj`, `liberecky-kraj`, `kralovehradecky-kraj`, `pardubicky-kraj`, `kraj-vysocina`, `jihomoravsky-kraj`, `olomoucky-kraj`, `zlinsky-kraj`, `moravskoslezsky-kraj`.
- Monitoring state is kept per `monitoringKey` between runs of the same user. The market summary is the `SUMMARY` record of the run's default key-value store.

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("<username>/prace-cz-scraper").call(run_input={
    "searchQueries": ["python"],
    "location": "praha",
    "maxItems": 100,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], job["company"], job["region"], job["salaryMin"], job["url"])
summary = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("SUMMARY")["value"]
print(summary["salary"]["monthlyCzk"])
```

#### JavaScript

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('<username>/prace-cz-scraper').call({
    searchQueries: ['python'], location: 'praha', maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((j) => `${j.title} (${j.company}, ${j.region})`));
```

You can also connect the actor to Make, Zapier, n8n, Google Sheets, Slack, e-mail or webhooks and schedule it in Apify Console.

### Troubleshooting

| Status message | What to do |
|---|---|
| `Skipping unsupported URL` | Use a Prace.cz search URL (`https://www.prace.cz/nabidky/...`) or a job URL (`https://www.prace.cz/nabidka/<id>/`). |
| `The search page was not found (HTTP 404)` | The location doesn't exist on the website. Use the form from the website URL, e.g. `praha`, `brno`, `jihomoravsky-kraj`. |
| `This input makes N searches` | Use fewer locations, regions or search queries (at most 280 searches per run). |
| `At most 20 locations per run` | Split the locations into several runs or use **Regions**. |
| `… start URL could not be read` | The job has expired, it is on the employer's career site, or the URL is wrong. Open it in your browser. |
| `Prace.cz is rejecting requests` | Try again later. |
| `… listing pages could not be read, so the results may be incomplete` | The website didn't answer some pages; run it again later for complete results. |
| `stopped reading N searches after 3 listing pages in a row could not be read` | The website showed something other than job lists for that search; the rest of it was skipped. Try again later. |
| `the site shows at most 10000 jobs per search` | Split the search by location, regions or keywords. |
| `Saved 0 jobs (new or changed since last run…)` | Nothing changed since the previous monitoring run; that's expected. |
| `removed jobs were not checked because the run did not read the whole search (…)` | Set **Max jobs** to 0 (or high enough), remove **Max jobs per search**, raise **Max pages per search**, or try again if pages could not be read. |
| `"Monitoring: include removed jobs" works only together with …` | Turn on **Monitoring: only new or changed jobs**. |
| `company info unavailable for the rest of the run: ARES rate limit` | ARES limited requests; the jobs were saved without company data and without the extra charge. Try again later. |
| `stopped adding company info at your maximum cost per run` | Raise the maximum cost per run, or lower **Max jobs**. |

### More Czech job scrapers

The same output format for the other big Czech job board (many jobs are published on both):

- [Jobs.cz Scraper](https://apify.com/tsutseisoftware/jobs-cz-scraper): Czech jobs with salary, region, requirements, monitoring and optional ARES company data

### FAQ

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

The actor reads only publicly available job listings, without logging in. It reads recruiters' contact details only if you turn on **Include recruiter contacts (personal data)** (off by default, and then you are responsible for using them lawfully, see [Recruiter contacts and privacy](#recruiter-contacts-and-privacy-optional-personal-data)), and it removes e-mails and phone numbers from descriptions; employer names that look like a private person are not returned. It makes 30 requests per minute by default, at most 120 in fast mode, and it slows down when the website asks; it never changes IP address to get around a limit and never solves captchas or other challenges. Note that the terms of use of Alma Career, the operator of Prace.cz, prohibit automated reading and copying of the website's content and further automated processing of its data. You are responsible for making sure your use of the data complies with those terms and with the law (including data protection and database rights). If unsure, consult a lawyer.

#### Where do the company data come from, and can I rely on them?

From ARES, the public register of economic subjects of the Ministry of Finance of the Czech Republic, which publishes them as open data. They have an informative character only (not an official record) and can be up to about two weeks behind the source registers. Sole traders and other natural persons are deliberately left out.

#### How are region and district determined?

From the place of work in the job list, using the official list of Czech municipalities (ČSÚ / RÚIAN open data) built into the actor. No extra requests. See *Region and district*.

#### Do I need a proxy?

No. Prace.cz works without a proxy, including from Apify's servers. You can still set Apify Proxy or your own proxies in **Proxy configuration** if you prefer. ARES is always contacted directly, never through a proxy.

#### Does it scrape Jobs.cz too?

No, only Prace.cz. Many jobs are published on both websites.

#### Can I get recruiters' e-mails or phone numbers?

Only what the job page shows, and only if you turn on **Include recruiter contacts (personal data)** together with **Add description and details**: the contact person's name and phone number. Prace.cz almost never shows a recruiter's e-mail, so `recruiterEmail` is nearly always empty, and the actor never guesses or looks up e-mails. These are personal data: use them only to contact the person about that specific job, never for marketing (see [Recruiter contacts and privacy](#recruiter-contacts-and-privacy-optional-personal-data)). Existing runs are not affected: the option is off by default.

#### Can I use it through the API?

Yes. See the Python and JavaScript examples above, or the **API** tab.

#### Something doesn't work

Open an issue in the **Issues** tab and include the input you used.

This is an unofficial tool, not affiliated with or endorsed by Alma Career Czechia s.r.o., the operator of Prace.cz, or by the Ministry of Finance of the Czech Republic. It is also known as a Prace.cz API, Prace.cz job scraper, "nabídky práce Prace.cz" or "mzdy podle krajů".

# Actor input Schema

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

Search or listing URLs from Prace.cz, e.g. https://www.prace.cz/nabidky/brno/?keywords\[]=skladník (set the search and filters on the website and paste the URL), or job URLs, e.g. https://www.prace.cz/nabidka/<id>/. Website filters below are added to these URLs too, unless the URL already sets them.

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

Keywords to search for, e.g. 'python', 'skladník', 'účetní'. Each query is a separate search (combined with Location and the website filters). At most 20.

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

One city or region as in the website URL, e.g. 'praha', 'brno', 'ostrava', 'jihomoravsky-kraj', 'stredocesky-kraj', 'zahranici' (abroad). Czech names work too ('Jihomoravský kraj'). Used for Search queries and website filters, not for Prace.cz URLs. For several places use Locations.

## `locations` (type: `array`):

Several cities or regions, one search each (e.g. 'praha', 'brno', 'ostrava'), at most 20. Same format as Location; both can be used together. Not used for Prace.cz URLs.

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

Only jobs in these regions (kraj). Without a Location and without Prace.cz URLs, the actor runs one search per region on the website, so you pay and wait only for those jobs. Otherwise it checks the region of each job's place of work and skips jobs it can't place (e.g. "Česko" for the whole country).

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

Maximum number of jobs saved. You pay per saved job. 0 = no limit.

## `maxItemsPerStartUrl` (type: `integer`):

Limit for each search query or URL separately, so one large search doesn't use up the whole Max jobs. 0 = no limit.

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

Open each job page to add the description (e-mails and phone numbers removed), benefits, required education and languages, contract type, street and whether the advertiser is an agency. Slower: one extra request per job (2 seconds each). Jobs published on an employer's own career site keep the listing data only.

## `descriptionMaxLength` (type: `integer`):

With "Add description and details": shorten the description to about this many characters (e.g. 300 for a snippet). 0 = full text.

## `jobTypes` (type: `array`):

Only these employment types, as in the website's filter. Several values = any of them.

## `contractTypes` (type: `array`):

Only these contract types. Several values = any of them.

## `education` (type: `array`):

Only jobs for these education levels, as in the website's filter.

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

Only jobs offering at least this monthly salary, e.g. 40000. The website also matches jobs whose employer hid the salary on the page; such jobs are returned without a salary (combine with "Only jobs with a salary" to skip them).

## `workArrangement` (type: `array`):

Only jobs with this place of work.

## `languages` (type: `array`):

Only jobs that ask for one of these languages.

## `suitableFor` (type: `array`):

Only jobs the employer marked as suitable for these groups.

## `employerType` (type: `string`):

Direct employers only (no recruitment agencies), or agencies only. Sets isAgency in the results.

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

Only jobs posted in the last N days. 1, 3 and 7 days are applied by the website; other values are checked by the actor on the posting date. 0 = any time.

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

Only jobs in at least one of these categories (obor), as listed on the website. Checked by the actor on each job, so combine it with a search or location to save time.

## `onlyWithSalary` (type: `boolean`):

Skip jobs that don't show a salary on the website.

## `titleKeywords` (type: `array`):

Keep only jobs whose title contains at least one of these words (case and diacritics don't matter).

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

Skip jobs whose title contains any of these words, e.g. 'senior', 'brigáda'.

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

Keep only jobs of companies whose name contains one of these texts, e.g. 'Škoda', 'O2'.

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

Skip jobs of companies whose name contains any of these texts.

## `includeCompanyInfo` (type: `boolean`):

Add data about the employer from ARES, the public register of economic subjects of the Czech Ministry of Finance: IČO (company ID), legal form, registered office, employee count range, main activity (CZ-NACE) and founding date. Off by default: when off, the actor never contacts ARES. Extra charge "Company info" (see the Pricing tab) only for saved jobs whose company data were found and returned; jobs without a match cost nothing extra. Matching is by the exact company name including its legal form, so some jobs stay without company data. Recruitment agencies (when known) and private persons are never looked up or returned. About 1 second per new company name.

## `includeRecruiterContacts` (type: `boolean`):

Off by default. Adds recruiterName, recruiterPhone and recruiterEmail exactly as the job page shows them in its "Kontaktní údaje" block (the site almost never shows an e-mail, so recruiterEmail is nearly always empty). Needs "Add description and details". Never for private employers or jobs on career sites; nothing is guessed or looked up elsewhere, and e-mails and phones in descriptions stay removed. These are personal data and YOU are the data controller (GDPR): use them only to contact the person about that specific job ad, inform them (Art. 14 GDPR) at the latest when you first contact them, respect objections and delete the data when no longer needed. Commercial e-mails/SMS without prior consent (Act 480/2004 Coll., § 7) and marketing calls without consent (Act 127/2005 Coll., § 96) are illegal in Czechia, also to business addresses and numbers. Don't use the contacts for marketing, don't resell them and don't build contact databases. No extra charge.

## `onlyNewOrChanged` (type: `boolean`):

Return only jobs that are new or whose title, company, salary, place or type of work changed since the previous run with the same Monitoring key. Unchanged jobs are free.

## `monitoringKey` (type: `string`):

Name of the saved state. Use a different key for each watched search (e.g. 'python-praha').

## `includeRemovedJobs` (type: `boolean`):

With "Monitoring: only new or changed jobs": also return jobs from the previous run with the same Monitoring key that are no longer in the search, as rows with changeType "removed", removedAt, firstSeenAt, daysOnline and the last known title, company, place, salary and URL. Checked only when the run reads the whole search (Max jobs, Max jobs per search, Max pages per search, unread pages or rejected requests skip the check, and the status message says why). Removed rows are normal dataset rows and are charged like any other job.

## `fastMode` (type: `boolean`):

Reads Prace.cz up to 4x faster: at most 120 requests per minute and 4 at a time instead of 30 per minute and 2 at a time. It stays polite: the moment the website answers "too many requests", "service unavailable" or slows down, the run switches back to the normal speed for the rest of the run, it waits when the website asks it to wait, and it never changes IP address to get around a limit. Extra charge "Fast mode" (see the Pricing tab, $0.01 per 1 000 jobs) only for jobs saved while the fast speed was on. Limits (Max jobs, Max pages) stay the same. Please don't run several fast-mode runs at the same time.

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

Safety limit for each search or URL (40 jobs per page). The website itself shows at most 250 pages (10 000 jobs) per search. 0 = no limit.

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

Not needed: Prace.cz works without a proxy, including from Apify servers. You can use Apify Proxy or your own proxies if you prefer.

## Actor input object example

```json
{
  "searchQueries": [
    "python"
  ],
  "location": "praha",
  "maxItems": 20,
  "maxItemsPerStartUrl": 0,
  "includeDetails": false,
  "descriptionMaxLength": 0,
  "employerType": "any",
  "onlyWithSalary": false,
  "includeCompanyInfo": false,
  "includeRecruiterContacts": false,
  "onlyNewOrChanged": false,
  "monitoringKey": "default",
  "includeRemovedJobs": false,
  "fastMode": false,
  "maxPages": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

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

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "python"
    ],
    "location": "praha",
    "maxItems": 20,
    "includeCompanyInfo": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("tsutseisoftware/prace-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 = {
    "searchQueries": ["python"],
    "location": "praha",
    "maxItems": 20,
    "includeCompanyInfo": False,
}

# Run the Actor and wait for it to finish
run = client.actor("tsutseisoftware/prace-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 '{
  "searchQueries": [
    "python"
  ],
  "location": "praha",
  "maxItems": 20,
  "includeCompanyInfo": false
}' |
apify call tsutseisoftware/prace-cz-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tsutseisoftware/prace-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/IgiATggAfa6myg6f8/builds/r828JTlb3QJLGQwuy/openapi.json
