# Glassdoor Jobs + Employer Rating (`steadyfetch/glassdoor-jobs-scraper`) Actor

Never charged for a blocked page, an empty search or a repeat. Glassdoor listings with the employer's star rating on the same row — no second run. About 4 in 5 rows carry one. Your row cap and deadline are hard limits.

- **URL**: https://apify.com/steadyfetch/glassdoor-jobs-scraper.md
- **Developed by:** [Steadyfetch Team](https://apify.com/steadyfetch) (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.20 / 1,000 job listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Glassdoor Jobs + Employer Rating

**Glassdoor job listings with the employer's star rating on the same row — no second run.** About 4 in 5 rows carry one. Set a row cap and a deadline and the run stops exactly there; blocked pages, empty searches, repeated listings and listings we could not read come back with an honest status and are never charged.

Every rival makes you run twice: once for the job cards, once for the company's rating. Here `employerRating` sits on the job row, at no extra request and no extra charge.

**From $1.20/1,000 job listings** — all-inclusive pay per event, no start fee, charged only on delivery.

> **Unofficial.** This actor is not affiliated with, endorsed by, or sponsored by Glassdoor. "Glassdoor" and all related marks are trademarks of Glassdoor, Inc. It reads only the public search pages an ordinary browser sees.

Every row carries `charged` and `missReason`, so you can reconcile the invoice from the dataset itself without opening the console. **Only rows with `charged: true` were billed.**

***

### Output — a real row

```json
{
  "source": "glassdoor",
  "sourceJobId": "1010240614385",
  "jobUrl": "https://www.glassdoor.co.uk/job-listing/data-analyst-itv-JV_IC2671300_KO0,12_KE13,16.htm?jl=1010240614385",
  "title": "Data Analyst",
  "company": "ITV",
  "employerRating": 3.9,
  "glassdoorEmployerId": "10157",
  "location": { "raw": "London, England", "city": "London", "region": "England", "country": "GB" },
  "isRemote": null,
  "salaryText": "£40k - £50k (Employer provided)",
  "salaryIsGlassdoorEstimate": false,
  "salary": { "min": 40000, "max": 50000, "currency": "GBP", "period": "year", "isEstimate": false },
  "postedRelative": "24h",
  "postedAt": "2026-08-26T09:12:00.000Z",
  "easyApply": false,
  "snippet": "…",
  "query": "data analyst",
  "country": "GB",
  "dedupeKey": "glassdoor:1010240614385",
  "charged": true,
  "missReason": null
}
```

**[Browse the full live example dataset →](https://api.apify.com/v2/datasets/J81m3lzB3VBkp7BT7/items?clean=true\&format=json)** — one real run, unedited: 30 delivered listings, 26 of them carrying the employer's star rating, and 60 repeated listings that were skipped and charged once. The last row is the run's own summary.

Plus **one search row per search term and country site** — `query`, `country`, `delivered`,
`outcome` — and **one summary row** with `delivered`, `stoppedBy`, `resumeCursor` and
`chargedEvents`.

`jobUrl` is rebuilt from the listing's own id and carries no ad or tracking token, so it is
safe to use as a join key and it will still resolve next month.

### Agent / API paste-block

```
Actor:      steadyfetch/glassdoor-jobs-scraper
Required:   queries            (array of job titles/keywords, or Glassdoor search URLs)
Optional:   countries          (array of ISO-2, default ["US"] — each is its own search)
            minEmployerRating  (1-5 — keeps only employers rated at least this)
            postedWithinDays   (integer, 1-30)
            remoteOnly         (boolean, default false — filtered rows are never charged)
            includeEmployerProfiles (boolean, default false — one profile row per unique
                               employer, charged per delivered profile; see pricing tab)
            maxItems           (integer, default 100 — hard cap for the whole run)
            maxPagesPerSearch  (integer, default 5 — the walk stops early on repeats)
            maxRunSeconds      (integer, default 900 — clean stop, never a timeout kill)
Charges:    job-listing        once per delivered job listing.
            employer-profile   once per delivered employer profile — only when
                               includeEmployerProfiles is on. Nothing else.
Note:       omit a field to use its default — do NOT send null.
Build spec: https://apify.com/steadyfetch/glassdoor-jobs-scraper/api
Token:      https://console.apify.com/settings/integrations
```

```bash
curl -X POST "https://api.apify.com/v2/acts/steadyfetch~glassdoor-jobs-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"queries":["data analyst"],"countries":["GB"],"maxItems":30}'
```

***

### The employer rating, and the honest number on it

The rating is on the same payload as the job card, so it costs nothing extra. It is **not on
every row**: measured across three cleared result pages, **74 of 90 rows carried one — about
4 in 5.** The rest are employers Glassdoor has no rating for, and those rows report
`employerRating: null` rather than a zero.

`minEmployerRating` filters on it. Rows it removes are never fetched twice and never charged.

### Employer profiles (optional add-on)

Turn on **Include employer profiles** and each unique employer among your delivered
listings gets one extra row read from its Glassdoor profile page:

```json
{
  "employerName": "ITV",
  "overallRating": 3.9,
  "ratingCount": 682,
  "recommendToFriendPct": 73,
  "ceoName": "Carolyn McCall",
  "ceoApprovalPct": 69,
  "glassdoorEmployerId": "10157",
  "profileUrl": "https://www.glassdoor.com/Overview/Working-at-ITV-EI_IE10157.11,14.htm",
  "charged": true
}
```

**One profile per employer per run**, however many of its listings you collected —
30 listings from one employer is one profile row, and it is never fetched or
charged twice, including when a run is moved or restarted. Each delivered
profile is charged as its own event, shown on the pricing tab — and **while the
add-on rolls out, delivered profiles are included free**: each profile row
carries `charged: false` and says so right on the row. The honest-miss
contract is the same as everywhere else in this actor: a profile page that
answers with an access check is an **uncharged** row marked re-runnable, and an
employer Glassdoor has no rating for is an **uncharged** row that says exactly
that. Off by default — a run that does not ask for profiles is billed exactly
as before.

### How many jobs one search returns

**About 40 to 50 distinct listings.** Glassdoor shows 30 cards per result page, but paging
deeper mostly returns the same 30 again: on four independent walks (both the `?p=` and the
`_IP{N}` form, up to page 20) the union of unique listing ids landed between 41 and 50.

So this actor **de-duplicates by listing id and stops a search once two pages in a row add
nothing new** — otherwise you would pay for the same 30 jobs five times. A bigger `maxItems`
does not produce more rows on its own: **more rows come from more search terms and more
country sites.** The summary row repeats this every run.

### Searching one city

Glassdoor encodes a location as an internal id inside the URL, not as a plain city name.
Rather than guess it, run the search you want on Glassdoor, then paste that URL into
`queries` — the location, radius and any filters you set come with it. A pasted search keeps
its own country and is not crossed with your `countries` list.

### Country sites

`countries` picks Glassdoor's own country sites. **glassdoor.com and glassdoor.co.uk are
verified end to end**; Canada, Ireland, Australia, New Zealand, India and Singapore use the
identical search grammar. `location.country` is the country site the listing was found on —
Glassdoor's card prints a city line, not a country, so nothing is geocoded or guessed.

### Salary, exactly as Glassdoor prints it

`salaryText` is the string on the page. `salary` is our parse of it. **`salaryIsGlassdoorEstimate`
tells you whether the number is Glassdoor's own model or the employer's own figure** — Glassdoor
labels these "(Glassdoor est.)" and "(Employer provided)", and no rival surfaces the difference.
When a listing has no salary at all, both stay `null` rather than being estimated.

### What you are never charged for

| situation | `missReason` | run status |
|---|---|---|
| Glassdoor answered with an access check | `CHALLENGED` | SUCCEEDED — re-runnable, and the row says so |
| Glassdoor rate-limited us | `THROTTLED` | SUCCEEDED — re-runnable |
| Glassdoor returned a page we could not read | `SOURCE_UNAVAILABLE` | SUCCEEDED — the row says it is on us |
| The search ran and matched nothing | `CLEAN_EMPTY` | SUCCEEDED — a real answer |
| A listing whose card we could not read | `SOURCE_UNAVAILABLE` | SUCCEEDED — reported as its own row, never dropped |
| A listing repeated on a later result page | — | never charged twice |
| A row your filters removed | — | never charged |
| A search term or URL we could not read | `USER_INPUT` | SUCCEEDED — the row names the fix |
| Your own `maxItems` / `maxRunSeconds` / cost cap | `STOPPED_AT_LIMIT` | SUCCEEDED — summary row + `resumeCursor` |

We never guess. An access check is reported as an access check, not as "no jobs found" — a
temporary problem is never dressed up as a permanent verdict, and it is never charged.

**This actor may fail when Glassdoor changes things.** The two you are most likely to see
are `CHALLENGED` (Glassdoor answered with a Cloudflare access check) and
`SOURCE_UNAVAILABLE` (a page we could not read as results). Both ship as a row with
**`charged: false`**, the run still SUCCEEDS, and the row says whether re-running helps.

### What one job listing costs

One event per delivered job listing — no start fee, no per-search fee, no
per-page fee, and no upcharge for the employer rating on the row. The optional
employer-profile row is its own separately priced event, charged only when you
turn it on and only per profile actually delivered (see the pricing tab).

| Your Apify plan | Price per job listing | Per 1,000 |
|---|---|---|
| Apify Free plan | $0.0048 | $4.80 |
| Bronze | $0.0036 | $3.60 |
| Silver | $0.0024 | $2.40 |
| **Gold, Platinum, Diamond** | **$0.0012** | **$1.20** |

The store header shows the Gold price. The whole ladder is printed here because an actor
whose promise is billing honesty cannot have an undisclosed tier ladder.

### Limits are hard limits

`maxItems`, `maxPagesPerSearch` and `maxRunSeconds` stop the run exactly where you set them.
The run still finishes **successfully**, the summary row names which limit bound, and
`resumeCursor` lists the searches that were not reached — so you continue from there instead
of paying to re-walk what you already have.

### FAQ

**Why did I get 45 rows when I asked for 200?**
Because you gave one search term. See "How many jobs one search returns" — add terms or
country sites, not a bigger cap.

**Can I paste a Glassdoor search URL?**
Yes, and it is the way to search a specific city. A link to a single job posting or a company
overview page is refused with a guidance row that says what to paste instead.

**Why is `employerRating` empty on some rows?**
Because Glassdoor has no rating for that employer. About one row in five. It is `null`, never
a zero, so an average over the column is not silently wrong.

**Is the salary real?**
`salaryIsGlassdoorEstimate` answers that per row. `false` means the employer published it;
`true` means Glassdoor modelled it.

**Does `{}` charge anything?**
No. With no search terms the run returns uncharged sample rows that show the exact output
shape, and it contacts Glassdoor not at all.

**How do I get this into n8n, Make or a sheet?**
Run it and read the dataset — one row per job, stable column names, `charged` on every row.

### Honesty contract

Glassdoor sits behind an access check that clears for some requests and not others. When a
search cannot be completed, it comes back with an honest status, marked re-runnable, and
**never charged**.

Two runs started back to back can refuse each other — the access check reacts to how recently
that route was hit, not to who is asking. If a run comes back with `CHALLENGED` rows, leave a
few minutes and run it again; you were not charged for the attempt. Issues are answered in
about three hours during working hours.

***

### Steadyfetch jobs suite

One actor per source, one job each, the same row shape — so a pipeline built on one source takes the
next without a rewrite. All-inclusive pay per event, no start fee, charged only on delivery.

| Source | Actor |
|---|---|
| Indeed | **[Indeed Jobs — Stops at Your Limit](https://apify.com/steadyfetch/indeed-jobs-scraper)** |
| A company's own career site (paste a domain) | **[Career-Site Jobs — Paste a Domain](https://apify.com/steadyfetch/company-jobs-by-domain)** |
| Glassdoor, with the employer rating | **this actor** |
| Several boards at once, de-duplicated | **[Multi-Board Jobs — No Duplicates](https://apify.com/steadyfetch/multi-job-board-scraper)** |

**The rest of the steadyfetch shelf** — same contract everywhere: all-inclusive pay per event,
no start fee, charged only on delivery.

| Family | Actors |
|---|---|
| Ad creative intelligence | [Facebook](https://apify.com/steadyfetch/facebook-ads-transcript-scraper) · [Google Ads video](https://apify.com/steadyfetch/google-ads-video-transcript-scraper) · [TikTok](https://apify.com/steadyfetch/tiktok-ads-transcript-scraper) · [LinkedIn](https://apify.com/steadyfetch/linkedin-ads-transcript-scraper) · [Google Ads text & OCR](https://apify.com/steadyfetch/google-ads-creative-text-scraper) |
| Trends & keywords | [Google Trends](https://apify.com/steadyfetch/google-trends-scraper) · [Trends Now](https://apify.com/steadyfetch/google-trends-now-scraper) · [Breakout keywords](https://apify.com/steadyfetch/breakout-keywords-scraper) · Autocomplete keywords · Keyword volume & CPC · [Social trends](https://apify.com/steadyfetch/social-trends-scraper) |
| YouTube transcripts | [YouTube videos](https://apify.com/steadyfetch/youtube-transcript-scraper) · [YouTube channels](https://apify.com/steadyfetch/youtube-channel-transcripts) |
| Instagram | [Reel transcripts](https://apify.com/steadyfetch/instagram-reel-transcript-scraper) · [Profile posts](https://apify.com/steadyfetch/instagram-profile-posts) |
| **Jobs** | [Indeed](https://apify.com/steadyfetch/indeed-jobs-scraper) · [Career sites by domain](https://apify.com/steadyfetch/company-jobs-by-domain) · [Glassdoor](https://apify.com/steadyfetch/glassdoor-jobs-scraper) · [Multi-board](https://apify.com/steadyfetch/multi-job-board-scraper) |
| Amazon | [Products](https://apify.com/steadyfetch/amazon-product-scraper) · [Search](https://apify.com/steadyfetch/amazon-search-scraper) · Bestsellers · Sellers |
| Any media file | [Speech to Text](https://apify.com/steadyfetch/media-transcriber) · any link or file |

Unlinked names are publishing shortly on the same account — search **steadyfetch** on Apify Store.

# Actor input Schema

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

Job titles or keywords (for example "data analyst"). You can also paste a Glassdoor search URL such as https://www.glassdoor.co.uk/Job/data-analyst-jobs-SRCH\_KO0,12.htm — a pasted search keeps its own country, location and filters, which is how you search one city. Leave the default in place to see uncharged sample rows first.

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

Which Glassdoor country sites to search. Each country is a separate search, so two terms across two countries is four searches. glassdoor.com and glassdoor.co.uk are verified end to end; the others use the identical Glassdoor search grammar.

## `minEmployerRating` (type: `number`):

Keep only listings whose employer carries at least this Glassdoor star rating, from 1 to 5. Half stars work — 3.5 is a sensible floor. Listings for employers Glassdoor has no rating for are left out when this is set: about one row in five has no rating.

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

Keep only listings Glassdoor labels as posted in the last N days. Glassdoor publishes an age ("14d", "24h", "30d+"), not a date, and its oldest bucket is "30d+" — so 30 keeps everything.

## `remoteOnly` (type: `boolean`):

Keep only listings whose Glassdoor location line says Remote. Glassdoor has no separate remote flag on the results page, so this reads the location exactly as printed.

## `includeEmployerProfiles` (type: `boolean`):

Fetch the Glassdoor employer profile for each unique employer in your delivered job listings — one profile per employer per run, however many listings it has. Off by default. If this add-on is not yet available on the actor, the run says so in an uncharged row and delivers your job listings as normal.

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

Hard cap on delivered job listings across every search in this run.

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

How many Glassdoor result pages one search may walk. A search stops earlier on its own once two pages in a row add nothing new — Glassdoor often serves the same 30 listings again, and this actor will not fetch or charge for them twice.

## `maxRunSeconds` (type: `integer`):

The run stops cleanly before this many seconds and reports what is left, instead of being killed by a timeout.

## Actor input object example

```json
{
  "queries": [
    "data analyst",
    "product manager"
  ],
  "countries": [
    "US"
  ],
  "remoteOnly": false,
  "includeEmployerProfiles": false,
  "maxItems": 100,
  "maxPagesPerSearch": 5,
  "maxRunSeconds": 900
}
```

# Actor output Schema

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

One row per delivered job listing: title, company, the employer's Glassdoor star rating, location, salary as printed, whether that salary is a Glassdoor estimate, posted age and the listing link. Every row carries `charged` and `missReason`, so the invoice reconciles from the dataset itself. Only rows with charged = true were billed.

## `profiles` (type: `string`):

When "Include employer profiles" is on: one row per unique employer among the delivered listings — overall rating, rating count, % who would recommend, CEO name and approval. Profiles that could not be fetched, or employers Glassdoor has no rating for, are reported uncharged.

## `searches` (type: `string`):

One row per search term and country site: how many jobs it delivered, or why it delivered none. Searches that were blocked, empty or filtered out are never charged.

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

Delivered count, searches run, pages fetched, uncharged misses, what stopped the run, and the charged-event totals.

## `errors` (type: `string`):

Present only when Glassdoor answered a search with an access check, a rate limit or an unreadable page: the search and the reason. These are re-runnable, not permanent.

# 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 analyst",
        "product manager"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyfetch/glassdoor-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 = { "queries": [
        "data analyst",
        "product manager",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("steadyfetch/glassdoor-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 '{
  "queries": [
    "data analyst",
    "product manager"
  ]
}' |
apify call steadyfetch/glassdoor-jobs-scraper --silent --output-dataset

```

## MCP server setup

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