# Welcome to the Jungle Scraper: Jobs and Hiring Signals (`amitaviv/welcome-to-the-jungle-scraper`) Actor

Scrape Welcome to the Jungle job ads by keyword, filters or pasted search URL, with full descriptions and a new-jobs-only mode. A second mode returns one hiring-signal record per company: open jobs by department, roles opened since your last run, tech tools, headcount. Pay per delivered record.

- **URL**: https://apify.com/amitaviv/welcome-to-the-jungle-scraper.md
- **Developed by:** [Amit Aviv](https://apify.com/amitaviv) (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.96 / 1,000 job delivereds

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

## Welcome to the Jungle Scraper: Jobs and Hiring Signals

Returns job ads from welcometothejungle.com for keywords and filters or from company page URLs, and, in a second mode, one hiring-signal record per company on a watchlist: open jobs by department and contract, roles opened since your previous run, tech tools, headcount and website. It uses plain HTTP, so 25 jobs with full details take about 5 seconds; you pay only for delivered records, and a run that delivers nothing because of an error ends as Failed, never as a green empty run.

### What you get

**Jobs mode** (one record per job ad)

- Title, company, contract type and duration, remote policy, city, region, country, coordinates, every listed location
- Salary range, currency and period, minimum experience, education level, language of the ad, publication date
- Category, sub-category, profession, sectors, summary, key missions, candidate profile, benefits
- With **Include full details** (on by default, same price): full description as text and HTML, apply URL, skills, tools, office address, company website, last update
- Company size, creation year and labels

**Hiring signals mode** (one record per company)

- Open jobs, counted by department, contract type and country; jobs published in the last 30 days; the five latest roles
- From the second run on: roles opened since your previous run (listed), roles closed, open jobs at the previous run
- Tech tools (also grouped by category), sectors, headcount, size band, headquarters, labels, website and website domain

Both modes: JSON, CSV or Excel, or through the API, plus a run summary (`OUTPUT_SUMMARY` in the run's key-value store) saying what was requested, delivered and charged, which inputs failed or were ignored, and why the run stopped.

What it does not return: the site's recruiter, contact and team fields; email addresses and phone numbers in ad texts (the sentence holding one is replaced by `[contact details removed]`, and so is a line that is only a titled name such as "Mme Dupont Claire" and a sentence that names a contact after a cue such as "auprès de M. …" or "contacter Mme …"); the recruitment-process text (it usually names the interviewers); team and video content, logos, applicant data, anything behind a login. A name that an employer writes elsewhere in the ad text can remain: in a 2,000-job run with full details (`DzQLLIVRYFcVDPbIj`) no email address or phone number was left and one ad still named a staff member ("auprès de M. …", the pattern removed since build 0.1.17).

### How to use

**Jobs**

1. Type keywords and set the optional filters (country, contract, remote, published within N days). Or paste a company page URL from welcometothejungle.com (`/fr/companies/doctolib`, any edition: `/en/`, `/fr/`, `/en-GB/` …) to get every job of that company; if you search or filter on the company's **Jobs** tab first, the URL in your browser carries that search and the actor returns the same jobs.
2. Enter them in **Jobs mode: keywords or Welcome to the Jungle URLs**. Several entries are fine. The filter fields apply to keywords; a URL carries its own filters.
3. Set **Max jobs per keyword or URL** and press Start.

**Hiring signals**

1. Set **Mode** to Hiring signals and list your companies: exact name (`Back Market`), slug (`back-market`), company page URL or website domain (`doctolib.com`).
2. Press Start, then schedule the same input (daily or weekly). Every run after the first tells you which roles each company opened and how many it closed since the previous run.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `mode` | `jobs` or `companies` | `jobs` | What to return. If only `companies` is filled, hiring signals mode is used. |
| `queries` | list of text | none | Jobs mode. Keywords, or company page URLs of welcometothejungle.com with or without a search made on the company's Jobs tab (see "Pasted URLs" under Limits). Site URLs without https:// are accepted; any other text is a keyword, also with a dot (`node.js`). Duplicates are ignored; URLs of other sites and URLs with a filter the actor does not understand are not run and are named in the status message. Empty with filters set = every job matching the filters. |
| `companies` | list of text | none | Hiring signals mode. Name, slug, company page URL or website domain, one per line. |
| `countryCodes` | list of text | all | Jobs mode, keywords. Two-letter codes: FR, GB, US, DE, ES … (`UK` is read as `GB`). Anything else, such as `France`, fails the run with a message before any request. |
| `contractTypes` | list | all | Jobs mode, keywords. `full_time`, `part_time`, `internship`, `apprenticeship`, `temporary`, `freelance`, `vie`, `graduate_program`, `volunteer`, `other`. Any other value fails the run with a message before any request. |
| `remote` | list | any | Jobs mode, keywords. `fulltime`, `partial`, `punctual`, `no`, `unknown`. Any other value fails the run the same way. |
| `publishedWithinDays` | number | no limit | Jobs mode, keywords. Only jobs published in the last N days. |
| `sortBy` | `relevance` or `newest` | `relevance` | Relevance is limited to 1,000 jobs per entry by the site. Newest first has no limit and is used automatically above 1,000 per entry and for monitoring. |
| `includeDetails` | yes/no | yes | Jobs mode. Full description, apply URL, skills, tools, address, company website (one more request per delivered job). |
| `maxItemsPerQuery` | number | 100 | Jobs mode. Stop after this many jobs for each entry. |
| `language` | `en`, `fr`, `es`, `cs`, `sk` | `en` | Site edition: language of category, sector and size labels and of the URLs. Ads stay in the language they were written in. |
| `onlyNew` | yes/no | no | Jobs mode monitoring: see "Monitoring". |
| `monitorKey` | text | empty | Name of the memory, to keep two schedules or two watchlists apart. |
| `proxyConfiguration` | proxy | off | Not needed for this site. Turn on only if your runs are blocked. |

Jobs input that was run (run `E9nsT0ApahjEjQOWW`, 25 jobs in 7 s):

```json
{
  "queries": ["data engineer"],
  "countryCodes": ["FR"],
  "remote": ["partial", "fulltime"],
  "publishedWithinDays": 30,
  "maxItemsPerQuery": 25
}
```

Hiring signals input that was run (run `FXbQtr4gs0flB8I2C`, 4 companies in 5 s; the fifth entry was not found, not charged, and named in the status message):

```json
{
  "mode": "companies",
  "companies": ["doctolib", "backmarket.com", "qonto", "mirakl", "nosuchcompany-xyz"]
}
```

### Example result

A job from run `Upp7tdX93M01ohJ5i` on 2026-10-13 (the URL input shown as `query`). Texts longer than 150 characters and lists longer than 4 entries are cut here with `…`; nothing else is edited.

```json
{
  "id": "2b6b9520-e9e4-4b23-bb72-1d31e20d521a",
  "url": "https://www.welcometothejungle.com/fr/companies/securitesociale/jobs/1-comptable-f-h-en-cdd-a-temps-plein_montreuil",
  "title": "1 Comptable (F/H) en CDD à temps plein",
  "companyName": "La Sécurité Sociale",
  "companySlug": "securitesociale",
  "companyUrl": "https://www.welcometothejungle.com/fr/companies/securitesociale",
  "contractType": "temporary",
  "contractDurationMinMonths": 5,
  "contractDurationMaxMonths": 5,
  "remote": "fulltime",
  "city": "Montreuil",
  "region": "Ile-de-France",
  "country": "France",
  "countryCode": "FR",
  "latitude": 48.86827,
  "longitude": 2.43453,
  "locations": [{ "city": "Montreuil", "region": "Ile-de-France", "country": "France", "countryCode": "FR" }],
  "salaryMin": 24000,
  "salaryMax": 29000,
  "salaryCurrency": "EUR",
  "salaryPeriod": "yearly",
  "experienceMinYears": 1,
  "educationLevel": "bac_2",
  "language": "fr",
  "publishedAt": "2026-10-13T13:43:11Z",
  "category": "Affaires et finance",
  "subCategory": "Comptabilité",
  "profession": "Comptable",
  "sectors": [
    "Santé",
    "Administration publique",
    "SocialTech / GreenTech"
  ],
  "summary": "Rejoignez l'UGECAM Ile de France, un acteur clé de la santé publique en France. En tant que comptable, vous serez responsable de la gestion des relevé…",
  "keyMissions": [
    "Traiter les relevés bancaires, tenir le journal de trésorerie et réaliser les états de rapprochement financier.",
    "Lettrer les comptes de tiers, faire les ajustements de solde, contrôler et valider les factures fournisseurs.",
    "Participer aux clôtures et au contrôle interne, suivre les créances clients de plusieurs établissements."
  ],
  "profile": "Diplôme et expérience requis :\n\nBac + 2 dans le domaine comptable exigé\n\nOu une expérience équivalente",
  "benefits": [
    "Horaires de travail flexibles",
    "Entre 1-2 jours de télétravail",
    "Équipe dédiée à la Diversité & inclusivité",
    "RTT / Jour de repos",
    "…"
  ],
  "companyEmployees": 145000,
  "companyCreationYear": 1945,
  "companyLabels": [],
  "description": "Contexte\nL'UGECAM Ile de France, organisme de l'assurance maladie, est un important acteur régional de santé publique qui gère 19 établissements sanit…",
  "descriptionHtml": "<p><strong>Contexte</strong></p><p>L&#39;UGECAM Ile de France, organisme de l&#39;assurance maladie, est un important acteur régional de santé publiqu…",
  "applyUrl": "https://www.aplitrak.com/?adid=dWdlY2FtaWRmLjY3NDUxLjkzODBAdWNhbnNzLmFwbGl0cmFrLmNvbQ",
  "skills": [
    "Accounting"
  ],
  "tools": [
    "Excel"
  ],
  "startDate": "2026-10-12T22:00:00Z",
  "updatedAt": "2026-10-13T13:43:12.025227Z",
  "address": "4 Place du Général de Gaulle, 93100 Montreuil, France",
  "companyWebsite": "https://www.securite-sociale.fr/accueil",
  "detailsFetched": true,
  "query": "https://www.welcometothejungle.com/fr/jobs?query=comptable&refinementList%5Boffices.country_code%5D%5B%5D=FR&sortBy=mostRecent",
  "scrapedAt": "2026-10-13T14:08:45.406Z"
}
```

| Field | Meaning |
|---|---|
| `id` | The site's own id of the ad; stable between runs. |
| `contractType`, `remote`, `educationLevel` | The site's codes (e.g. `full_time`, `partial`, `bac_5`). |
| `salaryMin`, `salaryMax`, `salaryPeriod` | As published; many ads publish no salary (19 of 31 did in run `bIlQd31OnFOgLq7GZ`). |
| `profile`, `summary`, `description` | Text. `descriptionHtml` keeps the ad's formatting. |
| `applyUrl` | The employer's application form when the ad links to one, otherwise the job page (where the application form is). |
| `detailsFetched` | `false` when details were turned off, the job's page was already gone, or its request still failed after the retries; the fields from `description` to `companyWebsite` are then empty, except `applyUrl`, which is the job page. Such a job is delivered and charged, and the status message and `OUTPUT_SUMMARY` say how many there were. |
| `companyUrl` | The company's page. With `language` `es`, `cs` or `sk` it is the English page: the site has company pages in English and French only. |
| `query` | The keyword or URL that returned the job. |

A company from run `FXbQtr4gs0flB8I2C` (first run for this watchlist; `techTools`, `techToolsByCategory` and `latestJobs` shortened):

```json
{
  "id": "8GNWxLL",
  "slug": "doctolib",
  "name": "Doctolib",
  "url": "https://www.welcometothejungle.com/en/companies/doctolib",
  "website": "https://about.doctolib.com/",
  "websiteDomain": "about.doctolib.com",
  "sectors": ["Mobile Apps", "Software", "SaaS / Cloud Services", "Health"],
  "employees": 3000,
  "sizeBand": "> 2,000 employees",
  "headquartersCity": "Levallois-Perret",
  "headquartersCountryCode": "FR",
  "labels": ["bcorp"],
  "techTools": ["Python", "Ruby", "Java", "React JS", "Amazon RDS", "Swift (iOS)", "React Native", "Amazon Redshift", "…"],
  "techToolsByCategory": { "backend": ["Python", "Ruby", "Java", "Kotlin"], "frontend": ["React JS", "TypeScript"], "…": [] },
  "openJobs": 84,
  "jobsByDepartment": { "Tech & Engineering": 35, "Sales & Customer Service": 18, "Project / Product Management": 11, "Communication & Marketing & Advertising": 7, "HR / People": 5, "Business & Finance": 3, "Research & Development": 1, "Security & Defense": 1, "Healthcare": 1, "Culture & Arts": 1, "Hospitality": 1 },
  "jobsByContract": { "full_time": 55, "internship": 29 },
  "jobsByCountry": { "FR": 84 },
  "jobsPublishedLast30Days": 63,
  "latestJobPublishedAt": "2026-10-12T22:01:16Z",
  "latestJobs": [
    { "id": "0d89d397-8058-43db-805d-95a29d1c7f46", "title": "IA Engineer Intern (x/f/m) - Paris", "contractType": "internship", "department": "Tech & Engineering", "city": "Paris", "countryCode": "FR", "remote": "partial", "publishedAt": "2026-10-12T22:01:16Z", "url": "https://www.welcometothejungle.com/en/companies/doctolib/jobs/ia-engineer-intern-x-f-m-paris_paris" }
  ],
  "isFirstRun": true,
  "previousRunAt": null,
  "openJobsAtPreviousRun": null,
  "newJobsCount": null,
  "newJobs": [],
  "closedJobsCount": null,
  "matchedBy": "name",
  "input": "doctolib",
  "scrapedAt": "2026-10-13T10:08:14.529Z"
}
```

| Field | Meaning |
|---|---|
| `openJobs` | Jobs of the company in the site's job search at the time of the run. |
| `jobsByDepartment`, `jobsByContract`, `jobsByCountry` | Counts over the open jobs (a job with offices in two countries counts in both). Computed over at most 1,000 jobs per company. |
| `isFirstRun` | `true` when there is no earlier run to compare with; `newJobsCount` and `closedJobsCount` are then `null`. |
| `newJobsCount`, `newJobs` | Roles that were not open at your previous run (`previousRunAt`). `newJobs` lists up to 50, newest first, in the same shape as `latestJobs`. |
| `closedJobsCount` | Roles open at the previous run that are gone now. |
| `techTools`, `sizeBand` | From the company's profile on the site; empty when the company has not filled them in. |
| `slug`, `url` | The slug the company's job pages use. A company the site has renamed can have a second slug; both lead to the same record (same `id`). |
| `matchedBy` | How the entry was matched: `slug` (a company page URL, or a slug that is not also the name), `name` (exact) or `domain` (the website on the company's profile). |

Fields the site does not provide are `null` (or an empty list), never missing.

### Pricing

Pay per event. You pay for what is delivered, not for time or memory.

| Event | Free | Bronze | Silver | Gold and above |
|---|---|---|---|---|
| Run start (once per run) | $0.003 | $0.003 | $0.003 | $0.003 |
| Job delivered (details included) | $0.00144 | $0.0012 | $0.00108 | $0.00096 |
| Company hiring-signal record | $0.0048 | $0.004 | $0.0036 | $0.0032 |

Worked examples on the Bronze plan:

- 1 search, 500 jobs with full details: 500 × $0.0012 + $0.003 = **$0.603**.

- A daily "only new" run that finds 12 new jobs: 12 × $0.0012 + $0.003 = **$0.0174**; a day with nothing new costs the start event, **$0.003**.

- A watchlist of 200 companies, checked weekly: 200 × $0.004 + $0.003 = **$0.803** per run.

- Records that could not be delivered are not charged: an unknown company, an empty search or a failed entry costs nothing beyond the run start. A job returned by two of your entries is delivered and charged once, and counts towards the maximum of both.

- The run start is charged by the platform each time the run's container starts, so a run you abort and resurrect pays it twice (run `a2AuVDGk53bhY3aco`: 2 starts, 1,500 jobs, none charged twice).

- The maximum total charge you set for a run is respected: the actor stops before it would go over, keeps what was delivered, and says so in the status message (run `rMqQ05aOYTyONX3cY`: limit $0.0402 = start + 31 jobs, exactly 31 delivered and charged).

### Reliability and failure behaviour

| What happened | Run status | Status message |
|---|---|---|
| Records delivered | Succeeded | `Scraped 25 jobs from 1 query` |
| The site has no jobs for your search | Succeeded | `0 results: Welcome to the Jungle has no jobs for this query` |
| Some entries worked, some failed or were not found | Succeeded | `Scraped 4 companies from 5 queries — 1 of 5 queries failed ("nosuchcompany-xyz": no company named exactly "nosuchcompany-xyz" on Welcome to the Jungle. …)` |
| Some entries had no jobs or could not be used | Succeeded | `Scraped 15 jobs from 2 queries — 1 of 2 queries had no results ("…"); 1 input entry ignored ("https://www.linkedin.com/jobs": not a welcometothejungle.com URL)` |
| Nothing delivered because of errors or blocking | **Failed** | `No jobs delivered: Welcome to the Jungle blocked every attempt; try again later. No results were charged.` |
| Input the actor cannot use | **Failed**, in about a second | `Nothing to do: the input has no "queries". Add at least one entry to "queries", e.g. …` or `"France" in "countryCodes" is not a two-letter country code: …` |
| Details were on and some job pages were gone or could not be fetched | Succeeded | `Scraped 8 jobs from 1 query — 2 jobs delivered without details (the page was gone or could not be fetched)` (if no detail of a page of results can be fetched, the entry fails instead and nothing is charged for it) |
| Maximum charge reached | Succeeded | `Scraped 31 jobs from 2 queries — stopped at the maximum charge set for this run` |
| Maximum charge below the price of one record | **Failed**, before any request | `The maximum charge set for this run ($0.0035) is below the price of one job, …` |
| Run timeout close | Succeeded | `… — stopped just before the run timeout; raise the timeout to get the rest` |

- Requests are retried up to 3 times with increasing pauses on network errors, HTTP 429, 5xx and refusals.
- The site's search settings are read at the start of every run. If that one request is refused after its retries, the settings last known to work (built into the actor) are used and the log says so; if the search then refuses them too, the run ends Failed.
- Records are saved page by page (100 jobs). A run that is aborted, times out or is resurrected keeps what was delivered and does not deliver or charge the same record twice.
- `OUTPUT_SUMMARY` is written after every delivered page with status `RUNNING` and replaced at the end. A run you abort gracefully ends with status `ABORTED` in it (run `dyJCdifVtQRtNGJUb`: 100 delivered and charged); a run aborted immediately keeps the last one written (run `t834x75RXBwJ7ztMI`, aborted at 100 jobs: status `RUNNING`, 100 delivered and charged; resurrected, it ended with 400 different jobs, 400 charged).
- Measured on 2026-10-13 from the Apify platform without a proxy: in the last test round (19 runs on build 0.1.17, about 1,900 requests to the site) no request was refused or failed. Across the roughly 140 test runs made that day, one request was refused and went through on its first retry (run `TOOr9mkfBWIR8gab3`), and 1 run failed for a reason on the site's side (run `h6ameRreePnJfo1nP`: the settings request came back without the search settings; the run ended Failed with no results charged). That request is now retried and has the fallback above; the fallback itself has only been exercised in offline tests. Refused requests are named in the run log.
- The site changed its public pages on 2026-10-13 (see "Pasted URLs" below). The search and job data this actor reads kept working through that change; if the site retires them, runs will fail with a message, not return empty data.

### Limits

- In relevance order the site serves at most 1,000 jobs per search. Ask for more than 1,000 per entry (or choose newest first) and the actor follows the publication date instead, which has no cap: run `OjoH3KpamzH7cf5Td` delivered 2,500 internships in France, all different, in 11 seconds (without details).
- Speed: about 400 jobs per minute with full details (2,000 in 305 s, run `DzQLLIVRYFcVDPbIj`; 1,200 in 181 s, run `UBy1fRlmRMnqpDAWJ`); 1,000 jobs in 6 s without details (run `Bmh36j25JO2SfA7l2`). About one company per second in hiring signals mode.
- Salary, experience, education level, skills and tools are empty when the employer did not fill them in.
- Pasted URLs. Since 13 October 2026 the site's own job search page (`/jobs?query=…`) no longer shows results to visitors who are not signed in; the search that is left in the open is the one on each company's **Jobs** tab (`/fr/companies/<company>/jobs`, which the site now shows as `/companies-v1/`). The actor reads the URLs that page writes, checked on 2026-10-13 against the requests the site itself sends: keyword (`query`), job type, remote policy, profession, experience, contract duration, ad language, the "title only" switch (`searchTitle`), sort by date (`sortBy=mostRecent`) and a city with its radius (`aroundLatLng` with `aroundRadius` in kilometres, as the site writes it; 20 km when the URL has none). For four such searches the actor's request matched exactly as many jobs as the site's (27 to 41,048). The same parameters on an old `/jobs?…` URL are read the same way, over all companies. Run `MZJxoQZ5yfBgkpsUL`: a company URL with job type, experience and sort returned that company's 28 matching jobs; run `IKBvaQYvjWpYBrQDd`: a 5 km radius around Paris.
- A URL that filters on a region (`refinementList[offices.state]`) is not run: the site writes the region in the page's language (Bretagne) and searches another name (Brittany), which the actor cannot translate (run `FtaoHcKi0bwhjr10b`). A URL with any other filter the actor does not understand, or a filter value the site has no code for, is not run either (runs `4FOeu6ma1ul52IA7z`, `XJ6wlxiLANQIKPIxb`). In each case nothing is charged for the entry and the status message names the reason, because leaving a filter out would deliver a wider search than you asked for. A salary filter was not found on the site on 2026-10-13; `range[salary_yearly_minimum][min]=60000` on a URL is still read as a minimum yearly salary (run `PwWqMvVZNWF6Alulb`).
- Newest-first order (used for more than 1,000 jobs per entry and for monitoring) matches multi-word keywords a little more loosely than relevance order: on 2026-10-13 `data engineer` matched 9,298 jobs by relevance and 9,812 newest first; a single word matched the same number in both.
- Hiring signals: a company must match by slug, exact name (ignoring case, accents and punctuation) or website domain. The domain must be the host of the website on the company's profile, or its parent domain when the profile's name is that domain's name (`doctolib.com` matches Doctolib's `about.doctolib.com`). So `backmarket.fr` does not match a profile that says `backmarket.com`, and `bnpparibas.com` does not match BNP Paribas Real Estate at `realestate.bnpparibas.com`. When several different companies match, the only one with open jobs is taken; otherwise the entry fails, uncharged, with the candidates and their slugs named (run `NoobgHWpX93ZieqwP`). Counts by department cover at most 1,000 open jobs per company.
- Coverage is what Welcome to the Jungle lists: mostly France, plus the United States, United Kingdom, Canada, Germany, Spain and others.

### Monitoring

**New jobs only (jobs mode).** Turn on **Only new jobs**, save the input as a task and schedule it.

- The first run delivers the newest jobs of each search, up to your maximum, and remembers them. Older jobs below that maximum are treated as backlog and are not delivered by later runs.
- Every later run delivers and charges only jobs it has not delivered: those published since the previous run, plus jobs that reached the site's search late (it looks back up to 3 hours for those, but never further back than the oldest job of a first run that was cut by the maximum). Five runs over 9 minutes on all jobs in France with a maximum of 30 (`fmVaOQoCWLzJ9xC4s`, `pJsyDMbdQpFsMBBj1`, `gepPIWNhs2Y1EUjGn`, `glgKPRQF6WObevEMG`, `X4OYUv8exULBHjJPV`): 30 jobs, then 2, 0, 2 and 1, every one published after the first run's newest job, none twice. Up to build 0.1.16 a later run could deliver jobs older than the first run's oldest on a busy search; that is fixed in 0.1.17.
- If more new jobs appear between two runs than your maximum, the newest are delivered and the next run delivers the rest.
- Entries of one input that share jobs do not repeat each other's jobs in later runs. Runs `Jh6ptQd2HqDz49zMM` and, 12 seconds later, `kfhNibEREX4f2xd8l` (`data engineer` and `data analyst`, 40 each): 65 jobs (15 matched both keywords), then 1 job, which the site's search had not listed at the first run.
- A run with nothing new ends as Succeeded with `0 new jobs in 1 monitored query (no results charged)` and costs only the run start.
- A run that is aborted and resurrected continues where it stopped (run `VZ5MaHFRDEo3bi5rf`: 298 jobs before the abort, 500 different jobs in total, none charged twice).

**Hiring signals.** Every run delivers one record per company; from the second run on it contains the roles opened and closed since the previous run. The comparison point moves forward only when a company's record was delivered.

The memory for both is a key-value store named `welcome-to-the-jungle-scraper-monitor` in your own account (one record per search or company and monitor name; up to 10,000 job ids per search). Delete the store or change **Monitor name** to start over. To be notified, add an integration to the task (email, Slack, webhook) on "run succeeded".

### FAQ

**Do I need a proxy?** No. All test runs without a proxy reached the site from the Apify platform. With Apify Proxy turned on the actor also works (run `CPsnfAy9k1q0prseq`), only slower.

**Why did I get fewer jobs than the site shows?** In relevance order the site stops at 1,000 per search; choose newest first or raise the maximum above 1,000. A job matched by two of your entries is delivered once and counts towards the maximum of both.

**Why is a company "not found" when it is on the site?** Names must match exactly, and a name or domain shared by several companies is not guessed. The status message and `OUTPUT_SUMMARY` list the closest companies with their slugs; use the slug.

**Can I run it from code?** Yes: the API, the Apify clients for JavaScript and Python, and the integrations Apify offers. The Output tab shows the URLs.

**What happens when the site changes?** Runs fail with a message instead of returning empty data. Report the run id under Issues.

### Legal and responsible use

This actor collects job advertisements and company profile data that Welcome to the Jungle shows publicly without a login. It is built to leave people out: recruiter, contact and team fields of the source are not read, the recruitment-process text is left out because it usually names interviewers, and in ad texts a sentence that holds an email address or phone number, a sentence that names a contact after a cue such as "auprès de M. …", and a line that is only a titled name are replaced by `[contact details removed]`. In a 2,000-job run with full details (`DzQLLIVRYFcVDPbIj`) and a 1,200-job run (`UBy1fRlmRMnqpDAWJ`) no email address or phone number was left in any field. This removal works on patterns, so it is not a guarantee: free text written by an employer can still mention a member of its staff by name (one ad in the 2,000-job run did, before the cue rule was added). If you find one, treat it as personal data.

Welcome to the Jungle's terms of use prohibit automated extraction of its content, and its job and company database may be protected by copyright and database rights. You are responsible for how you use this actor and the data: check whether your use is permitted, do not republish ads or rebuild the site's database, keep volumes to what you need, and comply with data-protection law (GDPR and others) in your country and the country of the data. Do not use the output for unsolicited marketing to individuals. If you are unsure, ask a lawyer before you build on it.

### Related actors

- [Company Hiring Tracker](https://apify.com/amitaviv/company-hiring-tracker): the hiring-signals idea for companies that are not on Welcome to the Jungle. It reads their own job boards (Greenhouse, Lever, Ashby, Workable, Personio, Teamtailor, Workday and more) from a domain or careers page and reports open, new and closed roles per company.
- [SEEK Jobs Scraper](https://apify.com/amitaviv/seek-jobs-scraper): job ads from Australia and New Zealand, with salary parsed into numbers and searches past SEEK's 500-result cap.
- [Europages Company Scraper](https://apify.com/amitaviv/europages-company-scraper): European B2B companies by industry keyword and country (address, phone, website, company email), to build the list of companies you then watch for hiring.

### Changelog

- 2026-10-13, build 0.1.17: new-jobs monitoring no longer delivers jobs older than the first run's oldest on busy searches; pasted URLs are read as the site writes them today (company Jobs tab, `/companies-v1/`, radius in kilometres, experience and duration ranges, profession, title-only); invalid filter values fail with a message; the status message counts jobs delivered without details; a contact named after "auprès de M. …" is removed from ad texts.
- 2026-10-13, 0.1: first version. Jobs mode with details, filters, URLs, newest-first paging past 1,000 and new-jobs monitoring; hiring signals mode.

### Support

Open an issue on the actor's **Issues** tab with the run id.

# Actor input Schema

## `mode` (type: `string`):

Jobs: one record per job ad, from the keywords or URLs below. Hiring signals: one record per company in the Companies list, with its open jobs counted by department and contract and the roles opened since your previous run. If only the Companies list is filled, hiring signals mode is used. Default: jobs.

## `queries` (type: `array`):

Keywords (e.g. data engineer, node.js) or URLs copied from welcometothejungle.com: a company page URL returns every job of that company, and the URL of a search made on a company's Jobs tab (keyword, job type, remote, profession, experience, city with radius, sort) returns the jobs that search shows. The filters below apply to keywords; a pasted URL carries its own filters, and a URL with a filter the actor does not understand is not run and not charged. Leave empty and set filters to get every job that matches them.

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

One company per line: its exact name on Welcome to the Jungle (Doctolib), its slug from the company page URL (doctolib), the company page URL, or its website domain (doctolib.com). An entry that matches no company, or several, is not charged and is named in the status message with the closest candidates and their slugs. Each delivered company is one "company" charge.

## `countryCodes` (type: `array`):

Jobs mode, keywords only. Two-letter country codes of the job location, e.g. FR, GB, US, DE, ES (UK is read as GB). Country names are not accepted. Empty = all countries.

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

Jobs mode, keywords only. One or more contract types, e.g. \["full_time", "internship"] (full_time = permanent; temporary = fixed term; vie = French international volunteer programme). Empty = all contract types.

## `remote` (type: `array`):

Jobs mode, keywords only. One or more remote policies as the employer set them, e.g. \["fulltime", "partial"]: fulltime = fully remote, partial = some days remote, punctual = occasional remote, no = on site, unknown = not stated. Empty = any.

## `publishedWithinDays` (type: `integer`):

Jobs mode, keywords only. Only jobs published in the last N days, e.g. 7. Empty = no limit.

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

Relevance returns at most 1,000 jobs per keyword or URL (the site's own limit). Newest first has no limit; it is used automatically when you ask for more than 1,000 per query or turn on Only new jobs. Default: relevance.

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

Jobs mode. Adds the full description, apply URL, skills, tools, office address and company website from each job's page (one extra request per delivered job, same price). Default on. Turn off for a faster run with the search fields only (description, address and company website are then null, skills and tools empty, applyUrl is the job page, detailsFetched false).

## `maxItemsPerQuery` (type: `integer`):

Jobs mode. Number of jobs: stop after this many for each keyword or URL (default 100, up to 100,000). You are charged one "job" event per delivered job.

## `language` (type: `string`):

The language edition of Welcome to the Jungle to read: it sets the language of category, sector and size labels and of the job URLs. Job ads themselves stay in the language they were written in. Default: en.

## `onlyNew` (type: `boolean`):

Jobs mode. The first run delivers the newest jobs of each search, up to the maximum, and remembers them; older jobs below the maximum are not delivered later. Every later run delivers and charges only jobs it has not delivered yet: those published since the previous run, and jobs that reached the site's search late. Schedule the actor to get new jobs only. The memory is a key-value store named welcome-to-the-jungle-scraper-monitor in your account; delete it to start over.

## `monitorKey` (type: `string`):

Any short name, e.g. paris-data-daily. Runs with the same search (or the same company) and the same monitor name share one memory. Give two schedules or two watchlists different names to keep their memories apart. Default: empty (one memory per search or company).

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

Off by default: this source does not need a proxy. Turn on Apify Proxy (datacenter) only if your runs are being blocked.

## Actor input object example

```json
{
  "mode": "jobs",
  "queries": [
    "data engineer"
  ],
  "sortBy": "relevance",
  "includeDetails": true,
  "maxItemsPerQuery": 100,
  "language": "en",
  "onlyNew": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All delivered records as JSON. Jobs mode, one object per job: id, url, title, companyName, contractType, remote, city, countryCode, locations, salaryMin, salaryMax, salaryCurrency, salaryPeriod, experienceMinYears, publishedAt, category, profession, summary, benefits; with Include full details also description, descriptionHtml, applyUrl, skills, tools, address, companyWebsite. Hiring signals mode, one object per company: name, url, website, employees, sizeBand, sectors, techTools, openJobs, jobsByDepartment, jobsByContract, jobsByCountry, jobsPublishedLast30Days, latestJobs, and from the second run newJobsCount, newJobs, closedJobsCount.

## `resultsCsv` (type: `string`):

All delivered records as a CSV file.

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

What was requested, delivered and charged, which inputs failed or were ignored, and why the run stopped.

# 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 = {
    "queries": [
        "data engineer"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("amitaviv/welcome-to-the-jungle-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 = {
    "queries": ["data engineer"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("amitaviv/welcome-to-the-jungle-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 '{
  "queries": [
    "data engineer"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call amitaviv/welcome-to-the-jungle-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,amitaviv/welcome-to-the-jungle-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/g9AjKQIKhMc7zYsKY/builds/Ii6djBxgSrUmRdE6P/openapi.json
