# Welcome to the Jungle Jobs Scraper & New Job Monitor (`neverempty/welcome-to-the-jungle-jobs-scraper`) Actor

Welcome to the Jungle jobs (France, US, UK, Canada, Germany, Spain and more) by keyword, company, city, contract, remote and salary: title, company size and sector, salary as numbers, location, date and link. No 1,000-job limit. Optional full job page and only-new monitor.

- **URL**: https://apify.com/neverempty/welcome-to-the-jungle-jobs-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.89 / 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.
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 Jobs Scraper & New Job Monitor

Get **Welcome to the Jungle jobs** as clean JSON: job title, **company with size, founding year and sector**, contract type, **remote policy**, **salary as numbers** (min, max, currency, period, and the yearly minimum and maximum), experience, office city and country with coordinates, date published and the link. Search by **keyword**, **company**, **country**, **city and radius**, **contract**, **remote**, **experience**, **company size**, **sector**, **job category**, **education**, **minimum yearly salary** and **date posted**. Turn on **Monitor mode** and scheduled runs return **only jobs published since the last check**, for a job alert, a Slack bot or a hiring-signal lead list. Turn on **job page details** to add the full description, candidate profile, skills, tools, start date, contract length, number of hiring steps, and the company's website, LinkedIn, revenue and headquarters.

On 2026-10-21 Welcome to the Jungle listed **89,976 jobs**: 59,190 with an office in France, 18,264 in the US, 5,471 in the UK, 1,858 in Canada, 1,149 in Germany, 895 in Spain and more. 28,426 of them state a salary amount.

- **All the jobs, not just the first 1,000.** In newest-first order (the default), `remote data` or all of Paris comes back in full, up to `maxResultsPerSearch` (50,000).
- **Salary you can sort and filter.** `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` as stated in the ad, plus `salaryYearlyMin` and `salaryYearlyMax` so monthly, daily and yearly pay can be compared. Filter with `minYearlySalary`, `salaryCurrency` and `onlyWithSalary`.
- **Company data in every row, without extra cost.** Employees, founding year, sector, industry, logo and company page come with the basic row. With job page details you also get the company website, LinkedIn, revenue, average age, number of open jobs and headquarters.
- **Clean text.** Description and candidate profile keep their line breaks and lists; HTML is removed. Skills, tools, missions and benefits are lists.
- **New job monitor.** The first run only remembers what matches now and is free. Later runs return only jobs published since, including jobs the site indexes a few hours late. When nothing is new, the run costs one small check fee instead of a full search.
- **Each job is charged once.** Overlapping keywords and companies return each job once in a run, and a monitor does not return a job again in a later run, even under another search of the same watch. A job that closed between the search and its job page is skipped, not sold, and the next job takes its place.
- **Free rows say why.** No match, an unknown company name, nothing new, a refused or unreadable request, a search that has more jobs than you asked for, and a run that reached its maximum charge each come back as a free row with a plain-English `note`.

Unofficial. Reads the public job search and the public job pages of welcometothejungle.com, the same data a visitor sees without logging in. It does not log in, does not apply to jobs and does not solve or bypass check pages. It returns **the company, not the people**: there are no recruiter or contact person columns, the hiring process is returned as a number of steps only, and phone numbers and e-mail addresses in text are replaced with `[phone removed]` and `[email removed]`.

### What you get

One row per job. Example (job page details on, `description`, `candidateProfile`, `companyDescription` and the logo link shortened here):

