# Careerjet Scraper \[From $3.5💰] | Jobs | 23 Portals | Salary (`ahmed_jasarevic/careerjet-scraper`) Actor

Scrape job postings from Careerjet's 23 country portals for job posting aggregation, hiring trend analysis and job board backfill. Structured JSON with parsed salary fields via the Apify API.

- **URL**: https://apify.com/ahmed_jasarevic/careerjet-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

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

## Careerjet Scraper

Scrape job postings from **Careerjet's 23 verified country portals** — France, Spain, Italy, the
Netherlands, Poland, Austria, Portugal, Sweden, Switzerland, Czechia, Japan, Brazil, Norway,
Finland, Hungary, Slovakia, Romania, Ukraine, Russia, South Korea, Türkiye, Israel and Singapore —
in a **single run**, with no official API account required. One pay-per-event run returns job
title, company, location, parsed salary, posting date, full description and the outbound apply URL
for every ad it finds.

Built for recruiters, job-board operators, labour-market analysts and hiring-signal lead-gen teams
that need cross-country vacancy data as a **job listings data feed**. Careerjet is an aggregator —
it never hosts the vacancy, so each row carries the employer (`company`, `companyUrl`) and the
origin site (`publisherName` / `publisherUrl`) it indexed the ad from.

### Main use cases

- **Job posting aggregation** — collect openings across many countries in one run instead of
  merging per-board exports by hand.
- **Job market research and hiring trend analysis** — snapshot the same query on a schedule to see
  which roles, employers and locations are growing.
- **Job board backfill and job feed syndication** — populate a niche job board with structured
  listings that link out to the employer's own application page.
- **Salary benchmarking and recruitment data** — parsed `salaryMin` / `salaryMax` /
  `salaryCurrency` / `salaryPeriod` fields turn raw ads into comparable pay ranges.
- **Competitor hiring tracking and job board monitoring** — watch a company's or sector's live
  vacancies as a leading demand signal.

### How it works

The Actor is a plain **`CheerioCrawler`** — server-rendered HTML over HTTP, **no browser at all**.
Careerjet's markup is classic server-rendered PHP (no `__NEXT_DATA__`, no client-side search
index), so every job card can be parsed from the raw response. A run works in five steps:

0. **A 12-second probe** of the first listing URL logs HTTP status, byte count and whether the
   answer is the Cloudflare Turnstile challenge — the first log lines say why the run will return
   rows or 0 rows.
1. **Listing pass** parses every served page into cards.
2. **Listing recovery** rotates challenged listing pages through up to **three fresh exit IPs**.
3. **Detail pass, probed first** fetches one `/jobad/<id>` per cookie-less host to test whether the
   host is gated; gated hosts have their remaining ads **skipped instead of fetched**.
4. **Bounded detail retry** re-fetches at most 10 challenged detail pages once from a fresh IP.

Careerjet gates every content page behind a Cloudflare Turnstile interstitial served from
`/bi-verifycc`. This is an **IP-reputation gate, not a JavaScript-rendering gate** — a datacenter
IP is always challenged, a residential one is usually served — which is why the whole Actor stays on
plain HTTP with the RESIDENTIAL proxy group pre-enabled.

### Scrape job listings from Careerjet's 23 verified country portals

Careerjet's gate is **per portal, not per IP**. Some domains serve their listing page to any
residential exit IP; others answer every request — from any exit IP, and however many different ones
are tried — with the interstitial. Verified over plain HTTP on the RESIDENTIAL group on 2026-10-07:

| Listing page **served** (23) | Listing page **always gated** (13) |
| --- | --- |
| `.fr` `.es` `.it` `.nl` `.pl` `.at` `.pt` `.se` `.ch` `.cz` `.jp` `.com.br` `.no` `.fi` `.hu` `.sk` `.ro` `.ua` `.ru` `.co.kr` `.com.tr` `.co.il` `.com.sg` | `.com` (US) `.co.uk` `.de` `.ie` `.com.au` `.co.in` `.be` `.dk` `.co.za` `.sg` `.co.nz` `.ae` `.ly` |

The gated portals are what the **input dropdown omits**: they answer every request with the
interstitial and can only ever return 0 rows here, so the Actor never claims them.

