# Posao.hr Scraper: Croatia Jobs + Salary (`ceejobs/posao-hr-scraper`) Actor

Scrape Posao.hr (www.posao.hr) job listings into clean JSON: title, company, location, salary normalized to EUR/month, application deadline, tags, optional full description. Pay per result, no subscription.

- **URL**: https://apify.com/ceejobs/posao-hr-scraper.md
- **Developed by:** [CEE Jobs](https://apify.com/ceejobs) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 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?

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

## Posao.hr Jobs Scraper — Croatia job listings with salary, location and application deadline

Extract Posao.hr job listings with salary, location and application deadline for Croatia market
research, recruiting and lead generation. **Posao.hr publishes no posting dates** — not on the
results page and not on a job's own page — so every row carries the deadline the board does
print (`expiresAt`) and a `postedAt` of `null`.

### What you get

- Every job on www.posao.hr, as one structured row each: title, employer, place of work, the
  date the ad stops accepting applications, and the board's own job URL.
- Salary normalized to **EUR per month** (`salary.minEurMonth` / `salary.maxEurMonth`) next to
  the raw label the board printed, so rows priced in different currencies sort together.
- A stable `id` of the form `posao-hr:<ad id>`, so repeat runs deduplicate against your own
  database instead of against a row number.

### Sample item

One real ad, `includeDetails` on:

```json
{
  "id": "posao-hr:1239282",
  "url": "https://www.posao.hr/oglasi/automehanicar-automehatronicar-m-z/1239282/",
  "title": "Automehaničar / Automehatroničar (m/ž)",
  "company": "Autoservis Turkalj",
  "companyUrl": "https://www.posao.hr/autoservis-turkalj-svi-poslovi/548364/",
  "location": {
    "raw": "Sisak",
    "city": "Sisak",
    "country": "HR"
  },
  "remote": null,
  "employmentType": "permanent employment",
  "salary": {
    "min": 2000,
    "max": 2500,
    "currency": "EUR",
    "period": "month",
    "raw": "from 2 000 € to 2 500 € net",
    "minEurMonth": null,
    "maxEurMonth": null
  },
  "postedAt": null,
  "expiresAt": "2026-09-27",
  "description": "About the job Job description: Servisiranje vozila: Samostalno obavljanje redovnih servisa i popravaka na vozilima. Dijagnostika kvarova: Detekcija me …",
  "tags": [
    "salary-disclosed",
    "Electrical & mechanical engineering",
    "Installation, maintenance & repair",
    "Machinery & ship construction",
    "Production, craft & industry"
  ],
  "source": {
    "board": "posao-hr",
    "boardJobId": "1239282",
    "scrapedAt": "2026-08-23T09:14:03.221Z"
  }
}
```

Without `includeDetails` the same ad ships as the results page has it: `companyUrl`,
`employmentType`, `description` and every `salary` field `null`, `tags` just
`["salary-disclosed"]`, and `expiresAt` the same day worked out from the board's countdown.

Reading the fields this board fills differently from most:

- **`postedAt` is always `null`.** The board publishes a countdown to the application deadline
  ("Expires in 13 days") and nothing about when the ad went up. That countdown is `expiresAt`.
- **`salary` is only ever filled by the detail fetch.** The results page prints the words
  "Salary disclosed" on ads that name a figure and nothing at all on the rest; those words are a
  marker, not an amount, so they become the tag `salary-disclosed` and leave `salary` empty.
  Turn `includeDetails` on to get the figure itself.
- **`minEurMonth` / `maxEurMonth` are `null` on a net amount.** Croatian ads usually state pay
  net, and the board's salary cell says which. Those two fields are gross by definition, so a net
  label keeps `min`, `max`, `currency` and `raw` and earns no conversion — filing take-home pay
  next to gross pay in the same column would be worse than leaving the comparison to you.
- **`remote` is three-valued.** `true` when the place of work is nothing but home, `false` when
  it names a workplace *and* home ("Zagreb (hybrid)", "ured u Zagrebu ili rad od kuće"), `null`
  when the ad says nothing. Only the board's own place field is read; "remote" in a job title is
  the employer's copywriting.
- **`tags`** are the board's own categories from the ad's page, plus `salary-disclosed` and
  `hzz` (an ad syndicated from the Croatian public employment service) where they apply.

Anything the board did not publish is `null`, never missing.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `extra` | object | `{}` | **The way to search this board.** `{"occupation": "programer"}` crawls `https://www.posao.hr/zanimanja/programer/`; `{"field": "informatika-i-telekomunikacije"}` crawls `https://www.posao.hr/djelatnosti/informatika-i-telekomunikacije/`. Take the slug straight out of a posao.hr URL (lowercase ASCII, no diacritics) — e.g. occupations `vozac`, `konobar`, `prodavac`, `zavarivac`; fields `ekonomija-racunovodstvo-financije`, `strojarstvo-i-brodogradnja`. Set one or the other, not both. |
| `keywords` | array of strings | `[]` | Job title, skill or company words, **matched here rather than on the board** — posao.hr has no keyword search a crawler can address (see Limitations). Without an `extra` filter the actor walks the board's whole listing, ~337 pages of 15 rows, which takes roughly 1–3 minutes and finds nothing if your keyword is not Croatian. Prefer `extra`. |
| `location` | string | `""` | City or region in the board's own spelling, with or without diacritics — `Zagreb`, `Split`, `sibenik`. Matched against each row's own place of work, because the board's place filter lives on a path `robots.txt` disallows. |
| `maxItems` | integer | `100` | Hard stop after this many jobs. This is the cost dial. |
| `includeDetails` | boolean | `false` | Also open each job's own page for the description, **the salary figure**, the exact application deadline, the employer's page and the board's categories. Roughly 1.5x slower. |
| `postedWithinDays` | integer | `0` | No effect on this board — it publishes no posting dates, so there is nothing to filter on. |
| `startUrls` | array | `[]` | Listing URLs on www.posao.hr to crawl directly. `/poslovi/`, `/zanimanja/<slug>/` and `/djelatnosti/<slug>/` are paged; any other URL is crawled as the single page it is. |
| `proxyConfiguration` | object | Apify proxy | Apify datacenter proxy groups only; residential groups and custom proxy URLs are rejected. |

### Pricing

$2 per 1,000 jobs, plus $2 per 1,000 when `includeDetails` is on. No subscription.

### Limitations

Public listing data only — no recruiter names or e-mail addresses, no data behind a login. The
crawler obeys www.posao.hr's `robots.txt`, including for URLs you pass in `startUrls`, and runs on
datacenter proxies without any CAPTCHA or challenge circumvention: a challenge page is reported
as an error, not retried through another browser. Apify datacenter proxy groups only; residential
groups and custom proxy URLs are rejected before the run starts. Jobs the board removes between
the listing page and the detail fetch are skipped rather than shipped half-filled.

**posao.hr has no keyword search this crawler can use.** Its search box POSTs to the site rather
than putting the term in a URL, and the query parameters that look like a search (`?keywords=`,
`?sKljucne=`, `?q=`) are ignored — the board answers all of them with its unfiltered listing.
Whatever path the form does reach lives under `/poslovi/<something>/`, which `robots.txt`
disallows. That is why `keywords` is applied to rows after they are fetched, and why the
occupation and field filters are the ones to reach for: an occupation listing is a handful of
pages, the whole board is ~337.

The board's ad pages come in two shapes — a structured template and one the employer designed
themselves — and only the structured one carries an employment type or a salary figure, so those
two fields are filled on some rows and not others even with `includeDetails` on. On the
structured template the employer's address, telephone and e-mail block is dropped whole rather
than scrubbed, so a description never carries a way of reaching a named person.

# Actor input Schema

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

Job title, skill or company words. posao.hr has no keyword search a crawler can address, so these filter the full listing client-side: without an occupation or field filter under 'Board-specific filters' the run walks ~337 pages of 15 rows to apply them. Croatian words work best. See the README.

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

City or region in the board's own spelling, with or without diacritics (e.g. Zagreb, Split, Šibenik). Matched against each row's own place of work, because the board's place filter lives on a path its robots.txt disallows. Optional.

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

Stop after this many jobs. Controls cost: $2 per 1,000 jobs.

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

Adds description, expiry date and better salary data. Costs an extra $2 per 1,000 jobs and is slower (board-dependent, roughly 1.5–10×).

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

Only keep jobs posted in the last N days. 0 = no filter.

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

Listing URLs from the board to crawl directly instead of keywords.

## `extra` (type: `object`):

occupation: a posao.hr occupation slug (e.g. programer, prodavac) → crawls /zanimanja/<slug>/; field: a field slug (e.g. informatika-i-telekomunikacije) → /djelatnosti/<slug>/. Use one of them; keywords alone filter the whole board client-side (~337 pages).

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

Apify datacenter proxy groups only; residential groups and custom proxy URLs are rejected. The datacenter pool is enough for this board.

## Actor input object example

```json
{
  "maxItems": 100,
  "includeDetails": false,
  "postedWithinDays": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `jobsCsv` (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 = {
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ceejobs/posao-hr-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 = { "proxyConfiguration": { "useApifyProxy": True } }

# Run the Actor and wait for it to finish
run = client.actor("ceejobs/posao-hr-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 '{
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call ceejobs/posao-hr-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ceejobs/posao-hr-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/jFQPemdGYYuQUAgTD/builds/oLqDEyAcXhcfg6qvk/openapi.json
