# Welcome to the Jungle Scraper (WTTJ) - Jobs + Apply URLs (`alexmorain/wttj-jobs-scraper`) Actor

Scrape Welcome to the Jungle job listings through the official search API. Full job details, direct ATS apply URLs, company data, 15+ filters, exports beyond the 1000-result limit.

- **URL**: https://apify.com/alexmorain/wttj-jobs-scraper.md
- **Developed by:** [Alex Morain](https://apify.com/alexmorain) (community)
- **Categories:** Jobs, Automation, Lead generation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $2.00 / 1,000 jobs

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

## Welcome to the Jungle Scraper (WTTJ): jobs, apply URLs, company data

Extract job listings from [Welcome to the Jungle](https://www.welcometothejungle.com), the leading job board in France with 88,000+ active listings across Europe and the US.

This scraper talks directly to the official WTTJ search and job APIs. No browser, no HTML parsing, no fragile selectors. That makes it fast, cheap, and stable: a full job with all details takes well under a second.

### How to scrape Welcome to the Jungle

1. Open the **Input** tab. Every field is optional and one search is already filled in, so you can press **Start** right away to see what the output looks like.
2. Replace the **Search query** with your own keyword, or clear it entirely to browse the whole corpus and filter instead.
3. Narrow with the filters: country and city, contract type, remote policy, sector, company size. They are applied by the search API before collection, so a narrow search is both faster and cheaper.
4. Set **Max jobs** to `0` when you want every matching job rather than a sample.
5. Press **Start**, then download the dataset as JSON, CSV or Excel, or plug it into the API, a webhook or an integration.

#### Which fields should I fill?

| If you want to... | Fill in | Leave empty |
|---|---|---|
| Find one type of role | `query`, `countryCodes` | everything else |
| Build a lead list of companies that hire | `sectors`, `companySizeMin/Max`, `countryCodes` | `query` |
| Feed a job board daily | `publishedWithinDays: 1`, `sortBy: newest` | `query` |
| Watch specific companies | `companySlugs` | all other filters |
| Do salary research | `salaryYearlyMin`, `professionCategories` | `query` |

Nothing is mandatory. An empty input is a valid run: it returns the most relevant 100 jobs of the whole corpus.

### What you get for each job

- **Direct ATS apply URL** (Recruitee, Lever, Greenhouse, Workday...) plus the WTTJ listing URL
- Full HTML **job description**, candidate **profile**, **recruitment process**, key missions
- **Salary** (min, max, currency, period, normalized yearly minimum)
- Contract type and duration, **remote policy**, minimum experience, education level
- **Skills and tools** extracted by WTTJ (e.g. Accounting, Slack, Notion)
- All office locations with city, region, country, and **GPS coordinates**
- Company data: name, sector, **headcount**, creation year, **website**, **LinkedIn**, description, open jobs count, headquarters address
- Which application fields the employer requires (resume, cover letter, portfolio)

### Why this scraper

- **Exports beyond 1,000 results.** The WTTJ search API caps every query at 1,000 hits. This scraper detects the cap and automatically splits the export by publication date, so `maxItems: 0` really exports every matching job, including the whole 88k corpus.
- **15+ native filters**, applied server-side so you only pay for jobs you want: keyword, country, city, GPS radius, contract type, remote policy, listing language, max required experience, education level, minimum yearly salary, company sector, profession category, company size, publication window.
- **Company mode.** Pass company slugs to export every job from specific companies, e.g. for competitive hiring intelligence.
- **Self-healing auth.** The public search key is read from the live site config at runtime, with a bundled fallback, so key rotations do not break your scheduled runs.
- **Clean, flat output.** Consistent field names, ISO dates, normalized salary, deduplicated results.

### Typical use cases

- **Lead generation**: companies that hire are companies that buy. Filter by sector, size, and city, then use the company website and LinkedIn fields to build outreach lists.
- **Job aggregation**: refresh listings hourly with `publishedWithinDays: 1` and feed your own job board.
- **Market and salary research**: salary data is normalized to yearly amounts for easy comparison.
- **Competitive hiring intelligence**: track who your competitors are recruiting with company mode.
- **ATS detection**: the apply URL reveals which ATS every company uses.

### Input example

Every field is optional. This exports all fully-remote permanent tech jobs in France paying at least 50k:

```json
{
    "countryCodes": ["FR"],
    "contractTypes": ["full_time"],
    "remote": ["fulltime"],
    "sectors": ["tech-1"],
    "salaryYearlyMin": 50000,
    "maxItems": 0
}
```

Keyword search around a GPS point:

```json
{
    "query": "data engineer",
    "aroundLatLng": "45.7640, 4.8357",
    "aroundRadiusKm": 50,
    "maxItems": 200
}
```

All jobs from specific companies:

```json
{
    "companySlugs": ["alan", "qonto", "pennylane"],
    "maxItems": 0
}
```

### Output example (shortened)

```json
{
    "id": "92252162-eb3e-4385-9fce-fe1f9f5ec0bc",
    "title": "Alternance Comptable de Copropriété",
    "url": "https://www.welcometothejungle.com/fr/companies/matera/jobs/alternance-comptable-de-copropriete_colombes",
    "applyUrl": "https://matera.recruitee.com/o/alternance-comptable-2/c/new",
    "ats": "external",
    "publishedAt": "2026-08-12T09:14:00Z",
    "contractType": "apprenticeship",
    "remote": "partial",
    "salaryText": "28000-32000 EUR/yearly",
    "skills": ["Comptabilité", "Amélioration continue"],
    "tools": ["Slack", "Notion"],
    "cities": "Colombes",
    "companyName": "Matera",
    "companyWebsite": "https://matera.eu",
    "companyLinkedin": "https://www.linkedin.com/company/matera-eu",
    "companyNbEmployees": 200,
    "companyJobsCount": 12
}
```

Download the dataset as JSON, CSV, Excel, or feed it to the API, webhooks, and integrations (Zapier, Make, Google Sheets).

### Speed and cost

Search fields only (`includeDetails: false`): about 1,000 jobs per Algolia query, thousands of jobs per minute.

With full details: around 10 jobs per second thanks to parallel detail fetching. Detail requests occasionally get rate-limited from datacenter IPs; the scraper retries automatically and you can set a residential proxy in the input as a fallback.

### Is it legal?

This scraper only collects publicly available job listings through the same public APIs the WTTJ website itself uses. It does not access any personal data, does not log in, and does not bypass any authentication. Job listings are public information that employers want distributed. Still, check the website's terms of service and your local regulations for your specific use case.

### FAQ

**Does it cover all WTTJ regional sites?** Yes. The job corpus is shared across regions. The `resultsLanguage` input picks the language for localized labels (profession names, skills).

**How fresh is the data?** The scraper queries the same live search index as the website, so results are real-time.

**Can I schedule it?** Yes, use Apify Schedules. A common setup: run daily with `publishedWithinDays: 1` to collect new listings, with deduplication handled downstream or by dataset merge.

**A filter I need is missing?** Open an issue on the actor page and it will usually ship within days.

# Actor input Schema

## `query` (type: `string`):

Free-text search across the whole listing: job title, skill, keyword, company name. <code>comptable</code>, <code>data engineer</code>, <code>product designer</code>.<br><br>Leave it empty to browse the entire corpus and rely on the filters below instead. That is the right move for lead generation, where you want every company hiring in a sector rather than one role.

## `searchOnlyInJobTitle` (type: `boolean`):

Restricts the search above to job titles only, instead of the full text of the listing.<br><br>Turn it on when a common word floods your results: searching <code>python</code> across full text returns every listing that mentions it in passing, while the title-only search returns the roles actually about it.

## `resultsLanguage` (type: `string`):

Language used for the localized labels in the results: profession names, skills, sectors.<br><br><b>It does not filter anything and does not translate the jobs</b>, which keep the language they were written in. To filter on the language of the listing itself, use <i>Job listing language</i> further down.

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

<b>Relevance</b> uses the Welcome to the Jungle ranking, best when you have a keyword. <b>Newest first</b> sorts by publication date, which is what you want for a recurring run that feeds a job board or an alert.

## `publishedWithinDays` (type: `string`):

Keeps only jobs published within the window.<br><br>One thing to know: Welcome to the Jungle refreshes the publication date when an employer renews a listing, so <i>Last 24 hours</i> still returns a healthy volume and is the natural setting for a daily scheduled run. Leave on <i>Any time</i> for a full export.

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

Filter by the country of the office, using ISO 3166-1 alpha-2 codes: <code>FR</code>, <code>US</code>, <code>GB</code>, <code>DE</code>, <code>ES</code>, <code>BE</code>, <code>NL</code>, <code>CH</code>, <code>CA</code>, <code>IT</code>, <code>PT</code>, <code>PL</code>, <code>IE</code>, <code>CZ</code>...<br><br>Leave empty to search every country. The corpus is mostly French, with real volume across Europe and the US.

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

Filter by the city of the office, matched exactly on the name as Welcome to the Jungle writes it: <code>Paris</code>, <code>Lyon</code>, <code>Marseille</code>, <code>Bordeaux</code>.<br><br>Exact match means a misspelling returns nothing rather than an approximation. When in doubt, prefer the GPS radius below, which catches the whole urban area including suburbs listed under their own name.

## `aroundLatLng` (type: `string`):

Search around a point, format <code>lat, lng</code>. Paris is <code>48.8566, 2.3522</code>, Lyon <code>45.7640, 4.8357</code>. Any map tool gives you these.<br><br>Combine with the radius below. This is the reliable way to cover a labour market: it catches the surrounding towns that a city name filter would miss.

## `aroundRadiusKm` (type: `integer`):

Radius in kilometres around the GPS point. Ignored when no GPS point is set; defaults to 20 km when one is.<br><br>Rules of thumb: 20 km for a city and its inner suburbs, 50 km for a commuting area, 100 km for a region.

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

Filter by contract type. Tick as many as you like; leave empty for all of them.<br><br>Note that <i>Permanent contract</i> covers the French CDI, and <i>Work-study / Apprenticeship</i> is the alternance, which is a large share of the French corpus.

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

Filter by remote work policy, as declared by the employer. Leave empty for all.<br><br><i>Not specified</i> is a real and frequent value, not an error: many employers simply do not fill the field. Excluding it when you filter on <i>Fully remote</i> is what makes a remote-jobs feed clean.

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

Filter on the language the listing is written in, which is a decent proxy for the working language of the team. Leave empty for all.<br><br>Different from <i>Results language</i> above, which only controls the labels in the output.

## `maxExperienceRequired` (type: `integer`):

Keeps only jobs asking for at most this many years of experience. <code>0</code> keeps only jobs open to complete beginners, <code>2</code> keeps the junior-friendly market.<br><br>Leave empty to keep every seniority level. Jobs that do not declare an experience level are kept.

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

Filter on the required education level, expressed in the French system where <code>Bac</code> is the end of secondary school and <code>Bac +5</code> a master's degree. Leave empty for all.

## `salaryYearlyMin` (type: `integer`):

Keeps only jobs with a declared yearly salary of at least this amount, in the local currency, usually EUR. The Actor normalizes monthly and daily rates to a yearly figure before comparing.<br><br><b>Careful:</b> a job without published salary data is excluded by this filter, and many are. Use it for salary research, not for exhaustive collection.

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

Filter on the industry sector of the company. Leave empty for all.<br><br>This is the main lever for lead generation: pick the sectors you sell to, and the output gives you every company in them that is currently hiring, with its website, LinkedIn and headcount.

## `professionCategories` (type: `array`):

Filter on the profession family of the job itself, which is not the same as the company sector: a developer at a bank is Tech & Engineering in Banking. Leave empty for all.

## `companySizeMin` (type: `integer`):

Keeps only companies with at least this many employees. Leave empty for no lower bound.

## `companySizeMax` (type: `integer`):

Keeps only companies with at most this many employees. Leave empty for no upper bound.<br><br>Combined with the minimum, this is how you target a segment: 20 to 200 for scale-ups, 1000 and above for large accounts.

## `companySlugs` (type: `array`):

Export every job of specific companies, whatever the other filters. One per line, using the slug from the company URL (<code>welcometothejungle.com/fr/companies/<b>alan</b></code>) or the exact company name.<br><br>This is competitive hiring intelligence: watch what your competitors, your prospects or your portfolio are recruiting for.

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

Maximum number of jobs returned.<br><br><code>0</code> exports every matching job, and it really means every one: the search API caps any single query at 1,000 results, and the Actor detects that ceiling and splits the export by publication date to walk past it. Set a number instead to keep a first run short and predictable, since you pay per job returned.

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

Fetches the full listing of every job, which adds the complete description, the candidate profile, the recruitment process, the skills and tools, the required application fields, and above all the <b>direct apply URL on the employer's own ATS</b> plus the company website and LinkedIn.<br><br>On by default because those fields are the point of the Actor. Turn it off for a fast inventory of what exists: the search fields alone (title, company, city, contract, salary, dates) come back in one pass, thousands of jobs per minute. It does not change the price.

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

Leave this off. The search API needs no proxy and the job detail requests rarely need one; when a detail request is rate-limited the Actor retries by itself. Set a residential proxy only if a large run shows repeated detail failures in the log.

## Actor input object example

```json
{
  "query": "data engineer",
  "searchOnlyInJobTitle": false,
  "resultsLanguage": "fr",
  "sortBy": "relevance",
  "publishedWithinDays": "any",
  "countryCodes": [
    "FR"
  ],
  "maxItems": 100,
  "includeDetails": true
}
```

# Actor output Schema

## `jobs` (type: `string`):

Every scraped job listing, one item per job. Download as JSON, CSV, or Excel.

## `overview` (type: `string`):

Browse the results in the console, with apply URLs and company data.

# 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 = {
    "query": "data engineer",
    "countryCodes": [
        "FR"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("alexmorain/wttj-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 = {
    "query": "data engineer",
    "countryCodes": ["FR"],
}

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

```

## MCP server setup

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