**Why rows = portals × 5.** Careerjet serves a **5-card, pager-less** listing page to a guest
session and ignores `page=N` (measured on 16 portals; `?page=2..4` returns page 1's ads, which the
row buffer deduplicates). A run therefore scales by **portals**, never by pages, and `maxItems`
is the only row-shaped input: with `countryDomains` empty the Actor plans `ceil(maxItems / 5)`
verified portals (30 → 6, 100 → 20 of the 23 available). An explicit portal list is the starting
point — when it cannot hold `maxItems` the Actor **tops it up** with more verified portals and logs
which ones it added. The practical ceiling is **115 rows** (the most the 23 verified portals hold).

Measured run times (Actor run only — Apify adds container start-up on top):

| Run | Result |
| --- | --- |
| `.fr`, 5 rows, details on | **10 s** — 5 rows card-only; details tried twice (fresh exit IP on the 2nd try) |
| `.es`, 5 rows, details on | **8 s** — 5 rows card-only |
| 6 open portals in one run, listing only | **25 s** — 20 rows; 2 gated pages rotated 3× then reported |
| 5 default portals, details on | **9 s** — 25 rows card-only; 1 probe/host, 20 ads skipped, 5 challenges (was 35) |
| `.com` (US) alone, details on | **8 s** — 0 rows, 4 exit IPs challenged, explicit error instead of silence |
| 14 remaining portals, one bulk probe | **26 s** — 11 served (55 rows), `.co.nz` `.ae` `.ly` challenged from 3 exit IPs each |

### Job posting aggregation across country portals in a single run

Careerjet renders every job card **server-side in plain HTML**, with no client-side templating. The
same card markup appears on search-results pages and in Careerjet's own "Latest jobs"
`section.jobs-contextual` block, so the parser was validated offline against real, live
server-rendered HTML rather than assumed:

```html
<section class="jobs-contextual standard">
    <ul class="jobs">
        <li>
            <article class="job clicky" data-url="/jobad/us222530badcca995d5d84006ae36d046c">
                <header><h3><a href="/jobad/us222530badcca995d5d84006ae36d046c">…</a></h3></header>
                <p class="company">UniFirst</p>
                <ul class="location"><li><svg class="icon" /> Tacoma, WA</li></ul>
                <ul class="salary"><li><svg class="icon" /> $31.25-33.65 per hour</li></ul>
                <div class="desc">Description: …</div>
                <footer><ul><li><span class="badge">Just now</span></li></ul></footer>
            </article>
        </li>
    </ul>
</section>
```

Detail-page selectors come from Careerjet's own published stylesheet
`static.careerjet.org/css/all.min.css`, which declares the layout explicitly:

```css
#job>.container>header .job-title        /* title                                    */
#job>.container>header .company a        /* company                                  */
#job>.container>header .details>li       /* location, date, contract type, salary    */
#job>.container>.content                 /* description body                         */
#job .actions a                          /* apply button                             */
```

Every selector has a looser fallback, so a partial markup change degrades a field instead of
breaking the run.

#### Exact endpoints used

| Purpose | URL pattern |
| --- | --- |
| Search (most portals) | `https://{portal}/jobs?s={keywords}&l={location}&sort={sort}&page={n}` |
| Search (UK) | `https://www.careerjet.co.uk/search/jobs?s=…&l=…` |
| Search (DE) | `https://www.careerjet.de/suchen/stellenangebote?s=…&l=…` |
| Job detail | `https://{portal}/jobad/{2-letter country}{32 hex chars}` |

The `s` / `l` parameter names are not guesswork — they come from Careerjet's own
`<form id="search-main" method="get" action="/jobs">`, whose inputs are `name="s"` (What) and
`name="l"` (Where). Localized search routes are stored per portal in `src/countries.ts`; if a
portal's primary route renders zero cards the Actor retries once on that portal's next known route.

#### Proxy is effectively required

Careerjet puts every content page behind a Cloudflare Turnstile interstitial served from
`/bi-verifycc`, confirmed on **all 29 country domains tested** plus a Google-Cloud-hosted reader
proxy — i.e. **every datacenter IP is challenged**, regardless of User-Agent, headers or HTTP
version.

| Attempt | Result |
| --- | --- |
| Cheerio, direct (no proxy) | blocked |
| Cheerio + Apify Proxy RESIDENTIAL, `apifyProxyCountry: GB` | listing served; job-ad pages still challenged |
| Spoofed User-Agent (Googlebot / bingbot / empty) | blocked — the WAF judges the IP, not the UA |
| TLS-impersonating client (`curl_cffi`) | blocked — Chrome's TLS + HTTP/2 fingerprint is not enough |
| Apify `UNBLOCKER` proxy group | `502` (no such group for this account) |
| Careerjet RSS / sitemap / `/jobview/` / `/clk/` / `/apply/` | 404 — legacy surfaces are gone |
| Playwright headless Chrome + RESIDENTIAL | blocked — waited the full 45 s |
| Playwright **headed** under Xvfb + RESIDENTIAL | cleared on a fresh exit IP, but only after up to ~60 s/page |

