# Glassdoor Job Scraper (`boncross/glassdoor-jobs`) Actor

Scrape Glassdoor job listings for a role and location. Get title, employer, location, salary range with its source, posting age and easy-apply flag, across as many pages as you need. Export to CSV, JSON or Excel, or schedule it.

- **URL**: https://apify.com/boncross/glassdoor-jobs.md
- **Developed by:** [BonCross API](https://apify.com/boncross) (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.25 / 1,000 job-details

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

## Glassdoor Job Scraper

### What is Glassdoor Job Scraper?

Glassdoor Job Scraper gets you the job listings Glassdoor shows for a role and a place, each with its salary range and a label that says whether the employer published that salary or Glassdoor estimated it. The two are easy to mix up, and a benchmark built on the mix is wrong. Here you can keep them apart, or ask for employer-stated pay only.

Type a job title and a location, choose one of eight national Glassdoor sites, and decide whether you want the full advert and the employer's profile as well.

What you're getting:

- **Pay you can trust, because you know its source.** Low, median and high figures with currency and period, plus `salarySource` on each row.
- **The employer behind the job.** With full details on you get five Glassdoor ratings, size, revenue, industry, sector, headquarters and website on the same row as the advert.
- **About 900 jobs a minute with full details.** Six searches of five pages returned 670 jobs in 42 to 47 seconds on 2 October 2026.
- **$0.30 per 1,000 jobs with full details** on the Free plan, and $0.20 per 1,000 if listings are enough. No proxy to set up and no proxy charges.

**BonCross Verified** means we tested this actor against the live site before release and kept the record:

- 23 of 23 live checks on its inputs and filters passed on 7 October 2026
- 2 searches run the same day in Austin and New York: 60 jobs, none empty
- Charges matched the rows: 1 start, 2 searches, 60 jobs

### Read this before you use the pay

Many of Glassdoor's salary ranges are estimates. `EMPLOYER_PROVIDED` means the employer's own figure. `ESTIMATED` means the output of Glassdoor's model. On a 60-row London run it split 23 stated, 28 estimated and 9 with no salary at all.

For a salary benchmark, switch on "Only employer-stated pay". Don't present an estimate as a published salary.

### What data can you extract from Glassdoor?

Every row has the listing fields:

| Field | Notes |
|---|---|
| `title`, `normalisedTitle` | 💼 the listing title, and Glassdoor's normalised version |
| `company`, `employerId` | 🏢 the employer, and its id for joining to other data |
| `location`, `locationType`, `countryId` | 📍 where the job is (`C` city, `S` state, `N` country) |
| `payMin`, `payMedian`, `payMax` | 💷 the salary range for the role |
| `payCurrency`, `payPeriod` | e.g. GBP, ANNUAL |
| `salarySource` | `EMPLOYER_PROVIDED` or `ESTIMATED` |
| `ageInDays` | how long the listing has been up |
| `isEasyApply` | apply without leaving the site |
| `isExpired`, `isSponsored` | listing state and whether it holds a paid slot |
| `descriptionSnippet` | the site's summary fragments |
| `companyLogo` | employer logo |
| `search` | which of your searches produced the row |
| `url`, `id` | 🔗 link to the listing and its id |
| `scrapedAt` | when the row was collected |

With "Fetch full job details" on, each row adds:

| Field | Notes |
|---|---|
| `description`, `descriptionHtml` | 📄 the full advert as plain text, and (optionally) its original HTML |
| `discoveredAt` | when Glassdoor first found the listing |
| `jobTypes`, `remoteWorkTypes`, `skills` | e.g. Full-time; the remote-work arrangement Glassdoor tags on the job, where it tags one; the skills listed against the job |
| `lat`, `lng`, `city`, `region`, `country` | the job's map position and address parts |
| `employerRating`, `employerRatingCulture`, `employerRatingWorkLife`, `employerRatingCareer`, `employerRatingPay` | ⭐ the employer's Glassdoor ratings, out of 5 |
| `employerSize`, `employerRevenue`, `employerType`, `employerFounded` | employer profile |
| `employerWebsite`, `employerHeadquarters`, `employerIndustry`, `employerSector` | employer profile |

### How to scrape Glassdoor jobs

1. [Create a free Apify account](https://console.apify.com/sign-up) and open [Glassdoor Job Scraper](https://apify.com/boncross/glassdoor-jobs).
2. Type the role you're after and a location. Leave the location empty to search the whole country.
3. Put any further keywords under "More searches".
4. Choose how many pages you want from each search. A page is 30 listings.
5. Turn on "Fetch full job details" if you want the advert and the employer profile.
6. Press Start, then download the rows as Excel, CSV, JSON, XML or HTML.

### ⬇️ Input

The one thing you must give is a keyword. A location narrows it.

Depth is set in pages here, not rows: "Max pages per search" defaults to 5 (150 listings) and goes up to 30. "Max rows in total" caps the run across all your searches, and 0 means no ceiling.

| Input | Type | Notes |
|---|---|---|
| `keyword` | string | Job title or keyword |
| `location` | string | City or area. Empty searches the whole country |
| `searches` | array | More keywords, each using the location above |
| `maxPagesPerSearch` | integer | Pages per search, 30 listings a page. Default 5, maximum 30. A search whose filters keep nothing stops after 5 pages in a row with no job kept |
| `maxItems` | integer | Ceiling on rows for the whole run, across every search. Default `0`, no ceiling |
| `fetchDetails` | boolean | Open each job for the full advert and employer profile. Default off |
| `includeDescription` | boolean | Keep the advert as plain text when details are fetched. Default on |
| `includeDescriptionHtml` | boolean | Also keep the advert's original HTML. Default off |
| `excludeExpired` | boolean | Drop expired listings. Default on |
| `excludeSponsored` | boolean | Drop paid-slot listings. Default off |
| `onlyEasyApply` | boolean | Keep only apply-on-site listings |
| `onlyEmployerStatedPay` | boolean | Keep only listings where the employer published the salary |
| `minPay` | integer | Drop listings whose top of range is below this, and listings with no pay. `0` keeps everything |
| `maxAgeInDays` | integer | Drop listings older than N days. `0` keeps everything |
| `domain` | string | Which national Glassdoor site to search. Default `www.glassdoor.co.uk` |

**Paralegal jobs in London, posted in the last two weeks**

```json
{
  "keyword": "paralegal",
  "location": "London",
  "maxPagesPerSearch": 5,
  "maxAgeInDays": 14,
  "domain": "www.glassdoor.co.uk"
}
```

**Several roles in one run, with full adverts and only employer-stated pay**

```json
{
  "keyword": "paralegal",
  "searches": ["legal secretary", "conveyancer"],
  "location": "Manchester",
  "maxPagesPerSearch": 2,
  "maxItems": 150,
  "fetchDetails": true,
  "onlyEmployerStatedPay": true
}
```

#### For developers

- Rows are de-duplicated by listing id across the whole run, searches included. Pages after the first hold new listings, not repeats.
- A search whose filters keep nothing stops after 5 pages in a row with no job kept.
- Filters run on the listing before details are fetched, and a listing they drop isn't charged. `excludeExpired` is applied again after the detail fetch, because the full record is where the expired flag is reliable. A job fetched with details and then dropped as expired is billed `job-detail`.
- `minPay` drops listings whose top of range is below your figure, and listings with no pay.
- Several searches are worked on at once, which is why adding searches adds little time.
- If Apify moves a run to another server part-way through, it carries on from where it stopped. Nothing is returned or billed a second time.

### ⬆️ Output

Each listing is one row. The Overview view shows title, employer, location, pay range, salary source, age and the easy-apply flag.

```json
{
  "id": "1010260150053",
  "title": "Court of Protection Paralegal - London Bridge",
  "normalisedTitle": "paralegal",
  "company": "Anthony Gold Solicitors",
  "employerId": 768388,
  "location": "London, England",
  "locationType": "C",
  "countryId": 2,
  "url": "https://www.glassdoor.co.uk/job-listing/court-of-protection-paralegal-london-bridge-anthony-gold-JV_IC2671300_KO0,43_KE44,56.htm?jl=1010260150053",
  "ageInDays": 14,
  "isEasyApply": false,
  "isExpired": false,
  "isSponsored": false,
  "salarySource": "ESTIMATED",
  "payCurrency": "GBP",
  "payPeriod": "ANNUAL",
  "payMin": 29250,
  "payMedian": 31582,
  "payMax": 34100,
  "companyLogo": "https://media.glassdoor.com/sql/768388/anthony-gold-squarelogo-1541512638929.png",
  "descriptionSnippet": "Manage professionally and confidentially, written and telephone correspondence with clients…",
  "search": "paralegal in London",
  "scrapedAt": "2026-09-26T11:01:47.445Z",
  "description": "…",
  "discoveredAt": "2026-09-11T00:00:00",
  "skills": ["Microsoft Word", "Legal drafting", "Communication skills"],
  "lat": 51.5,
  "lng": -0.1166667,
  "city": "London, England",
  "region": "England",
  "country": "United Kingdom",
  "employerRating": 3.5,
  "employerSize": "51 to 200 Employees",
  "employerIndustry": "Legal",
  "employerWebsite": "https://www.anthonygold.co.uk"
}
```

A search that fails writes one row with `search` and `error` in place of listings. A job whose full details couldn't be fetched keeps its listing fields and gains `detailError`.

### How many listings can you get, and how fast?

A search tells you how many listings it has: a London paralegal search reported **826**. You can take up to 30 pages of 30 from each search.

London searches, whole runs on Apify, 2 October 2026:

| Run | Rows | Time |
|---|---|---|
| 1 page, listings only | 30 | 2 to 3 seconds |
| 3 pages, listings only | 90 | 3 seconds |
| 4 searches × 3 pages, listings only | 281 | 4.4 seconds |
| 1 page, full details | 30 | 3 to 5 seconds |
| 6 searches × 5 pages, full details | 669 to 670 | 42 to 47 seconds |

How complete are the rows? On a 60-row listings run, title, employer, location, age, flags and snippet were on every row, the salary range on 51 of 60 and the logo on 46 of 60. With full details (30 rows), the advert, coordinates and address were on all 30, the employer ratings on 25 and the industry on 22.

### How much does it cost to scrape Glassdoor?

| Event | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| `actor-start`, per run | $0.001 | $0.001 | $0.001 | $0.001 |
| `search-run`, per search that returned its first page | $0 | $0 | $0 | $0 |
| `job-detail`, per job written with its full details, or fetched with details and then dropped as expired | $0.0003 | $0.00028 | $0.00026 | $0.00025 |
| `job-listed`, per listing row written without details (details off, or a job whose details could not be fetched) | $0.0002 | $0.0002 | $0.0002 | $0.0002 |

Enterprise plans pay the Business price.

**$0.30 per 1,000 jobs with full details** on the Free plan and $0.25 on Business. Listing-only rows are $0.20 per 1,000 on every plan. A job is billed one event or the other, never both. Platform usage and proxy usage are included.

- Six searches of five pages with full details, 670 jobs: $0.001 + 670 × $0.0003 = **$0.202** ($0.169 on Business).
- The same run listing-only: $0.001 + 670 × $0.0002 = **$0.135** on every plan.

Listings dropped by a filter before details are fetched aren't charged, and neither are error rows.

For half as much again as the listing price you get the advert, the coordinates and the employer's ratings, so we'd turn details on unless you only need titles and pay.

One search of five pages with details costs about $0.046. The $5 of credits in Apify's Free plan each month pays for about 108 of those, or roughly 16,000 jobs.

### Limitations

- **Job listings only.** Company reviews, company salary pages and employer profile pages are separate products on Glassdoor and aren't covered.
- **The full advert needs "Fetch full job details".** Without it you get the site's summary fragments. Some adverts arrive already shortened by the employer's feed.
- **A location search can include remote and nationwide listings** beside local ones, as Glassdoor's own results do. If a place name can't be matched, that search runs across the whole country and the run log says so.
- **`employerId` is 0 on some listings**, typically agency postings, so it isn't always usable as a join key.
- **The sponsored flag is Glassdoor's, and it varies.** On some days the UK site has marked its whole result set as sponsored, which leaves nothing after "Exclude sponsored listings".
- **The expired flag is only reliable with full details.** The listing rarely marks a job expired. The full record does.
- **Two of the eight sites are tested.** The UK site and glassdoor.com have been exercised in our checks. The other six national sites are offered but unverified.
- Pay figures are in the currency of the national site you choose.

### FAQ

#### Is the salary the employer's figure or Glassdoor's estimate?

It can be either, and `salarySource` tells you which on each row. "Only employer-stated pay" keeps just the published ones.

#### How do I tell whether a job is still open?

Run with "Fetch full job details" on and read `isExpired`. With details on, the flag comes from the job's own record, which is the one to trust for monitoring.

#### How do I get only fresh listings?

Set "Maximum age in days". The first example keeps the last two weeks. On a daily schedule, 1 or 2 days is enough.

#### Will I see the same job twice if two of my searches find it?

You shouldn't. Rows are de-duplicated by listing id across the run.

#### Why did "Exclude sponsored listings" return nothing?

Glassdoor sometimes marks an entire result set as sponsored. On those days the filter removes all of it. Turn the filter off and use `isSponsored` in the rows.

#### Which countries can I search?

The eight Glassdoor sites in the "Glassdoor site" list: the UK, the US (glassdoor.com), Canada, Australia, Ireland, Germany, France and the Netherlands. The site you choose sets the language and the currency.

#### Do I need a proxy or a Glassdoor account?

No account, no login, and no proxy to arrange. There are no proxy charges either.

#### Is scraping Glassdoor legal?

These are job listings Glassdoor shows to any visitor. Gathering public data is generally lawful in the UK and EU, and the responsibility for how you use it is yours, including under UK GDPR where a record identifies a person.

### Benchmarks, monitors and code

For a pay benchmark, schedule a weekly run per role with employer-stated pay only and send the dataset to Google Sheets or Airbyte. For hiring-market tracking, keep `ageInDays` and `discoveredAt` and you can see how long listings stay up. Slack, Zapier, Make and webhooks are there when a run should trigger something.

In code, Apify's run-sync-get-dataset-items endpoint runs the actor and returns the rows in one call. Use `apify-client` from Python or JavaScript. The API tab on the actor's page has the reference with your token already in it.

An AI agent connected to the Apify MCP server (`https://mcp.apify.com`) can call the actor as a tool and answer questions like "who is hiring paralegals in London, and what do the stated salaries look like?".

### More from BonCross

- [Greenhouse Jobs Scraper](https://apify.com/boncross/greenhouse-jobs): Every open job from a Greenhouse careers board.
- [hiring.cafe Job Scraper](https://apify.com/boncross/hiring-cafe-jobs): Every job hiring.cafe lists for your filters.
- [Indeed Job Scraper](https://apify.com/boncross/indeed-jobs): Indeed job listings for a role and location.
- [Lever Jobs Scraper](https://apify.com/boncross/lever-jobs): A Lever careers board for every open job.
- [LinkedIn Jobs Scraper](https://apify.com/boncross/linkedin-jobs-scraper): LinkedIn jobs from keywords and locations or search URLs, no login needed.
- [NHS Jobs Scraper](https://apify.com/boncross/nhs-jobs): NHS Jobs vacancies by keyword and location.

### Your feedback

If a salary is labelled with the wrong source, or one of the national sites misbehaves for you, tell us on the Issues tab in Apify Console. Reports about the six sites we haven't verified are especially welcome, and we read all of them.

# Changelog

This Actor's version history is a separate document: https://apify.com/boncross/glassdoor-jobs/changelog.md

# Actor input Schema

## `keyword` (type: `string`):

What to search for, e.g. paralegal or data analyst.

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

City or area, e.g. London. Leave empty to search the whole country.

## `searches` (type: `array`):

Additional keywords to run in the same job, each using the location above. Through the API an entry may also be an object with its own keyword and location.

## `maxPagesPerSearch` (type: `integer`):

Each page returns 30 listings.

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

A ceiling for the whole run, across every search. 0 means no ceiling.

## `excludeExpired` (type: `boolean`):

Drop listings the site has marked expired.

## `excludeSponsored` (type: `boolean`):

Drop listings holding a paid slot.

## `onlyEasyApply` (type: `boolean`):

Keep only listings you can apply to without leaving the site.

## `onlyEmployerStatedPay` (type: `boolean`):

Keep only listings where the employer published the salary. Otherwise the pay range is the site's own estimate, which each row labels.

## `minPay` (type: `integer`):

Drop listings whose top of range is below this. 0 keeps everything.

## `maxAgeInDays` (type: `integer`):

Drop listings older than this. 0 keeps everything.

## `fetchDetails` (type: `boolean`):

Open each job for its full advert text, the employer's rating, size, industry and website, the map coordinates and a reliable expired flag. Doubles the number of requests.

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

Include the advert as plain text. Needs "Fetch full job details".

## `includeDescriptionHtml` (type: `boolean`):

Also include the advert's original HTML. Makes rows considerably larger.

## `domain` (type: `string`):

Which national site to search. This sets the language and currency of the results.

## Actor input object example

```json
{
  "keyword": "paralegal",
  "location": "London",
  "searches": [],
  "maxPagesPerSearch": 5,
  "maxItems": 0,
  "excludeExpired": true,
  "excludeSponsored": false,
  "onlyEasyApply": false,
  "onlyEmployerStatedPay": false,
  "minPay": 0,
  "maxAgeInDays": 0,
  "fetchDetails": false,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "domain": "www.glassdoor.co.uk"
}
```

# Actor output Schema

## `dataset` (type: `string`):

Glassdoor jobs: every row the run returned.

## `stats` (type: `string`):

Counts for the run: what was searched, fetched, filtered and returned.

# 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 = {
    "keyword": "paralegal",
    "location": "London"
};

// Run the Actor and wait for it to finish
const run = await client.actor("boncross/glassdoor-jobs").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 = {
    "keyword": "paralegal",
    "location": "London",
}

# Run the Actor and wait for it to finish
run = client.actor("boncross/glassdoor-jobs").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 '{
  "keyword": "paralegal",
  "location": "London"
}' |
apify call boncross/glassdoor-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,boncross/glassdoor-jobs"
        }
    }
}
```

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/240hPZQopkn3v58hn/builds/v1YS7HXBjuJ55IC70/openapi.json