```json
{
  "status": "ok",
  "changeType": null,
  "jobId": "3fd70968-dccf-47ad-852f-1d6d1966eb68",
  "jobSlug": "migration-factory-orchestrator_paris",
  "title": "Migration Factory Orchestrator",
  "url": "https://www.welcometothejungle.com/en/companies/inqom/jobs/migration-factory-orchestrator_paris",
  "publishedAt": "2026-10-20T15:57:33.000Z",
  "company": "Inqom",
  "companySlug": "inqom",
  "companyUrl": "https://www.welcometothejungle.com/en/companies/inqom",
  "companyLogoUrl": "https://cdn-images.welcometothejungle.com/...",
  "companyEmployees": 112,
  "companyFoundedYear": 2016,
  "companySummary": "Logiciel comptable et fiscal SaaS pour cabinets et directions financières.",
  "companySectors": ["Artificial Intelligence / Machine Learning", "FinTech / InsurTech", "SaaS / Cloud Services"],
  "companyIndustry": "Tech",
  "category": "Tech & Engineering",
  "subCategory": "Data / Business Intelligence",
  "profession": "Data Engineer",
  "contractType": "full_time",
  "contractDurationMinMonths": null,
  "contractDurationMaxMonths": null,
  "remote": "fulltime",
  "hasRemote": true,
  "salaryMin": 51000,
  "salaryMax": 53000,
  "salaryCurrency": "EUR",
  "salaryPeriod": "yearly",
  "salaryYearlyMin": 51000,
  "salaryYearlyMax": 53000,
  "experienceYearsMin": 4,
  "educationLevel": null,
  "language": "fr",
  "city": "Paris",
  "region": "Ile-de-France",
  "country": "France",
  "countryCode": "FR",
  "offices": [{ "city": "Paris", "region": "Ile-de-France", "country": "France", "countryCode": "FR" }],
  "latitude": 48.86764,
  "longitude": 2.32294,
  "summary": "Rejoins Inqom, une entreprise en pleine croissance dans le secteur de la technologie...",
  "keyMissions": ["Cartographier le parcours de bout en bout, formaliser les étapes, les prérequis, les contrôles et les responsabilités.", "..."],
  "benefits": ["Flexible working hours", "Cooptation bonus", "Team building", "Professional development plan", "..."],
  "sponsored": false,
  "description": "...Le poste couvre les parcours et lots standardisables...",
  "candidateProfile": "- Expérience confirmée en opérations, industrialisation de processus...",
  "applyUrl": null,
  "applyOnSite": true,
  "skills": ["Project management", "Communication skills", "Automation tools", "Mechanical skills", "Team management"],
  "tools": ["N8n"],
  "recruitmentSteps": 5,
  "startDate": null,
  "updatedAt": "2026-10-20T16:13:17.480001Z",
  "officeAddress": "8 Rue Royale, 75008 Paris, France",
  "officePostalCode": "75008",
  "canonicalUrl": "https://www.welcometothejungle.com/fr/companies/inqom/jobs/migration-factory-orchestrator_paris",
  "companyWebsite": "http://www.inqom.com",
  "companyLinkedin": "inqomapp",
  "companyIndustryTags": ["Artificial Intelligence / Machine Learning", "FinTech / InsurTech", "SaaS / Cloud Services"],
  "companyRevenue": null,
  "companyAverageAge": 37,
  "companyOpenJobs": 19,
  "companyHeadquarters": "8, Rue Royale, Paris",
  "companyHeadquartersCity": "Paris",
  "companyHeadquartersCountryCode": "FR",
  "companyDescription": "Fondé en 2016, Inqom est un logiciel moderne de production comptable et fiscale en SaaS...",
  "detailsStatus": "ok",
  "searchQuery": "data",
  "searchCompany": null,
  "watchName": null,
  "checkedAt": "2026-10-21T06:00:00.000Z"
}
```

Without job page details, a row has every column up to `sponsored` plus `salaryYearlyMax`, `detailsStatus` (null), `dataNotes`, `searchQuery`, `searchCompany`, `watchName` and `checkedAt`.

- `contractType`: `full_time` (permanent), `part_time`, `temporary`, `internship`, `apprenticeship`, `freelance`, `vie`, `idv`, `graduate_program`, `volunteer`, `other`.
- `remote`: `fulltime` (fully remote), `partial` (hybrid), `punctual` (occasional), `no` (on site), `unknown`.
- `salaryYearlyMin` is the site's own yearly figure; `salaryYearlyMax` applies the same factor to `salaryMax`. When the ad gives only a maximum, `salaryYearlyMax` uses the usual factor of its period (×12 monthly, ×218 daily) and `salaryYearlyMin` stays null. Both are null when the ad states no salary.
- `dataNotes` lists short notes when the site's own data contradicts itself (for example a minimum salary above the maximum, a minimum contract length above the maximum, or a publication time in the future). The values are shown exactly as listed, not swapped. It is null otherwise.
- `changeType` is `new` in monitor mode and null otherwise.
- `detailsStatus` is `ok` when the job page was read, `unreadable` when it could not be (the row is then returned without details and charged as a job without details), and null when details were not asked for.