A headed browser did work — and that is exactly what this build drops. It costs 40–90 s per page and
the 2 GB Playwright image, and the only thing it ever buys beyond plain HTTP is the **full
description**. **Enable the `proxy` input and pick the RESIDENTIAL group**, ideally with
`apifyProxyCountry` set to the portal's country (`FR` for `.fr`, `JP` for `.jp`). The Actor pins the
run to **one sticky exit IP** because a cached clearance cookie only passes from the address it was
earned on, and switches to a fresh session whenever a pass comes back challenged. If **every**
listing page was challenged it exits with an explicit error — it never silently returns an empty
dataset.

### Output Fields: structured job data across all 23 country portals

One row per job posting; exactly one row per ad `id` — listing data is buffered and the
`/jobad/<id>` detail fetch overwrites it, so a row is never duplicated or half-empty. The Output tab
is driven by `.actor/output_schema.json` and renders the same dataset through two views:
**Overview** (one line per job) and **Details** (every extracted field).

| Field | Notes |
| --- | --- |
| `adId` | Ad id: 2-letter country code + 32 hex chars, e.g. `us222530badcca995d5d84006ae36d046c`. Unique across portals — the row key. |
| `countryCode` | The 2-letter prefix parsed out of `adId`. |
| `domain` | Careerjet country portal the row was scraped from. |
| `url` | Canonical `/jobad/<id>` page. |
| `title` | Job title as rendered by Careerjet. |
| `company`, `companyUrl` | Hiring company + employer profile page when the card links one (else `null`). |
| `location`, `locations[]` | Raw location text plus the comma/slash split parts for multi-site ads. |
| `salary` | Salary exactly as Careerjet renders it (may be `null` — many ads publish none). |
| `salaryMin`, `salaryMax` | Lower/upper bounds parsed out of `salary`. |
| `salaryCurrency`, `salaryPeriod` | Currency symbol/code detected; pay period (`hour`/`week`/`month`/`year`). |
| `datePostedLabel`, `datePosted` | Relative label as shown, plus a best-effort ISO-8601 date. |
| `description`, `descriptionHtml` | Full posting body (plain text + HTML) — detail fetches only. |
| `listingExcerpt` | Teaser text from the search-results card. |
| `employmentType`, `badges` | Contract / working-hours chips and all footer chips (incl. posting date). |
| `applyUrl` | The portal's apply link (detail fetches only). |
| `publisherName`, `publisherUrl` | Origin site Careerjet indexed the ad from, derived from `applyUrl`. |
| `query`, `queryLocation` | Keywords and location the search that surfaced this ad was run with. |
| `source`, `scrapedAt` | Emitted by `careerjet`; ISO-8601 parse timestamp. |

The salary/date parsers were validated offline against a real, unchallenged listing fragment: 5/5
cards parsed with 0 missing `adId`/`title`/`company`/`location`/`url`, and
`$18.50-19.25 per hour` → 18.5/19.25/USD-hour, `£45,000 – 55,000 per year` →
45000/55000/GBP-year, `€2.500 per maand` → 2500/EUR-month.

### Input Parameters

| Field | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `startUrls` | array | No | `[]` | Explicit Careerjet search URLs. When filled, `keywords`, `location` and `countryDomains` are ignored. |
| `keywords` | string | No | `software engineer` | Job title, keywords or company, sent as the `s` search parameter. |
| `location` | string | No | `""` | City or region, sent as the `l` parameter. Empty = country-wide search. |
| `countryDomains` | string\[] | No | `[]` | Multiselect of the 23 verified portals. Empty = the Actor auto-selects `ceil(maxItems / 5)` portals and tops up a short selection. |
| `sort` | enum | No | `relevance` | `relevance` | `date` | `salary`. |
| `maxItems` | integer | No | `100` | The row target (1–2000). The Actor plans `ceil(maxItems / 5)` portals and stops at the target. |
| `includeJobDetails` | boolean | No | `true` | Fetch each `/jobad/<id>` over HTTP for the full description + apply URL. Gated pages ship card-only. |
| `maxRequestsPerCrawl` | integer | No | `400` | Safety cap on total HTTP requests (1–5000). |
| `proxy` | object | No | RESIDENTIAL, Apify Proxy on | Pre-enabled. RESIDENTIAL is effectively required; the run pins one sticky session. |

