# HelloWork Jobs Scraper API - France Job Board (`dami_studio/hellowork-jobs-scraper`) Actor

When a French employer publishes no salary, HelloWork prints its own estimate in the same slot, labelled Estimation Hellowork. Every row says which you're holding. Title, company, city, department, CDI, CDD, interim, stage, alternance, skills and posted date. $0.49 per 1,000.

- **URL**: https://apify.com/dami\_studio/hellowork-jobs-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (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 $0.49 / 1,000 job returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## HelloWork Jobs Scraper API - France job board

When an employer publishes no salary, HelloWork fills the gap with its own estimate and labels it
"Estimation Hellowork". In the page data the two arrive as separate fields, `baseSalary` and
`estimatedSalary`, and across 90 postings they were never both present: 70 employer figures, 18
estimates, 2 with neither. Every row here carries `salaryIsEstimate`, so you always know which one
you are holding. See **Salary** below, because that distinction matters more than it sounds.

[HelloWork](https://www.hellowork.com) is one of France's biggest generalist job boards. Search by
keyword and city, filter by contract type, remote band, experience or salary, and get structured
rows back: title, company, city and department, CDI/CDD/intérim/stage/alternance, the salary when
the ad carries one, the exact posted date, and the link to the posting.

There's no login and no API key involved. It runs over plain HTTP rather than a browser, which is
what keeps it cheap.

### Billing

**$0.49 per 1,000 jobs**, plus a $0.001 actor-start fee per run.

It's a flat rate, the same number on the free plan as on any paid one. The start fee stays $0.001
per run whatever memory you allocate, so what a run costs tracks the jobs it returns and nothing
else.

You are charged once per real job row. Sample rows, error rows and runs that return nothing are
never charged.

### What you get

| Field | Notes |
|---|---|
| `title`, `companyName`, `jobUrl`, `jobId` | 100% of rows |
| `locationLabel`, `city`, `departmentCode`, `region`, `postalCode` | department code is the French `75`, `69`, `33`… |
| `contractType` | `CDI`, `CDD`, `Intérim`, `Stage`, `Alternance`, `Freelance`, … |
| `contractDuration` | for fixed-term postings: `6 mois`, `12 mois`, … |
| `workingTime` | `Full time` / `Part time` |
| `teleworkLabel`, `isRemote`, `isHybrid` | HelloWork's own télétravail band, where the employer set one |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText` | see **Salary** below |
| `salaryIsEstimate` | `false` = the employer published it, `true` = HelloWork's estimate |
| `salaryEstimateMin/Median/Max` | HelloWork's own percentile estimate, kept separate |
| `postedAt` | exact ISO timestamp from the posting page |
| `postedRelative`, `postedAtEstimated` | `"il y a 12 jours"`, and the date derived from it |
| `expiresAt` | when HelloWork will take the posting down |
| `occupationalCategory`, `sectors`, `skills`, `educationLevels`, `experienceRequired` | |
| `reference`, `directApply`, `companyProfileUrl`, `companyLogo` | |
| `descriptionHtml`, `descriptionText`, `requirementsText` | only when *Include the full job description* is on |
| `searchQuery`, `searchLocation`, `page`, `position`, `scrapedAt` | provenance for every row |

Fill rates from a real 236-row run (`comptable`, Lyon): title, company, URL, contract, working time,
posted date, expiry, sector and reference 100%; city and department 100%; postal code 98%; skills
96%; télétravail band 46%; experience 25%.

### Salary, honestly

HelloWork shows two different things, and they're easy to confuse:

- **`baseSalary`** is what the employer actually put in the ad.
- **`estimatedSalary`** is HelloWork's own guess for that job title and region. Its own UI labels
  that one "Estimation Hellowork".

Measured over 177 postings from six unrelated searches (IT, accounting, care work, temp work,
hospitality, internships):

| | share |
|---|---|
| Employer-published salary | **63%** |
| HelloWork's own estimate only | 19% |
| No salary at all | 18% |

The two never overlap. HelloWork only estimates when the employer published nothing. The split moves a
lot by sector: a 236-row accounting run in Lyon came back 88% employer-published, a 1,497-row
national retail run 57%.

Every row carries **`salaryIsEstimate`**. When it's `false`, `salaryMin`/`salaryMax` came from the
ad. When it's `true`, they came from HelloWork's estimate, and the same numbers are repeated in
`salaryEstimateMin`/`salaryEstimateMax` so you can drop them with one filter. If you only want real
offers: `salaryIsEstimate === false`.

Set **Only postings that show a salary** to make HelloWork itself filter for you.

### Example row

```json
{
  "jobId": "82120782",
  "title": "Backend Developer H/F",
  "companyName": "Team.is",
  "jobUrl": "https://www.hellowork.com/fr-fr/emplois/82120782.html",
  "city": "Paris 10e",
  "departmentCode": "75",
  "region": "Île-de-France",
  "postalCode": "75010",
  "contractType": "CDI",
  "workingTime": "Full time",
  "teleworkLabel": "Hybrid",
  "isHybrid": true,
  "salaryMin": 70000,
  "salaryMax": 80000,
  "salaryCurrency": "EUR",
  "salaryPeriod": "YEAR",
  "salaryIsEstimate": false,
  "postedAt": "2026-08-07T03:11:37Z",
  "expiresAt": "2026-09-06T03:11:37Z",
  "occupationalCategory": "Informatique",
  "skills": ["Node.js", "PostgreSQL", "API REST"]
}
```

### Input

```json
{
  "searchQueries": ["développeur"],
  "location": "Paris",
  "contractTypes": ["cdi"],
  "postedWithin": "week",
  "maxItems": 200
}
```

Or paste URLs straight from your browser:

```json
{
  "startUrls": [
    { "url": "https://www.hellowork.com/fr-fr/emploi/recherche.html?k=comptable&l=Lyon" },
    { "url": "https://www.hellowork.com/fr-fr/emplois/82120782.html" }
  ]
}
```

Filters available: contract type, remote band, working time, experience band, employer type
(company / recruitment agency / ESN / temp agency / training centre), posted-within, minimum
salary, radius around a city, salary-shown-only, and sort by relevance or by date.

### Speed and limits

Real runs, all at 1 GB:

| Run | Rows | Wall time |
|---|---|---|
| `comptable` in Nantes | 12 | 6 s |
| `comptable` in Lyon | 236 | 40 s |
| `commercial` in Marseille, listing only | 247 | 19 s |
| `vendeur`, France-wide | 2,000 | 9 min 34 s |

- 30 postings per search request. There's **no page cap**. HelloWork pages all the way to the end
  of a result set, so a 24,879-result search really does have 830 pages.
- A broad national search repeats roughly a quarter of its postings across pages, because HelloWork
  re-ranks between requests and lists multi-site jobs more than once. Duplicates are dropped, and
  `maxItems` counts unique postings, not raw cards.
- With *Open each posting* on (the default), each row also costs one request to the posting page.
  Turn it off for a much faster listing-only run: you keep title, company, location, contract,
  working time, remote band, the employer's salary and an approximate posted date, and you lose the
  exact timestamp, description, skills, sector, postal code and the salary estimate.

### What this does not do

- **It does not walk the intérim sub-site's own pages.** Temp-work postings that appear in the main
  search are returned normally; `hellowork.com/fr-fr/interim/…` landing pages are not crawled.
- **No candidate CVs, no company reviews, no salary-benchmark pages.** Jobs only.
- **No login-only content.** Everything here is what a logged-out visitor sees.
- **It will not apply for you.** `jobUrl` is where a human applies; HelloWork keeps most
  applications on its own site (`directApply`).
- **`teleworkLabel` is often empty.** Between 1% and 46% of postings carry a télétravail tag
  depending on the sector, because most employers do not set one. It isn't a field you'll always
  get, so it isn't advertised as one.
- **`postedAtEstimated` is not `postedAt`.** The search card only says "il y a 12 jours", so with
  *Open each posting* off you get a date good to the hour or the day, not a timestamp.
- **Deep runs are paced.** HelloWork rate-limits a single address after a couple of thousand
  requests, so posting pages are spread over rotating addresses. Expect about 200 rows a minute with
  detail on (2,000 rows took 9m34s) and several times that with it off.

### FAQ

**What is HelloWork?**
HelloWork, formerly RegionsJob, is one of France's biggest generalist job boards. It carries CDI and
CDD office roles, intérim, stage and alternance, across every sector. One broad keyword search
returned 24,879 postings when this was measured.

**Do I need a HelloWork account or an API key?**
No. Everything this actor reads is public. There is no login and no key to configure.

**How do I scrape HelloWork jobs by city?**
Put the city in `location` (`"Paris"`, `"Lyon"`, `"Marseille"`, `"Bordeaux"`, `"Nantes"`) and
optionally set `radiusKm` to widen it. You can also paste a HelloWork search URL into `startUrls`.

**Can I get only CDI jobs? Only alternance? Only stages?**
Yes. `contractTypes` accepts `cdi`, `cdd`, `interim`, `stage`, `alternance`, `freelance`,
`independant`, `fonctionnaire`, `franchise`, `associe` and `stage-lycee`, and you can combine them.

**Does it return salaries?**
For 63% of postings, yes, and that figure is the employer's own. For another 19% HelloWork supplies
an estimate instead, which this actor returns with `salaryIsEstimate: true` so you never mistake a
guess for an offer. 18% of postings have no salary at all.

**How many jobs can I get from one search?**
As many as HelloWork has. It paginates a 24,879-result search to page 830. In practice, set
`maxItems` to what you need and split very large pulls across several keywords or cities so they run
in parallel.

**How much does 1,000 jobs cost?**
$0.49, plus the $0.001 start fee for the run.

**Is it fast?**
A 236-row run with full posting detail took 40 seconds. Turn off *Open each posting* and 247 rows
took 19 seconds.

**Can I schedule it?**
Yes, use Apify Schedules. `postedWithin: "day"` with `sortBy: "date"` gives you a clean daily feed
of new French postings.

**What happens if a search returns nothing?**
You get one uncharged diagnostic row explaining why, and the run is not billed for it.

**Can I export to CSV or Excel?**
Yes. The dataset exports to JSON, CSV, Excel, XML and HTML, and it's readable through the Apify API.

# Actor input Schema

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

One or more searches, exactly as you would type them into HelloWork. French works best ("développeur", "comptable", "aide soignant"), English job titles also match. Each keyword is walked separately, so \["développeur", "data analyst"] runs two searches.

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

City, department or region in France, for example "Paris", "Lyon", "Bordeaux" or "Île-de-France". Leave empty to search the whole country.

## `radiusKm` (type: `integer`):

Search radius around the location, in kilometres. Only used when a location is set.

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

Restrict to one or more French contract types. Leave empty for all of them.

## `telework` (type: `array`):

Restrict to HelloWork's own télétravail bands. Note that only about 18% of postings carry any remote tag at all, so filtering here narrows results hard.

## `workingTime` (type: `array`):

Full time, part time, or postings that do not say.

## `experience` (type: `array`):

HelloWork's own experience bands.

## `employerType` (type: `array`):

Who is hiring: the company itself, a recruitment agency, a consultancy (ESN), a temp agency or a training centre.

## `postedWithin` (type: `string`):

Only postings published inside this window. Every row also carries the posted date, so you can filter afterwards too.

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

Only postings at or above this yearly salary, using HelloWork's own salary filter. Careful: HelloWork applies it to its own salary estimate as well as to employer-published figures, so most matches still have no published salary. Combine it with "Only postings that show a salary" if you want real numbers.

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

Ask HelloWork for postings that display a salary. Measured on a 35,033-result search, this cut it to 27,696 and every card on the first page carried an employer-published figure. Leaving it off returns more rows but only about 63% of them have a salary.

## `sortBy` (type: `string`):

HelloWork's own ordering: its relevance ranking, or newest first.

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

Paste HelloWork URLs instead of building a search. Works with a single posting (https://www.hellowork.com/fr-fr/emplois/82120782.html) and with a search page (https://www.hellowork.com/fr-fr/emploi/recherche.html?k=comptable\&l=Lyon).

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

Reads each posting's own page, which adds the exact posted date and expiry date, postal code and region, sector, required skills, education level, the company's HelloWork profile, the job reference, and HelloWork's salary estimate when the employer published none. Turn this off for a faster, listing-only run: you keep title, company, location, contract, salary and the approximate posted date.

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

Adds the complete posting text as HTML and as plain text, plus the requirements section. Needs "Open each posting" to be on. It comes from the same request, so it costs no extra time and no extra charge - it only makes the rows larger.

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

Stop after this many postings. HelloWork serves at most 74 pages (2,220 postings) per individual search, so use several keywords, cities or filters to go beyond that.

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

Leave as is. HelloWork answers the plain Apify address, so no proxy is needed; supply your own servers here only if you have a reason to.

## Actor input object example

```json
{
  "searchQueries": [
    "développeur",
    "comptable"
  ],
  "location": "Paris",
  "radiusKm": 20,
  "contractTypes": [],
  "telework": [],
  "workingTime": [],
  "experience": [],
  "employerType": [],
  "postedWithin": "",
  "minSalary": 40000,
  "onlyWithSalary": false,
  "sortBy": "relevance",
  "startUrls": [
    {
      "url": "https://www.hellowork.com/fr-fr/emploi/recherche.html?k=comptable&l=Lyon"
    }
  ],
  "includeDetails": true,
  "includeDescription": false,
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per job posting in the default dataset: title, company, city and department, contract type (CDI, CDD, interim, stage, alternance, freelance), employer-published salary, remote flag, exact posted date and the job URL. Empty or blocked runs return a single uncharged row instead.

# 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": [
        "développeur"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/hellowork-jobs-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": ["développeur"] }

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/hellowork-jobs-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": [
    "développeur"
  ]
}' |
apify call dami_studio/hellowork-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/hellowork-jobs-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/ZuWJvC6N9ce7JQUv1/builds/2CxTgVPeLEQYrpFCs/openapi.json
