ℹ️ Elevate your job search with the sleek Glassdoor Scraper for Apify. Tailor-made for the modern job seeker, it's your secret weapon in the job market. Dive into a world of opportunities with custom searches, precise filtering. Fast, efficient, and incredibly user-friendly 🚀✨
All notable changes to glassdoor-scraper are documented here.
Format: Keep a Changelog . Versioning: SemVer .
[0.3.5] — 2026-09-09
Pay-per-event is live, and it gets cheaper on higher Apify plans.
The monthly rental is gone. From 22 September 2026 you pay only for the jobs you actually get,
and platform usage is included rather than billed on top of a subscription.
Changed
No more monthly fee. The old model was $25/month plus your own platform usage. There is now
no subscription and no separate usage bill — if you collect fewer than roughly 60,000 jobs a
month, you pay less than before, most people by a wide margin.
A job costs $0.40 per 1,000, description included. The salary benchmark is now $1.00 per
1,000 instead of $1.50, matching company insights, so a run with everything switched on costs
$2.40 per 1,000 rather than $2.90.
Starting a run costs $0.00005 instead of $0.005 — Apify's own default, which also waives the
first five seconds of every run's compute.
Added
Automatic discounts on higher Apify plans. Applied from your subscription tier, nothing to
ask for:
Per 1,000 jobs
Free
Bronze
Silver
Gold and above
Jobs
$0.40
$0.35
$0.30
$0.28
➕ Company insights
$1.00
$0.80
$0.60
$0.50
➕ Salary benchmark
$1.00
$0.80
$0.60
$0.50
Everything on
$2.40
$1.95
$1.50
$1.28
Notes
Nothing changed in the data a run returns, in the input, or in how the scraper works. Your
existing runs and integrations keep working unchanged.
You are still never charged for a failed row, for a duplicate Glassdoor repeats across its pages,
or for an add-on that found nothing.
[0.3.4] — 2026-09-09
Search by city and by company works again.
Glassdoor moved the two lookups that turn a city name or a company name into an id. The scraper was
still calling the old addresses, which now return a "page not found", so any run that searched by
keyword + location or by company name failed straight away with location-unresolved or
employer-unresolved. Searching by pasted URL, by job ID, or by keyword alone was unaffected.
Nothing was lost on Glassdoor's side — the lookups were consolidated, not removed, and they return
the same ids as before. The scraper now calls the new addresses.
Fixed
Keyword + location searches run again. Verified live on glassdoor.com and glassdoor.fr.
Company-name searches run again. Verified live.
Every country site is covered by the same fix — checked on .com, .fr, .co.uk, .de and .ca.
The location the run locks onto is now named the way your country site names it — "New York,
NY (US)" on .com, "Paris, 75 (France)" on .fr, "Berlin (Deutschland)" on .de. That line is your
first check that Glassdoor picked the place you meant.
[0.3.3] — 2026-09-09
Four output fixes found by benchmarking this scraper against its competitors.
Fixed
job_types reads like a list of job types again. It was shipping Glassdoor's internal
translation keys — search-jobs.job-type-options.contract — on every row. It now carries the same
clean values the previous Glassdoor scraper did: FULL_TIME, PART_TIME, CONTRACT,
INTERNSHIP, TEMPORARY. A type we have not seen before comes through in the same shape rather
than being dropped, and job_type_labels still carries Glassdoor's own display text.
A rate-limited request is now reported as rate-limited. Every throttled request was landing
in your failed rows as parse-error, which said the data could not be read when in fact it was
never fetched. Blocked and not-found requests were mislabelled the same way and are now
waf-blocked and not-found.
company_city is filled. It was declared and left empty on every row; it now carries the
employer's city, taken from its headquarters.
We no longer claim a "500–700 results" limit. How far one search reaches varies — we have
measured 543 and 1,008 on the same keyword over different connections — so the run now warns you
when your request looks larger than one search can deliver, and tells you at the end exactly
how many it reached instead of quoting a figure that can be wrong.
Notes
job_location.unknown keeps its name. It looks like a placeholder and is not: it is the field
name the previous scraper used for the displayable location ("New York, NY"), and renaming it
would break existing integrations. city, state and country hold the parsed parts.
[0.3.2] — 2026-09-09
A run that gets moved to another server no longer starts over — or charges you twice.
Apify sometimes moves a running Actor to a different server. Until now this scraper lost everything
it had done and began again from the first page, re-delivering jobs you already had and billing for
them a second time. It now picks up where it left off.
Fixed
Migrations no longer restart the run. The scraper keeps a private note of which jobs it has
already delivered, and skips them when it resumes. You see one line in the log — "Picking up
where the run left off — N jobs already delivered, and you will not be charged for them again" —
and the run continues instead of repeating itself.
Nothing is billed twice. Jobs delivered before a move are never re-delivered and never
re-charged, in every search mode, including a run built from a list of job IDs.
maxItems stays honest across a move. A run asked for 500 jobs delivers 500 in total, not 500
more after the move.
Notes
The note the scraper keeps holds nothing but job IDs you already have in your results, plus three
counters. No links, no queries, no session data. It is deleted when the run ends.
Nothing changed in the data a run returns, in the input, or in prices.
[0.3.1] — 2026-09-08
New pricing: $0.40 per 1,000 jobs, full description included. Most runs get cheaper.
We benchmarked the eight busiest Glassdoor scrapers on Apify against the same search, then repriced
against what we found. The headline number drops from $1.00 to $0.40 per 1,000 jobs — the same
price as the category leader — and the full job description, which used to cost extra, is now part
of the price of a job. Two optional add-ons carry the depth nobody else sells.
Changed
A job now costs $0.0004 — $0.40 per 1,000, down from $1.00. One price, whatever the run.
The full job description is included. It used to make a job cost $0.003 instead of $0.001.
Every competing scraper ships the description in its base price, so ours no longer charges for it.
Fetch full job descriptions still exists, but it now changes only the speed of a run: on, we
make one request per job; off, one per thirty. Same price either way.
What a run costs now, start fee included: 100 jobs $0.05 (was $0.11) · 1,000 jobs $0.41
(was $1.01) · 1,000 with everything on $2.91 (was $3.01) · 10,000 with everything on $29.01
(was $30.01).
Added
Two optional add-ons, each billed on top of a job and only on rows where they found
something:
Company insights (+$1.00 per 1,000) — the employer's own description and mission, its CEO,
its competitors, its awards, links to its reviews and salaries pages, and how many reviews,
salaries and interviews Glassdoor holds. About 40% of listings have no employer page; those rows
cost you nothing extra.
Salary benchmark (+$1.50 per 1,000) — what the market pays for that role in that place, from
the 10th to the 90th percentile for base, additional and total pay, with Glassdoor's confidence
rating. No other Glassdoor scraper on the Store sells this.
Duplicates are never billed. Glassdoor repeats about 10% of listings across pages; they are
dropped before billing, so a run of 1,000 is 1,000 distinct jobs. (This was already true — it is
stated here because it is worth knowing when comparing per-job prices.)
Notes
The billed events changed name. job-basic and job-detailed are replaced by job,
company-insights and salary-benchmark, so old invoices and new ones list different lines.
Nothing changed in the data a run returns. Same fields, same names, same shape as 0.3.0.
[0.3.0] — 2026-09-08
One flat row. The v2 object is gone, and nothing you already read has moved.
Rows carried the same data twice: the field names from the previous Glassdoor scraper at the top
level, and a v2 object repeating them under modern names. You had to know which half to read, and
the two halves disagreed about how to say the same thing. There is now a single set of fields.
Changed
The v2 object no longer exists. Everything it carried is now a normal field on the row,
named the same way the rest of the row is named. Twenty-one of its entries were simply duplicates
of a field you already had, and were dropped rather than renamed.
Every legacy field is untouched. All 41 of them — same names, same types, same values,
including the ones deliberately left empty. A test now asserts the complete list on every row, so
this cannot drift by accident.
New fields you did not have before, all promoted out of v2: company_rating (the employer's
Glassdoor score, which had no legacy field at all), company_ratings with its ten detailed
scores, company_sector, company_type, company_id, company_profile_url, job_easy_apply,
job_expired, job_source, job_seo_url, job_age_in_days, job_type_labels,
job_description_snippet, company_details and job_salary_benchmark.
job_location and job_salary gained keys instead of gaining neighbours.job_location now
carries id, type (city / state / country / metro) and country_id; job_salary carries
median and source. Their existing keys are unchanged.
company_description is filled when Fetch company details is on. It was always empty
before — the one legacy field Glassdoor can now answer.
Three fields are deliberately kept in both forms, because the two renderings serve different
purposes and dropping either would break someone:
field
keeps
gains
employment type
job_types, Glassdoor's raw key
job_type_labels, e.g. ["Full-time"]
posting age
job_publication_age, e.g. "14 days ago"
job_age_in_days, e.g. 14
job link
job_url, the tracking link
job_seo_url, the readable one
Added
Five fields Glassdoor was already serving, at no extra cost. All of them come from the search
response, so they arrive in both modes without a single extra request.
job_location.state — the state or region, e.g. New York State. Absent on a country-wide job.
job_location.latitude / job_location.longitude — coordinates. Their precision follows the
job's location scope, not its address: a country-wide French listing answers 46 / 2, the
centroid of France. job_location.type tells you which scope applies.
job_occupation and job_occupation_id — Glassdoor's normalised occupation and its stable
numeric key. This is not a duplicate of job_normalized_title, which is localised: on a
live glassdoor.fr run the two differed on 11 rows out of 12 — ingénieur front end (h/f)
against front end engineer. It is what lets one role be grouped across country sites.
Five more were probed and deliberately left out, so nobody re-opens them without new evidence:
map.postalCode exists but is null on every job tested, header.urgencySignal (the only candidate
for the always-empty job_candidate_numbers) likewise, payPercentile25 / payPercentile75 return
0 on listings — a job only ever carries the low, median and high — and divisionEmployerName is
null throughout. Seniority, language, shift and job-poster fields are rejected by the API outright.
Views
Four views replace the two.Jobs is the complete one, logo first. Companies reads the
same rows through the employer's columns — ratings, size, sector, CEO, review counts. Salaries
puts what the job pays next to what the market pays for the role. Failed items is unchanged.
The v1_compat view is removed. With one set of fields there is nothing left to switch
between. If you linked to ?view=v1_compat, use ?view=overview; the columns you were reading
are all there.
Fixed
company_rating and the overall score in company_ratings are both kept on purpose. They look
like the same number and usually are, but they come from different places in Glassdoor and
disagree on about a quarter of rows — measured on live data, 1 against 2.6 on one job.
[0.2.8] — 2026-09-08
What the market pays, next to what the job pays.
Added
"Add salary benchmark" — a new option that answers the question a salary range on its own
cannot: is this job paid well? Every job gains Glassdoor's own market estimate for the same role
in the same place — base pay, additional pay (bonus, commission, profit sharing) and total
pay, each from the 10th to the 90th percentile, with the currency, the pay period and
Glassdoor's confidence rating. A New York software engineer benchmarks at $83,013 / $125,658 /
$190,213 base, $43,124 median on top, and $168,783 median all in.
The additional-pay percentiles are worth calling out on their own: almost nobody publishes the
bonus side of a role, and it is often a quarter of the package.
It joins on data every row already carries — Glassdoor's normalised job title and the structured
location — so there is nothing extra to configure. And it is asked once per role and place, not
once per job: 30 real jobs held 11 distinct roles, so a live run resolved 18 roles in 2
batched calls. Off by default.
⚠️ The benchmark is always ANNUAL, and the row says so in payPeriod. Glassdoor annualises
these estimates whatever the trade — a New York server comes back at $43,520, not at an hourly
rate — so a job advertised at "$50–100 per hour" is not below a $138,057 benchmark. The two
figures are in different units until you convert, and shipping the period alongside the numbers
is what keeps that from becoming a silent error.
Fixed
Company details survive a hiccup. Shipped in 0.2.7, the employer pass gave up on a batch after
a single failure — and a batch covers up to 30 companies, one of which can carry sixty listings.
A live run lost a dossier to a transient proxy error exactly that way. Both enrichment passes now
retry, like the location lookup already did. Nothing is recorded until a response parses, so a
retry cannot double-count.
Notes
industry is a required argument on Glassdoor's side and changes nothing: five different industry
ids returned the same figures to the cent. It is sent to satisfy the schema and never presented as
a filter.
The currency symbol is derived from the ISO code rather than taken from Glassdoor, which returns it
as an HTML entity (€ instead of €). That also collapsed two copies of the same table into
one, shared with job_salary.currency_symbol.
[0.2.7] — 2026-09-08
Two ratings we were not shipping, and the company behind the job.
Fixed
We were promising 8 detailed ratings and Glassdoor serves 10.businessOutlook (the share of
reviewers positive about where the company is heading) and diversityAndInclusion were being
requested by nobody, at exactly the same fill rate as the eight we already shipped. They cost no
extra request and are on every row now, in both modes. The README, the schema and the Jobs view
said eight; they now say ten, because that is what arrives.
Added
"Fetch company details" — a new option that adds what Glassdoor knows about the employer,
which no job query returns at any price:
the company's own description and mission;
its CEO — name, title, photo;
its competitors, with logos;
its awards — Fortune's 100 Best Companies to Work For, LinkedIn Top Companies…;
how much Glassdoor holds on it: review, salary, interview, photo and benefit counts;
direct links to its jobs, reviews and salaries pages.
It is asked once per company, not once per job, and up to 30 companies share a single request.
That matters because employers repeat: 700 real jobs held only 192 distinct employers. A run of
1,000 jobs therefore costs about 10 extra requests — against the 1,000 that fetching
descriptions costs. Off by default, since it does add a little time to each page.
A company with no Glassdoor profile, or a batch that fails, leaves the row exactly as it was:
company details are an enrichment, never a condition for a job to ship.
[0.2.6] — 2026-09-08
Six fields that were sitting in the API untouched — including one that was blank since v1.
Added
job_remote / v2.remote — whether the role is work-from-home. This field has existed in the
output since the very first version and was always null: Glassdoor does expose it, under
remoteWorkTypes, and nobody had asked for it. It comes back on 30% of an ordinary search and
100% of a remote-filtered one. A job Glassdoor says nothing about stays empty rather than being
reported as on-site — absence is not proof.
v2.seoUrl — the clean, readable listing URL Glassdoor itself links to, instead of only the
tracking redirect.
v2.company.profileUrl — the employer's Glassdoor page. The API has no field for it, so it is
built from the employer id and was checked against live pages before shipping.
v2.expired — whether the listing is still live.
v2.jobSource — where Glassdoor imported the listing from, e.g. "Indeed Job Search Platform".
v2.descriptionSnippet — a ~160-character opening excerpt of the description (124-175 across
the sample), served by the search itself. It is what makes a run without descriptions actually
triageable: you can tell what a job is about before deciding to pay for the full text. It is an
excerpt, not a summary, and only sometimes ends in an ellipsis — hence the name. It shows up as
the Description preview column. Comes in two flavours, like the full description does:
descriptionSnippet is plain text, descriptionSnippetHtml keeps the <b> Glassdoor wraps
around your searched keyword.
All six arrive with the search response: no extra request, no extra cost, both modes.
Readable job link, Remote, Expired, Listing source and Company profile are now
columns in the Jobs view, and the Legacy (v1) view gained job_remote and job_posted_date,
which had nothing to show until now.
Notes
Two candidates were probed and deliberately dropped: payPercentile25 / payPercentile75 return
0 on every job tested, and organic reports true even on sponsored listings. Shipping either
would have meant shipping a field that is always wrong.
[0.2.5] — 2026-09-07
The company profile and all 8 ratings now come with every job, description or not.
Changed
Company profiles no longer need "Fetch full details". Glassdoor serves the entire employer
dossier on the search response itself — we were fetching it a second time, once per job, for
nothing. Verified field by field against the old detail call on 20 jobs: 120 comparisons, 120
identical, zero differences. So size, industry, sector, revenue, year founded, headquarters,
website, company type, employer id, the apply link, employment type, posting date and all 8
detailed ratings are now on every row, in both modes, at no extra cost or time.
The option is now "Fetch full job descriptions", because the description is the only thing it
still buys. Everything it used to gate comes free.
A 1,000-job run with descriptions goes from about 1,034 requests to about 34.
Measured before shipping: 54 pages of the wider query — zero Cloudflare blocks, no latency
drift over 30 consecutive pages, pagination reaching just as far, +180 ms per page of 30 jobs and
1.6 KB more per job (against the 6–17 KB detail call it replaces).
Fixed
company_size and company_revenue shipped the literal string "Unknown". Glassdoor sends
"Unknown" / "Unknown / Non-Applicable" rather than omitting an unknown value; those now come
back empty, like every other missing field. Affects roughly one employer in five.
company_short_name returned the full company name. It now uses Glassdoor's actual short name
("NVIDIA" rather than "NVIDIA Corporation").
job_location.country now prefers Glassdoor's own country name, so it resolves for any
country instead of only the 20 the country selector offers — including in the job-ID mode, which
never goes through a search and would otherwise have been the one mode left without a country.
[0.2.4] — 2026-09-07
Currency and country, in every mode.
Fixed
Salaries came back without a currency when full details were off. A lite row showed
min and max but currency: null, so a €45,000 job and a $45,000 one were indistinguishable
in the same dataset. Glassdoor serves the currency on the search response itself — we simply
weren't asking for it. currency, currency_symbol and estimated (employer-stated vs
Glassdoor estimate) are now filled in both modes, at no extra cost or time.
job_location.country was always empty. It is now resolved for all 20 supported country
sites, in both modes.
A nationwide or region-wide job was reported as a city.city took whatever came before the
first comma, so a country-scoped listing became city: "united states" and a Brazilian
region-wide one became city: "trabalho remoto" — literally remote work. city is now filled
only when the job really is city-scoped, and left empty otherwise.
Currency symbols now cover all 20 supported markets (₹, S$, R$, HK$, CHF, NZ$, MX$, AR$…),
instead of five.
Added
v2.country — the country name, resolved from Glassdoor's own country id.
v2.locationType — what location refers to: C city, S state or region, N country,
M metro area. Lets you tell a city listing from a nationwide one without parsing strings.
v2.locationId / v2.countryId — Glassdoor's own identifiers, for joining runs together.
Country is now a column in the Jobs dataset view.
[0.2.3] — 2026-09-07
What a search can actually return, said honestly — and quicker runs.
Fixed
Runs announced 2,000 results and delivered about 500. Glassdoor advertises a huge total —
93,361 on one search we probed — but stops serving new results after roughly 500–700 jobs,
then simply offers no further page. Paginating to exhaustion returned 547 unique jobs at 30 per
page, 554 at 50 and 543 at 100: it is a limit on results, not on pages, so a larger page size
does not reach further. The old "Glassdoor returns at most 2,000 results, so we'll fetch 2,000"
line came from an early estimate that was never paginated to the end, and it promised four times
what any run could deliver. The run log, the Maximum jobs field and the README now state the
measured figure.
Two consequences worth knowing:
The warning also fires on smaller searches. A search reporting 900 jobs dries up at ~550
just like one reporting 93,000, and used to say nothing at all while quietly returning half of
what you asked for.
Nothing changed about how much we collect — only about what we tell you. The internal
ceiling is untouched, because a filtered search does reach further and lowering it would have
thrown away real results.
To go beyond it, split one search into several narrower ones — by posted date, salary band,
location, industry or job function — and combine the datasets, deduplicating on job_id. Each
narrower search gets its own allowance and the results barely overlap: on a live test one
filtered variation returned 739 jobs, 699 of which never appeared in the unfiltered run.
The Docker image only kept building thanks to a cache. The build installed pnpm@latest,
an unpinned version. pnpm moved to 12.x, whose new install engine cannot reconcile the
node_modules shipped in the Apify base image, and every fresh build failed. Existing builds
survived on a layer cache, so nothing looked wrong — one cache eviction and the Actor would have
become unbuildable with no code change to blame. pnpm is now pinned to the version that produced
the lockfile. This is the same class of failure that broke the previous Glassdoor scraper.
Changed
Runs are about 20% faster, and cost a fifth as much to operate. Measured over 11 live runs
sweeping concurrency and memory against the same search:
before
now
concurrent requests
10
25
memory
4 GB
1 GB
seconds per job
0.219
0.164
The scraper is limited by the network, not the machine: CPU sits at 9–13% and memory peaks around
175 MB whatever it is given, so 4 GB was twelve times what a run uses. 25 requests at a time is a
measured peak and not a floor — 50 was slower than 25 — and no run was blocked or failed at
any level tested. Your price is unchanged, since billing is per delivered job; runs simply
finish sooner.
Internal
The Actor manifest carried a name and description from the rebuild that never matched the
published Actor. Restored from the platform, which still held the originals. The name mattered
beyond the wording: it did not match the deployed Actor, so a plain deploy created a new private
Actor instead of updating production.
[0.2.2] — 2026-09-04
Added
🚫 Exclude employers. Paste the companies you never want in your results, one per line. A job
is left out when its company name contains what you type, so Amazon also removes
Amazon.com Services LLC — while Apple leaves Applebee's alone, because the match is on whole
words. Excluded jobs never reach your dataset and never trigger a details request, and the run
keeps paginating until it reaches your maximum: on a live test, excluding two employers dropped 15
jobs and still delivered the 20 requested. The count is reported at the end of the run.
(Glassdoor has no exclusion filter of its own — its filter bar offers Easy Apply, Remote, Company
rating, Date posted and Salary range — so this is applied as results arrive.)
[0.2.1] — 2026-09-03
Fixed
Saved runs from the previous scraper that set an employment type were being refused. Since
v0.1.6 the field accepts several values at once, and Apify checks the input before the actor
starts — so a task carrying the old single value ("fulltime") was rejected outright with
"Field input.jobType must be array", without ever reaching the compatibility layer. The old
field now accepts both the old single value and a list, so those tasks run again; the picker you
see in the Console moved to Type of employment and is unchanged. Nothing to redo on your side.
Hourly jobs reported a yearly figure. Glassdoor returns the pay twice — once in the period it
advertises, once converted to a year — and the full-details path read the converted one while
labelling it "hourly", so a $65/hour job came back as hourly, 135,200. Both paths now read the
figure that matches the stated period. Annual jobs were never affected, which is why this survived
earlier checks; 2 of 10 rows in a real run were wrong.
Runs by job ID came back without a job title. That mode does no search, and the details request
never asked for the title or location — so rows arrived with an empty title, no location, no
posting age and no company rating. All four are now filled.
[0.2.0] — 2026-09-02
Pay-per-event billing, and the cost work that makes it viable.
Apify retires the rental pricing model on 2026-10-01; Actors left on it are migrated to
pay-per-usage, where the developer earns nothing. This release moves the Actor to pay-per-event
and cuts the platform cost it now has to absorb.
Pricing
Billing is now per job, replacing the $25/month subscription:
job-basic $0.001 ($1 per 1,000) · job-detailed $0.003 ($3 per 1,000) ·
apify-actor-start $0.005 per run.
Charges fire once per delivered job, after the row is pushed. Previously the list was billed
once per page, which made it impossible to price the two modes differently.
failed: true rows are still never charged. apify-default-dataset-item is deliberately not
declared — it would bill them.
safeCharge() now refuses platform-managed events outright: charging apify-actor-start
from code fails the run.
README gains a Pricing section with worked cost examples.
Cost
Split the proxy pools. The Cloudflare-gated location/employer lookups keep residential IPs;
the bulk /graph crawl moves to the cheaper datacenter pool. Measured over 8 cities and
1,912 jobs: /graph is unaffected by the pool, while lookups score 8/8 on residential against
4/8 on datacenter — so neither pool alone is right. Cost per 1,000 enriched jobs falls from
$0.100 to $0.013.
Memory bounds set to 256–2048 MB (was unbounded, defaulting to 4,096 MB). Peak measured use
is 138 MB at every scale tested, and Apify's own production data put 4 GB runs at $3.19 per
1,000 results against $0.114 at 1 GB. Set the platform default to 512 MB — it also keeps
apify-actor-start at a single charge per run.
The default proxy input no longer pins an account-specific group.
[0.1.12] — 2026-09-01
Fixed
Apply links were missing their beginning. Glassdoor returns the apply link as a path
(/partner/jobListing.htm?…), never a full address, and we shipped it as-is — so v2.applyUrl
and job_apply_url couldn't be opened or clicked without knowing to prepend the site yourself.
Both now carry the complete URL (verified: it resolves straight to the job listing). job_url was
already complete, so rows used to contradict themselves.
All three links now follow the Glassdoor country site you picked — a glassdoor.fr run no longer
hands you glassdoor.com links. The query string is kept intact on purpose: it looks like tracking,
but it is what makes the partner apply link work.
Added
A link to the job listing on every row (v2.jobUrl), built from the job id alone — so it is
there in the fast mode too, where the previous scraper had no link at all. The legacy job_url
field now falls back to it instead of staying empty.
Readable employment types (v2.jobTypes): Full-time, Part-time, Contract — the previous
scraper exposed Glassdoor's raw internal keys (search-jobs.job-type-options.fulltime), which it
still does under the legacy field, untouched.
Changed
The Jobs table in the Output tab is now the complete picture. It leads with the logo, title,
company, rating and location, then the job link and apply link, employment type, the full
salary range, the description, and the whole company profile with its 8 ratings — 39 columns,
ordered most-useful-first. It mixes the clean new fields with the legacy ones that have no modern
equivalent, so nothing is only reachable by switching views. The always-empty benefits column was
removed from it (Glassdoor returned no benefits on any of 13 sampled listings); the field itself
stays on the row for backward compatibility.
[0.1.11] — 2026-09-01
Changed
The run log now names the location Glassdoor actually matched, instead of echoing what you
typed. The lookup returns a single best match, so an ambiguous name is decided for you —
Cambridge means Massachusetts and not England, Springfield means Missouri, Paris means
France. The log used to say Location locked in: cambridge; it now says
Location locked in: Cambridge, MA (US), so a wrong pick is obvious on the first run. Add the
state or country to your search (Cambridge, UK) — or paste a Glassdoor search URL — to force the
one you mean.
[0.1.10] — 2026-09-01
Fixed
🔴 Location search silently fell back to a nationwide search. Resolving a location (and a
company name) went through one single request with no retry, and the Apify residential proxy
rejects a tunnel 5–25% of the time. Any such blip was swallowed: the run widened to the whole
country, kept going, and billed you for jobs you never asked for. A real run answered
location: "new york" with 11,228 US-wide jobs. The lookup now retries on a fresh exit IP
(measured 20/20 successful resolutions, versus 6/8 before), and when a location genuinely
cannot be matched the run stops with a clear message and charges nothing instead of quietly
scraping the wrong thing. Searching by company name gets the same treatment — it used to crash the
whole run on the same blip.
🎯 Title match is now Off by default. Introduced in v0.1.9 as Strict, it was calibrated
against results polluted by the location bug above — so it was compensating for the wrong problem.
Re-measured on live data with a working location (Web developer + New York, 3 pages): Strict
keeps 5 of 150 real results (3%), discarding Full Stack Engineer, Lead Frontend Engineer
and Software Engineer, Frontend — exactly the jobs a buyer wants. Off restores the previous
scraper's behaviour, which never filtered. Strict and Loose remain available as opt-ins.
66% more jobs per search page. Each result page now requests 50 jobs (what the previous
scraper and Glassdoor's own site request) instead of 30. Glassdoor stops paginating at roughly the
same page number whatever the page size, so the smaller pages were throwing away a third of the
reachable depth: 3 pages returned 90 unique jobs, now 149. Same number of requests.
Local development no longer falls through to a stub session when AUTH_SERVICE_URL /
AUTH_SERVICE_KEY are set as documented — both those names and COOKIE_SERVICE_* are accepted.
[0.1.9] — 2026-07-23
Added
🎯 Title match — results now stay on topic. Glassdoor matches a keyword one word at a time
and, once the genuinely-matching jobs run out, keeps padding the results with loosely related
roles. A search for AI engineer drifted into anything containing "engineer" — attorneys,
machinists, project schedulers. The new Title match setting fixes that:
Strict (default) — a job is kept only if its title contains every word of your keyword.
Loose — the title must contain at least one of your words.
Off — keep everything Glassdoor returns, the previous behaviour.
Replayed against a real customer run, Strict took the dataset from 46% to 100% on-topic.
Skipped jobs are always reported at the end of the run, and are never charged.
Fixed
Pasted search URLs for a state, country or metro area returned nothing. Only city URLs were
understood; every other scope was silently skipped or sent with the wrong location type, which
Glassdoor answers with zero results. All four scopes now work, and a keyword-only search URL is
accepted as a nationwide search.
An unresolvable location no longer corrupts your keyword. When a location couldn't be matched,
the actor used to append it to your keyword and search nationwide — which quietly diluted the
results (measured: 100% → 39% on-topic). It now widens the scope without ever rewriting your query.
Full details are now on by default. A fresh run fetches the complete job description, the
apply link and the full company profile (size, industry, revenue, year founded, HQ, and 8
detailed ratings) out of the box — matching the previous Glassdoor Jobs Scraper, which always did.
Prefer speed and a lower cost? Turn Fetch full details off for the lite mode — you still get
the salary range, company rating and location. Full details make one extra request per job, so a
details-on run is slower and costs more than lite.
A cleaner, friendlier run log. The live log now reads as a tidy progress story — a branded
intro, your search milestones (location, company, jobs found), a running "N of M jobs so far…"
counter, and a friendly wrap-up — instead of the scraping engine's internal chatter. Technical
diagnostics (retries, internal stats, the data endpoint) are no longer shown by default; they stay
available in debug mode. No change to the data you get — just a nicer window into the run.
Multi-select filters. Employment type, seniority, industry and job function now let you pick
several values at once (e.g. Full-time and Contract, or Engineering and IT) — results match
any of them. Previously each was limited to a single choice.
Company logos on every job — shown in the results table even in the fast (list-only) mode, with
no extra request or cost.
Cleaner Output tab. A logo-first results table and a proper Actor output schema, so runs display
their jobs immediately in Apify Console instead of showing an empty or "no output schema" state.
Fixed
Location search now targets the city you enter. It was silently falling back to a nationwide
search on every run — the location lookup was being blocked before it could resolve. Searching
"New York" now returns New York–area jobs. (Same fix restores search by company name.)
Runs now return the full number of jobs you ask for. In full-details mode, asking for 100 could
return ~88: Glassdoor repeats around 10% of its listings across result pages, and those repeats were
eating into the count. Duplicate listings are now skipped and the search keeps going until it reaches
your target — so 100 means 100 (and no duplicate rows in fast mode either).
The previous scraper's "Domain" filter now works again. Saved tasks that set a Domain
(Engineering, Finance, Legal, IT, Consulting, HR, …) had it silently ignored — the old scraper's
category ids are dead on Glassdoor's side today. It's now mapped to the current, working
job-function taxonomy, so those runs narrow their results as intended. Every target id was
verified live (e.g. Domain = Legal on a "manager" search → 264 legal roles, versus ~795k
unfiltered).
The "All" option on the Domain and Industry dropdowns no longer slips through as a literal
filter value — it now correctly means "no filter".
Industry now offers all 25 Glassdoor sectors (was 4) — Healthcare, Legal, Manufacturing,
Retail, Government, Education, Real Estate, Insurance, Telecommunications, and more. The filter
targets Glassdoor's numeric sector id; the full taxonomy was enumerated from live data and every
sector filter-verified.
Company size brackets relabelled to match Glassdoor's real filter buckets: 1–200, 201–500,
501–1000, 1001–5000, 5000+. The previous labels (1–50, 51–200, 201–1000, …) were off — e.g. the
"51–200" option actually returns 201–500 companies. Verified against live results.
Job function filter — narrow a broad search to one kind of role (18 functions: Accounting &
Finance, Engineering, Healthcare & Medical, Sales, Skilled Trades, Legal, …). E.g. searching
"manager" with Accounting & Finance returns finance managers only. Wired to Glassdoor's numeric
sgocId; the taxonomy was discovered from live jobs and every category was filter-verified. The
previous scraper's "Domain" filter targeted ids that are now dead on Glassdoor's side — this
replaces it with the current, working taxonomy.
Fixed (filters that silently did nothing — live-verified against /graph)
Company size now actually filters. It was sending the label ("HUGE") to a filter that
expects a numeric bucket id — Glassdoor ignored it and returned every size. Now mapped to 1–5.
Easy Apply only now works and no longer breaks the search. It was sending
"EASY_APPLY_ONLY", which made the whole search query error out; now sends the numeric 1.
Both bugs were inherited from the previous scraper's filter mapping (a code bug there meant the
raw strings were shipped instead of the intended numeric ids, so those filters never worked).
Added
Industry filter (Financial Services · Management & Consulting · Human Resources & Staffing ·
Information Technology). Wired to Glassdoor's numeric industryNId, each id verified live. Also
fixes the industry filter for previous-version inputs, which likewise never worked before.
Input parse no longer crashes when a dropdown filter is used. Apify's select editor sends its
value as a string even when the field is declared integer, so postedWithinDays, minRating
and radiusKm arrived as "7"/"4"/"50" and failed Zod validation (
expected number, received string
) → run exited before scraping. These three now coerce at the input boundary and accept both
strings and numbers. minRating still enforces its 0–5 bound. (minRating/radiusKm were latent —
they only worked while left at their default.)
Initial build. Replaces the broken glassdoor-jobs-scraper.
Architecture
Step 2 — a detached browser service leases one Cloudflare cf_clearance; the actor itself is
a pure-HTTP scraper (CheerioCrawler + impit, Firefox fingerprint, no browser in the runtime).
One clearance is fleet-portable and amortises over ≥3600 items, so the browser is marginal.
Data path: POST /graph GraphQL, gd-csrf-token: 1 (literal — no token minting).
Added
Keyword, company, pasted-search-URL and direct-job-ID search modes.
Optional scrapeDetails: full description + employer dossier (size, industry, revenue,
8 detailed ratings) — one extra request per job.
Every dataset row carries both the frozen v1 field contract (root) and a clean v2 block,
so existing glassdoor-jobs-scraper buyers do not break. v1 inputs are auto-detected and
force scrapeDetails.
Salary, company rating and location arrive in the list query — no detail call needed for them.
The 2000-results-per-search cap is announced from totalJobsCount, never silently truncated.
Two charge events: job-list-page ($0.004), job-detail ($0.006).
Fixed (vs v1 behaviour, deliberate)
job_salary was corrected: v1 shipped min/max inverted, pay_period hardcoded "hourly",
and hourly figures for annual jobs. Now min < max, real period, matching figures.
company_size / company_industries are now populated (v1 hardcoded them to null).
Quality
80 unit tests, offline against captured samples. lint / tsc strict / lint:secrets /
lint:kv-leak all green. End-to-end smoke verified through the production residential proxy.
Not yet in this release
The cookie-bootstrap service (a separate deliverable). Until it exists, run with COOKIE_FILE
pointing at a jar captured via camouxai. Not production-deployable without it.
Facet-splitting past the 2000-per-search cap (BACKLOG #1).
Performance sweep under concurrency (/optimize-perf).