Free Apify accounts are automatically capped at **10 jobs per run**.

### Example Input And Output

**Row target only (recommended)** — portals are derived from `maxItems`:

```json
{
    "keywords": "software engineer",
    "location": "",
    "sort": "date",
    "maxItems": 100
}
```

Plans `ceil(100 / 5) = 20` verified portals, one request each, and stops at 100 rows.

**Pinned to three portals:**

```json
{
    "keywords": "nurse",
    "countryDomains": ["www.careerjet.fr", "www.careerjet.es", "www.careerjet.it"],
    "maxItems": 15
}
```

**Fast listing-only run (no detail fetches — ~1 request per portal):**

```json
{ "keywords": "nurse", "includeJobDetails": false, "maxItems": 50 }
```

Example output row:

```json
{
    "adId": "fr222530badcca995d5d84006ae36d046c",
    "countryCode": "fr",
    "domain": "www.careerjet.fr",
    "url": "https://www.careerjet.fr/jobad/fr222530badcca995d5d84006ae36d046c",
    "title": "Ingénieur logiciel senior",
    "company": "Acme SAS",
    "companyUrl": "https://www.careerjet.fr/company/acme",
    "location": "Paris, Île-de-France",
    "locations": ["Paris", "Île-de-France"],
    "salary": "45 000 € - 55 000 € par an",
    "salaryMin": 45000,
    "salaryMax": 55000,
    "salaryCurrency": "€",
    "salaryPeriod": "year",
    "datePostedLabel": "il y a 2 jours",
    "datePosted": "2026-10-03T00:00:00.000Z",
    "description": "Nous recrutons un ingénieur logiciel senior pour …",
    "listingExcerpt": "Nous recrutons un ingénieur logiciel senior …",
    "employmentType": ["Temps plein", "CDI"],
    "badges": ["il y a 2 jours", "Temps plein"],
    "applyUrl": "https://careers.acme.example/jobs/1234",
    "publisherName": "careers.acme.example",
    "publisherUrl": "https://careers.acme.example",
    "query": "software engineer",
    "queryLocation": "",
    "source": "careerjet",
    "scrapedAt": "2026-10-05T12:00:00.000Z"
}
```

### Careerjet API vs this scraper

Careerjet **does** run an official API, but it is not open: v4
(`search.api.careerjet.net/v4/query`) requires a publisher account, an API key via HTTP Basic auth,
and a mandatory IP whitelist. It caps depth at 10 pages × 100 results and omits the origin site.
The legacy affiliate API (`public.api.careerjet.net/search`) is dead, internal hosts
(`api.careerjet.net`) return `Unrecognized request URL` on every route, and
`ac.careerjet.net` is only the type-ahead autocompleter, not job data.

This Actor is the **no-account route** to the same listings: it reads what a guest browser sees and
returns 28 structured fields the partner API does not, including `publisherName` / `publisherUrl`.
If you already hold a Careerjet partner account, prefer the official v4 API for supported access —
and use this Actor when you need cross-portal coverage without signing up.

### Integrations & Automation

- **Apify API / Dataset API** — every run's rows are available as JSON via
  `/v2/datasets/{datasetId}/items?format=json&clean=true`.
- **Webhooks** — trigger downstream jobs on run completion (e.g. push new rows to your DB or ATS).
- **Zapier / Make** — connect the dataset to hundreds of apps without writing code.
- **Schedules** — recommend a **daily** run per target query for job market monitoring, or **weekly**
  for slower-moving hiring-trend snapshots. Recurring runs build the time series that makes trend
  analysis possible, and they improve the Actor's Store recommendation signals.

### Related Actors