### Input

- `searchQueries`: what you would type into the site's search box (`data engineer`, `product manager`, `comptable`). Each keyword is one search. Empty = every job that matches the filters.
- `companies`: company names from the company page address (`doctolib`, `back-market`) or the company page URL. Each company is searched with each keyword.
- `searchInTitleOnly`: a keyword must be in the job title.
- `siteLanguage`: language of links and of category, sector and country names (`en`, `fr`, `es`, `cs`, `sk`).
- Filters: `countries` (FR, US, GB ...), `cities` and `radiusKm`, `contractTypes`, `remote`, `experienceLevels`, `companySizes`, `educationLevels`, `jobLanguages`, `sectors`, `categories`, `minYearlySalary`, `salaryCurrency`, `onlyWithSalary`, `postedWithinDays`.
- `sortBy`: `date` (newest first, reads past 1,000) or `relevance` (the site's ranking, up to 1,000 per search).
- `maxResultsPerSearch`: default 100.
- `includeJobDetails`: read each job page for the extra columns.
- `onlyNew`, `watchName`, `resetMonitoringState`: monitor mode.

Up to 20 searches (keywords times companies) per run.

### Examples

Remote data jobs in France, newest first:

```json
{ "searchQueries": ["data engineer", "data analyst"], "countries": ["FR"], "remote": ["fulltime", "partial"], "maxResultsPerSearch": 200 }
```

Every internship in Paris and 30 km around it, posted in the last 7 days:

```json
{ "cities": ["Paris"], "radiusKm": 30, "contractTypes": ["internship"], "postedWithinDays": 7, "maxResultsPerSearch": 2000 }
```

All open jobs at two companies, with job page details:

```json
{ "companies": ["doctolib", "qonto"], "includeJobDetails": true, "maxResultsPerSearch": 500 }
```

Daily alert for well-paid product jobs in the US (schedule it once a day):

```json
{ "searchQueries": ["product manager"], "countries": ["US"], "minYearlySalary": 150000, "salaryCurrency": "USD", "onlyNew": true, "watchName": "us-pm-150k" }
```

### Pricing

Pay per event:

- **Job returned**: per job row without job page details.
- **Job with details**: per job row with job page details (`includeJobDetails`), instead of Job returned. A row whose job page could not be read is charged as Job returned.
- **Monitoring check**: in monitor mode, once per search when the check found no new job. Not charged on the first (baseline) run, when new jobs are returned, or when nothing could be read.

Free rows are never charged. If you set a maximum total charge for a run, the run stops before it would go over it and says so in a free row; job pages are only read for rows that fit.

### Monitor mode, step by step

1. First run: remembers the newest jobs that match each search (up to 1,000 per search) and returns a free `baseline-saved` row. Nothing is charged.
2. Later runs: read the jobs published since shortly before the last check (2 days of slack, so jobs the site indexes late are still caught) and return the ones this watch has not seen as `new`, up to `maxResultsPerSearch`. When there is none, the run returns a free `no-new-jobs` row and charges one monitoring check. A job returned under one search of a watch is remembered for the whole watch, so it is not returned or charged again under its other searches.
3. Jobs not returned because of `maxResultsPerSearch` or the charge limit are not remembered, so the next run returns them.
4. Nothing is judged from a search that could not be read (nothing returned, remembered or charged); the next run checks it again.

Use `watchName` to keep separate monitors apart, and `resetMonitoringState` to take a new baseline.

### Good to know

- `relevance` order follows the site's own ranking, which stops at 1,000 jobs per search; when more match, a free row says so. `date` order has no such stop.
- A monitor run reads up to 5,000 jobs per search. If more new jobs than that arrive between two checks, a free row says so and the next run continues.
- About 1 in 3 jobs states a salary amount. Without one, all salary columns are null; nothing is estimated.
- `radiusKm` needs exactly one city, and the city must be written as the site writes it (Paris, Lyon, London, New York).
- Most jobs are in French or English. `siteLanguage` changes the language of links and labels, not of the ad itself; use `jobLanguages` to pick the ad language.
- Names in free text (for example a signature at the end of a description) are not recognised; phone numbers and e-mail addresses are removed.

### Support

Questions, a field you need, or a search that does not work: open an issue on the Issues tab of this Actor.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

What you would type into the Welcome to the Jungle search box, for example data engineer, product manager, comptable, stage marketing. Each keyword is one search. Leave empty to list every job that matches the filters below.

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

Optional. Only jobs at these companies: the name from the company page address (doctolib, back-market, qonto) or the page URL itself, for example https://www.welcometothejungle.com/en/companies/doctolib. Each company is searched with each keyword.

## `searchInTitleOnly` (type: `boolean`):

On: a keyword must appear in the job title. Off (default): the site also matches the company, summary and missions, like its own search box.

## `siteLanguage` (type: `string`):

Language of the job and company links and of category, sector and country names. Every language lists the same jobs; to get jobs written in a given language use Job ad language.

## `countries` (type: `array`):

Optional. ISO two-letter country codes of the job office, for example FR, US, GB, CA, DE, ES, BE (the United Kingdom is GB, not UK). Empty = all countries.

## `cities` (type: `array`):

Optional. Office city as the site writes it, for example Paris, Lyon, Barcelona, London, New York, Prague. Empty = all cities.

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

Optional. With exactly one city: jobs within this many kilometres of it, not just in the city itself.

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

Optional. Keep only these contract types. Empty = all.

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

Optional. Keep only these remote policies. Empty = all.

## `experienceLevels` (type: `array`):

Optional. Minimum experience the job asks for. Empty = all.

## `companySizes` (type: `array`):

Optional. Keep only companies of these sizes. Empty = all.

## `educationLevels` (type: `array`):

Optional. Education level the job asks for (French levels as the site uses them). Empty = all.

## `jobLanguages` (type: `array`):

Optional. ISO two-letter language codes of the job ad itself, for example en, fr, es, de. Empty = all.

## `sectors` (type: `array`):

Optional. Keep only companies in these sectors. Empty = all.

## `categories` (type: `array`):

Optional. Keep only jobs in these job categories. Empty = all.

## `minYearlySalary` (type: `integer`):

Optional. Only jobs that state a salary whose yearly minimum is at least this amount (monthly and daily pay are converted to yearly by the site). Combine with Salary currency.

## `salaryCurrency` (type: `string`):

Optional. Only jobs paid in this currency.

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

On: skip jobs that do not state a salary.

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

Optional. Only jobs published in the last N days, for example 1 for the last 24 hours or 7 for the last week.

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

Newest first reads past the site's 1,000-result limit. Most relevant follows the site's own ranking, which stops at 1,000 jobs per search.

## `maxResultsPerSearch` (type: `integer`):

Stop each search after this many jobs. Every keyword (and company) is one search.

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

On: also read each job page for the full description, candidate profile, skills, tools, apply link, start date, contract length, number of hiring steps and company website, LinkedIn, revenue and headquarters. Charged as a job with details.

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

On: the first run only remembers the jobs that match now (free); later runs return only jobs published since. Schedule it daily or hourly for a job alert.

## `watchName` (type: `string`):

Optional. Keeps separate memories for separate monitors, for example paris-data or remote-design. Letters, digits, dot, dash and underscore.

## `resetMonitoringState` (type: `boolean`):

On: forget what this monitor remembered for these searches and take a new baseline (free).

## Actor input object example

```json
{
  "searchQueries": [
    "data engineer"
  ],
  "searchInTitleOnly": false,
  "siteLanguage": "en",
  "onlyWithSalary": false,
  "sortBy": "date",
  "maxResultsPerSearch": 100,
  "includeJobDetails": false,
  "onlyNew": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per Welcome to the Jungle job: job ID, title, link, date published, company with size, founding year, sector and logo, category and profession, contract, remote policy, salary (min, max, currency, period and yearly min/max as numbers), experience, education, office city, country and coordinates, summary, missions and benefits. With job page details: full description, candidate profile, skills, tools, apply link, start date, contract length, hiring steps, office address and company website, LinkedIn, revenue and headquarters. Monitor mode marks new jobs. A search with no jobs, nothing new, a refused request or a run that hit its maximum charge comes back as a free row that says why.

# 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": [
        "data engineer"
    ]
};

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

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

```

## MCP server setup

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