# HelloWork Scraper — France Jobs with Salary & Skills (`unfenced-group/hellowork-scraper`) Actor

Scrape hellowork.com for structured French job data: titles, companies, salaries, skills, category and full descriptions (HTML, text, Markdown). Estimated salary percentiles where available. Filter by keyword, location and contract type. Repost detection. No API key required.

- **URL**: https://apify.com/unfenced-group/hellowork-scraper.md
- **Developed by:** [Unfenced Group](https://apify.com/unfenced-group) (community)
- **Categories:** Jobs, Developer tools, Automation
- **Stats:** 35 total users, 9 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## HelloWork.fr Scraper

![hellowork-scraper](https://api.apify.com/v2/key-value-stores/ClElVyZWvQgPQIuDL/records/hellowork-scraper)

Extract structured job listing data from [hellowork.com](https://www.hellowork.com) — one of France's largest job boards, covering all industries and regions. Filter by keyword, location, and contract type. Full job descriptions in HTML, plain text, and Markdown. Built-in repost detection across runs.

***

### Features

- **Keyword & location search** — search any job title or keywords across all French regions
- **Push notifications:** get new results delivered to Telegram, Discord, Slack, WhatsApp or any webhook the moment a scheduled run finds them
- **Contract type filtering** — CDI, CDD, Interim, Stage, Alternance, Freelance
- **Salary data** — actual salary range, period and currency where the employer discloses it (returned as `null` when not published, never guessed)
- **Estimated salary percentiles** — median and p10 / p25 / p75 / p90 from HelloWork's own market data, where available
- **Skills & categorisation** — structured skills list, occupational category and industry parsed from HelloWork's JSON-LD
- **Triple description format** — HTML, plain text, and Markdown in every result
- **Cross-run repost detection** — content fingerprinting flags listings seen in previous runs
- **`daysOld` filter** — only return listings published within N days

***

### Why this scraper?

#### 🎯 Purpose-built

A dedicated scraper for this job board, tuned to its search, locations and salary formats — not a generic tool pointed at it.

#### 💶 Salary data

Salary parsed from the listing where the employer publishes it. Missing salaries return `null`, never a guess.

#### 📄 Complete job data

Full title and description, direct listing URL and publish date for every record.

***

### Input Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `searchQuery` | string | `"developer"` | Job title or keywords to search for. Example: "développeur python", "comptable". |
| `location` | string | (empty) | City, region or department. Example: "Paris", "Lyon", "Île-de-France". |
| `contractType` | string | `"CDI"` | Filter by contract type: CDI, CDD, Interim, Stage, Alternance, Freelance. Leave empty for all types. |
| `daysOld` | integer | `0` | Only return jobs posted within this many days. Set to 0 to disable. |
| `maxItems` | integer | `5` | Maximum number of job listings to return. Set to 0 for unlimited. |

#### Example inputs

**Search for developers in Paris:**

```json
{
  "keywords": "développeur",
  "location": "Paris",
  "maxResults": 100
}
```

**Recent nursing jobs on CDI contracts:**

```json
{
  "keywords": "infirmier",
  "contractType": "CDI",
  "daysOld": 14,
  "maxResults": 200
}
```

**Scrape specific job listings:**

***

### Output Schema

Each result contains the following fields:

| Field | Type | Description |
|---|---|---|
| `id` | String | HelloWork's unique listing ID |
| `url` | String | Direct URL to the job listing |
| `title` | String | Job title |
| `company` | String | Employer name |
| `location` | String | Location (city or region) |
| `postalCode` | String | Postal code |
| `country` | String | Country code (always `"FR"`) |
| `isRemote` | Boolean | `true` if the listing is remote (télétravail) |
| `contractType` | String | Contract label(s): `CDI`, `CDD`, `Interim`, `Stage`, `Alternance`, `Freelance` |
| `occupationalCategory` | String | HelloWork occupational category (e.g. `"Informatique"`) |
| `industry` | String | Employer industry / sector |
| `skills` | Array | Skills parsed from HelloWork's structured data (empty array when none listed) |
| `salaryMin` | Number | Minimum salary (null if the employer does not publish salary) |
| `salaryMax` | Number | Maximum salary (null if the employer does not publish salary) |
| `salaryPeriod` | String | Salary period: `YEAR`, `MONTH`, `WEEK`, `DAY`, `HOUR` |
| `salaryRaw` | String | Human-readable salary string (null if not published) |
| `currency` | String | Salary currency (e.g. `"EUR"`) |
| `estimatedSalary` | Object | HelloWork's estimated salary distribution (`median`, `percentile10/25/75/90`, `currency`, `period`) — `null` when not available |
| `publishDate` | String | Publication date (YYYY-MM-DD) |
| `expiryDate` | String | Listing expiry date (YYYY-MM-DD) |
| `descriptionHtml` | String | Full job description as HTML |
| `descriptionText` | String | Full job description as plain text |
| `descriptionMarkdown` | String | Full job description as Markdown |
| `source` | String | Always `"hellowork.com"` |
| `scrapedAt` | String | ISO 8601 timestamp of collection time |
| `contentHash` | String | Stable fingerprint of the listing |

***

#### Example record

```json
{
  "id": "79648393",
  "url": "https://www.hellowork.com/fr-fr/offres/79648393",
  "title": "Testeur Logiciel H/F",
  "company": "Otteo",
  "location": "Lille",
  "postalCode": "59000",
  "country": "FR",
  "isRemote": false,
  "contractType": "FULL_TIME",
  "salaryMin": 32000,
  "salaryMax": 42000,
  "salaryPeriod": "YEAR",
  "salaryRaw": "€32 000 – €42 000 / year",
  "currency": "EUR",
  "publishDate": "2026-07-02",
  "expiryDate": "2026-08-01",
  "descriptionHtml": "Les missions du posteOTTEO ?C'est une société de conseil et un cabinet de recrutement. Eh oui, « une de plus », mais att …",
  "descriptionText": "Les missions du posteOTTEO ?C'est une société de conseil et un cabinet de recrutement. Eh oui, « une de plus », mais att …",
  "descriptionMarkdown": "Les missions du posteOTTEO ?C'est une société de conseil et un cabinet de recrutement. Eh oui, « une de plus », mais att …",
  "source": "hellowork.com",
  "scrapedAt": "2026-07-02T11:13:24.139Z",
  "contentHash": "ea13c7e9d621d39d"
}
```

### 💰 Pricing

**$0.94 per 1,000 results** — you only pay for successfully retrieved listings.
Failed retries and filtered reposts are never charged.

| Results | Cost |
|---------|------|
| 100 | ~$0.10 |
| 1,000 | ~$0.94 |
| 10,000 | ~$9.40 |
| 100,000 | ~$94.00 |

> Flat-rate alternatives typically charge $29–$49/month regardless of usage.
> At 10,000 results/month, this scraper costs significantly less with no commitment.

Use the **Max results** cap in the input to control your spend exactly.

***

### Performance

| Run size | Approx. time |
|---|---|
| 100 listings | ~2 min |
| 1,000 listings | ~15 min |
| 10,000 listings | ~2.5 hours |

***

***

### Examples

**Developer jobs in Paris**

```json
{
  "keywords": "développeur",
  "location": "Paris",
  "maxResults": 50
}
```

**CDI contracts in Lyon posted this week**

```json
{
  "keywords": "ingénieur",
  "location": "Lyon",
  "contractType": "CDI",
  "daysOld": 7,
  "maxResults": 100
}
```

**Remote tech roles across France**

```json
{
  "keywords": "data scientist",
  "maxResults": 100
}
```

**Daily incremental feed**

```json
{
  "keywords": "développeur web",
  "location": "Ile-de-France",
  "daysOld": 1,
  "maxResults": 500
}
```

### Known Limitations

- **Salary availability**: Not all employers publish an exact salary. When they do not, `salaryMin`/`salaryMax` are `null` (never guessed), but HelloWork's estimated salary distribution (`estimatedSalary`, p10–p90) is provided for many roles.
- **Apply URL**: Not exposed in the HTML. Use the `url` field as the canonical apply link.

***

### Technical Details

- **Source:** hellowork.com — France's largest generalist job board
- **Memory:** 256 MB
- **Repost storage:** KeyValueStore `hellowork-job-dedup`, 90-day TTL
- **Retry:** Automatic retry on network errors, exponential backoff, 3 attempts per request

***

### Additional Services

Need a custom actor, additional filters, scheduled runs, or integration support?.nl]\(mailto:info@unfencedgroup.nl) — we build on request.

***

### Related scrapers

Other scrapers in our **Jobs — France** collection:

- [APEC.fr Scraper](https://apify.com/unfenced-group/apec-fr-scraper)
- [Cadremploi.fr Scraper](https://apify.com/unfenced-group/cadremploi-scraper)
- [France Travail Scraper — 961k+ French Jobs](https://apify.com/unfenced-group/france-travail-scraper)
- [Welcome to the Jungle Scraper](https://apify.com/unfenced-group/welcometothejungle-scraper)

***

### Run it on a schedule

This actor is built for repeat use. Set it to run daily, weekly, or hourly, and the data keeps flowing without you touching it.

- **Schedule runs** — open the actor, go to Schedules, and pick a cadence. Each run only charges you for the results it returns.
- **Connect it to your stack** — push results straight to Google Sheets, Slack, a webhook, or your database using Apify Integrations. No glue code needed.
- **Pull results via API** — every run writes a clean dataset you can fetch with one API call, ready for whatever you build on top of it.

Set it once and it runs on its own.

***

### Rate this actor

If this scraper does its job, a short review on the **Reviews** tab helps other users find it. Something not working? Open an issue on the **Issues** tab instead — issues get fixed.

***

### Need a custom scraper?

**[Unfenced Group](https://www.unfencedgroup.nl)** builds Apify actors for any website — for free.

If the site you need isn't in our portfolio yet, just ask. We scope, build, and publish it at no cost to you. You only pay for results — we absorb the compute and proxy costs ourselves. Same pay-per-result pricing, same quality, same standards as every actor in this portfolio.

**Get in touch:** [www.unfencedgroup.nl](https://www.unfencedgroup.nl)

# Actor input Schema

## `searchQuery` (type: `string`):

Job title or keywords to search for. Example: "développeur python", "comptable".

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

City, region or department. Example: "Paris", "Lyon", "Île-de-France".

## `contractType` (type: `string`):

Filter by contract type. Leave empty for all types.

## `daysOld` (type: `integer`):

Only return jobs posted within this many days. Set to 0 to disable.

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

Maximum number of job listings to return. Set to 0 for unlimited.

## `telegramToken` (type: `string`):

Bot token from @BotFather, e.g. '110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw'. Requires Telegram chat ID below.

## `telegramChatId` (type: `string`):

Chat or channel ID the bot posts to, e.g. '-1001234567890'. Get it from @userinfobot.

## `discordWebhookUrl` (type: `string`):

Discord channel webhook, e.g. 'https://discord.com/api/webhooks/%E2%80%A6'. Channel settings → Integrations → Webhooks.

## `slackWebhookUrl` (type: `string`):

Slack incoming webhook, e.g. 'https://hooks.slack.com/services/%E2%80%A6'.

## `whatsappPhoneNumberId` (type: `string`):

Meta Cloud API phone number ID, e.g. '106540352242922'. Requires access token and recipient below.

## `whatsappAccessToken` (type: `string`):

Meta Cloud API access token for the WhatsApp Business account.

## `whatsappTo` (type: `string`):

Recipient phone number in international format, e.g. '31612345678'.

## `webhookUrl` (type: `string`):

Any HTTPS endpoint. Receives a JSON POST with site, query, result counts and the newest items, e.g. 'https://example.com/hooks/jobs'.

## `webhookHeaders` (type: `object`):

Optional extra HTTP headers for the webhook request, e.g. {"Authorization": "Bearer abc123"}.

## `notificationLimit` (type: `integer`):

Maximum number of results shown in a notification message. Default 10, cap 25.

## `notifyOnlyChanges` (type: `boolean`):

Only send a notification when the run found new or changed items. Applies when incremental tracking is active; full runs with results always notify.

## Actor input object example

```json
{
  "searchQuery": "developer",
  "location": "",
  "contractType": "CDI",
  "maxItems": 100,
  "notificationLimit": 10,
  "notifyOnlyChanges": true
}
```

# Actor output Schema

## `results` (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 = {
    "searchQuery": "developer",
    "contractType": "CDI",
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("unfenced-group/hellowork-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 = {
    "searchQuery": "developer",
    "contractType": "CDI",
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("unfenced-group/hellowork-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 '{
  "searchQuery": "developer",
  "contractType": "CDI",
  "maxItems": 100
}' |
apify call unfenced-group/hellowork-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,unfenced-group/hellowork-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/V6bE1nM5jE09lTyKb/builds/apYMnLHz6DlbsoreP/openapi.json