- [careerjet-job-scraper](https://apify.com/axlymxp/careerjet-job-scraper)
- [careerjet-mcp-server](https://apify.com/axlymxp/careerjet-mcp-server)
- [careerjet-job-search-api](https://apify.com/nabeelbaghoor/careerjet-job-search-api)
- [job-posting-aggregator](https://apify.com/ghost_banana/job-posting-aggregator)
- [ats-job-aggregator](https://apify.com/nexgendata/ats-job-aggregator)

### FAQ

#### Is there an official Careerjet API?

Yes — Careerjet publishes a v4 partner API at `search.api.careerjet.net/v4/query`, but it requires a
publisher account, an API key with HTTP Basic auth (empty password) and a whitelisted IP, and it
caps results at 10 pages × 100. The legacy affiliate API is dead. This Actor needs no account.

#### What are the alternatives to this Actor?

Apify Store carries several Careerjet actors (see Related Actors) plus broader job-posting
aggregators. Commercial job-data vendors such as TheirStack, JobsPikr, Jobo Data and jobdataapi
publish job feeds too. The open-source JobSpy library is a common alternative for multi-board
scraping in Python. This Actor's differentiator is no-account access to 23 verified Careerjet
portals with parsed salary fields and the outbound apply URL.

#### How do I aggregate job listings from multiple job boards?

Run the Actor once across as many Careerjet portals as you need — set `keywords` and let
`maxItems` drive portal selection, or pin `countryDomains` explicitly. Every row lands in one
dataset keyed by `adId`, so there is no per-board export to merge by hand.

#### Is there a job listings API?

The Actor's own Dataset API is one: `GET /v2/datasets/{datasetId}/items?format=json&clean=true`
returns the structured rows as JSON. Combined with the pay-per-event pricing (`result` $0.0035),
you get a job-listings API without a partner agreement.

#### How do I get salary data for benchmarking from job postings?

Each row carries `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod`, parsed from the raw
`salary` string — for example `£45,000 – 55,000 per year` becomes 45000/55000/GBP/year. Ads with no
published salary have `salary: null` and null bounds, so filter those out before averaging.

#### Is it legal to scrape job postings from Indeed or LinkedIn?

Legality depends on the source's Terms of Service and your jurisdiction. This Actor does not scrape
Indeed or LinkedIn — it scrapes Careerjet, whose `robots.txt` disallows the paging/legacy routes but
**not** the `/jobad/` detail path this Actor reads. Review those terms and your own compliance
requirements before running at scale.

#### Which countries does Careerjet cover in this Actor?

23 portals are verified to return rows: France, Spain, Italy, the Netherlands, Poland, Austria,
Portugal, Sweden, Switzerland, Czechia, Japan, Brazil, Norway, Finland, Hungary, Slovakia, Romania,
Ukraine, Russia, South Korea, Türkiye, Israel and Singapore. Careerjet's US, UK, Germany, Ireland,
Australia, India and several other portals are always gated and are omitted from the input dropdown.

#### What is the difference between a job board and a job aggregator?

A job board hosts vacancies on its own domain; Careerjet is an **aggregator** — it indexes ads from
employers and other sites and links out to apply. That is why every row here carries
`publisherName` / `publisherUrl` (the origin site) and `applyUrl` (the outbound apply link).

### SEO Keywords

careerjet api, careerjet scraper, job aggregator api, job listings data feed, job postings api,
scrape job listings, job posting aggregation, job market research, hiring trend analysis, track
competitor hiring, job board monitoring, job feed syndication, recruitment data, salary
benchmarking, job board backfill, indeed api, linkedin jobs scraper, jobspy, job search engines,
indeed alternative, ats job feed, structured job data, job data api, european job market data

### For AI Agents & LLM Apps

**Purpose:** return structured Careerjet job postings (title, company, location, parsed salary,
dates, description, apply URL, publisher) from one or more of 23 verified country portals.

Minimal working input:

```json
{ "keywords": "software engineer", "maxItems": 30 }
```

Variant — pinned portals with full details:

```json
{
    "keywords": "nurse",
    "countryDomains": ["www.careerjet.fr", "www.careerjet.es"],
    "includeJobDetails": true,
    "maxItems": 10
}
```

**Output fields:** `adId`, `countryCode`, `domain`, `url`, `title`, `company`, `companyUrl`,
`location`, `locations`, `salary`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`,
`datePostedLabel`, `datePosted`, `description`, `descriptionHtml`, `listingExcerpt`,
`employmentType`, `badges`, `applyUrl`, `publisherName`, `publisherUrl`, `query`, `queryLocation`,
`source`, `scrapedAt`.

**Behaviors an agent should know:**

- If `startUrls` is set, `keywords`, `location` and `countryDomains` are **ignored**.
- `maxItems` is the only row-shaped input; leaves unset → default **100 rows** (billed).
  Careerjet yields **5 rows per portal per search**, so the Actor plans `ceil(maxItems / 5)`
  portals — the practical ceiling is 115 rows across the 23 verified portals.
- Careerjet's US/UK/DE/IE/AU/IN/be/dk/za/sg/nz/ae/ly portals are **always gated** and return **0
  rows**; they are omitted from `countryDomains`.
- `includeJobDetails: true` (default) adds billed detail-page requests and enables `description`,
  `descriptionHtml` and `applyUrl`. Gated job-ad pages ship **card-only** rows instead — the run
  names the affected hosts and does not fail.
- The RESIDENTIAL proxy is **effectively required**; datacenter IPs are always challenged (0 rows).
  The run pins one sticky session and rotates only on a challenge.

**Billing:** pay-per-event — `result` **$0.0035** per result (DIAMOND $0.0033) plus `Actor Start`
**$0.00005** per run.

### Legal & Compliance Disclaimer

This Actor is an independent tool and is **not affiliated with, endorsed by, or sponsored by
Careerjet**. It reads only publicly available Careerjet pages over HTTP (no login bypass, no
CAPTCHA solving, no browser) and does not access any authenticated or private surface. Users are
responsible for complying with Careerjet's Terms of Service, its `robots.txt`, and applicable
data-protection law (e.g. GDPR for EU/UK job data) when using the extracted postings. The dataset
contains employer names, job postings and outbound apply links; do not use it for unsolicited
commercial outreach in violation of applicable law.

# Actor input Schema

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

Explicit Careerjet search URLs to scrape. When filled, `keywords`, `location` and `countryDomains` are ignored.

## `keywords` (type: `string`):

Job title, keywords or company, sent as the `s` search parameter.

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

City or region, sent as the `l` search parameter. Leave empty for a country-wide search.

## `countryDomains` (type: `array`):

Pick one or more portals from the dropdown. Leave it empty and the Actor selects exactly as many verified portals as Max job items needs (5 rows per portal per search). A selection that cannot hold Max job items is topped up automatically with more verified portals — the log names the ones it added. Each portal's localized search route is used automatically. Every portal listed here was verified on 2026-10-07 to serve its listing page over plain HTTP from a residential exit IP; portals that always answer with the Turnstile interstitial (and therefore return no rows) were removed from this list.

## `sort` (type: `string`):

Careerjet's `sort` search parameter.

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

How many job rows this run should return — the only row-shaped setting there is. Careerjet yields 5 rows per portal per search, so the Actor plans ceil(maxItems / 5) portals when Country portals is left empty (e.g. 30 → 6 portals, 100 → 20). It stops as soon as the target is reached.

## `includeJobDetails` (type: `boolean`):

Open each `/jobad/<id>` page over plain HTTP to capture the full description, publisher and apply URL. Pages that come back with the Cloudflare interstitial ship as listing cards (title/company/location/excerpt) — no browser is launched to clear them. Turn off for a listing-only run.

## `maxRequestsPerCrawl` (type: `integer`):

Safety cap on total HTTP requests (listing pages + detail pages).

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

Pre-enabled with Apify Proxy / RESIDENTIAL, because Careerjet challenges or times out every datacenter IP (0 rows without it). The run pins one sticky proxy session for the whole crawl (a cached clearance cookie only passes from the IP it was earned on) and rotates to a fresh session when a pass comes back challenged or times out. Optionally set apifyProxyCountry to the country of the portal you are scraping.

## Actor input object example

```json
{
  "startUrls": [],
  "keywords": "software engineer",
  "location": "London",
  "countryDomains": [],
  "sort": "date",
  "maxItems": 30,
  "includeJobDetails": true,
  "maxRequestsPerCrawl": 200,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Every scraped job posting as a table. Column layout and ordering come from the views defined in .actor/dataset_schema.json; switch between the Overview and Details views with the view selector.

## `json` (type: `string`):

The same rows as clean JSON with null-valued fields stripped — what the Dataset API returns with format=json\&clean=true.

## `datasetApi` (type: `string`):

Dataset metadata endpoint: item count, field statistics, schema and view definitions.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "startUrls": [],
    "keywords": "software engineer",
    "location": "London",
    "countryDomains": [],
    "sort": "date",
    "maxItems": 30,
    "includeJobDetails": true,
    "maxRequestsPerCrawl": 200,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/careerjet-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [],
    "keywords": "software engineer",
    "location": "London",
    "countryDomains": [],
    "sort": "date",
    "maxItems": 30,
    "includeJobDetails": True,
    "maxRequestsPerCrawl": 200,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/careerjet-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [],
  "keywords": "software engineer",
  "location": "London",
  "countryDomains": [],
  "sort": "date",
  "maxItems": 30,
  "includeJobDetails": true,
  "maxRequestsPerCrawl": 200,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call ahmed_jasarevic/careerjet-scraper --silent --output-dataset

```

## MCP server setup

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