ZipRecruiter Jobs Scraper - Advertised Pay
Pricing
from $0.75 / 1,000 results
ZipRecruiter Jobs Scraper - Advertised Pay
Searches ZipRecruiter by keyword and location and returns each listing with the employer's advertised pay as numbers, the pay interval and currency, employmentType, city, state, ZipRecruiter's own posted timestamp and the apply URL. Salary estimates are dropped, never passed off as real.
Pricing
from $0.75 / 1,000 results
Rating
0.0
(0)
Developer
String
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
14 days ago
Last modified
Categories
Share
What does ZipRecruiter Jobs Scraper do?
It runs a ZipRecruiter keyword search in a location and writes one row per listing on the results page, with the employer's advertised pay as numbers, the interval it is quoted over, the currency, the employment type, a split city and state, ZipRecruiter's own posted timestamp and the apply URL.
The distinction that matters here is advertised versus estimated. ZipRecruiter computes a salary
estimate for postings that quote no pay and marks that block as not for display. This Actor drops it.
salaryMin is null exactly when the employer named no figure, so any average you compute from this
dataset is an average of what employers actually offered rather than a mixture of offers and
predictions.
The rows are read out of the search page's React Server Components payload, not out of the schema.org
markup beside it. That markup is an ItemList with a title and a link per job and nothing else. The
payload carries pay as integers with an explicit interval and currency, the employer's posted date, a
resolved city and state, and the employment type.
No ZipRecruiter account, cookie or API key is used. This is the search a logged-out visitor runs.
What data does it extract?
| Field | Type | Description |
|---|---|---|
jobId | string | ZipRecruiter's listing key. Stable, so use it to de-duplicate and to diff between runs |
title | string | The posting title |
company | string | The employer name on the listing |
salaryMin | number | Advertised lower bound, for example 78000. null when the employer advertised no pay |
salaryMax | number | Advertised upper bound. null for a flat rate, since one figure is a rate and not a range |
salaryPeriod | string | The interval the pay is quoted over, for example year or hour |
currency | string | ISO code, for example USD |
employmentType | string | ZipRecruiter's own type, lowercased, for example full_time or part_time |
location | string | As ZipRecruiter displays it, for example Austin, TX US |
city | string | Already split out, so you can group without parsing |
state | string | Already split out |
postedAt | string | ISO 8601 timestamp from ZipRecruiter's own posted date. A real timestamp, not a day count |
jobUrl | string | Direct link to the ZipRecruiter posting |
query | string | The search term this row came from, so a multi-query run stays attributable |
sourceUrl | string | The search URL the row was read from |
collectedAt | string | ISO 8601 timestamp of the run |
Why scrape ZipRecruiter?
ZipRecruiter's index leans towards the parts of the labour market that technology-focused boards cover badly: hourly work, healthcare, trades, logistics, retail, regional employers. Combined with a real advertised-pay field and a real posted timestamp, that makes it a decent source for wage research rather than only for job discovery.
- Benchmark advertised wages by role and metro, with no estimates diluting the sample.
- Compare hourly against salaried demand for the same role using
salaryPeriod. - Track how posted pay for a role moves over months, keyed on
jobIdandpostedAt. - Build recruiting or sales lists by role, employer and metro.
- Feed a niche job board with live postings and a stable link back to the source.
- Measure how quickly listings for a role turn over, by diffing scheduled runs.
How to use it
- Put job titles or keywords in Search terms, one per entry. Up to 50 per run.
- Set Location to a city, a state or a ZIP code. It applies to every query in the run.
- Set Maximum results if you want a ceiling. The default is 1000.
- Start the run and export the dataset as JSON, CSV, Excel or XML, or read it over the API.
- To cover more of a market, add narrower search terms rather than expecting one term to paginate. Each query reads one results page.
Input
| Field | Type | Default | Description |
|---|---|---|---|
queries | array of strings | required | Job titles or keywords. Between 1 and 50. Duplicates collapse to one fetch |
location | string | United States | City, state or ZIP. One per run |
maxItems | integer | 1000 | Cap on dataset rows, up to 50,000 |
concurrency | integer | 3 | Searches run in parallel, up to 10 |
{"queries": ["registered nurse", "cdl driver", "warehouse associate"],"location": "Austin, TX","maxItems": 1000,"concurrency": 3}
Output
One row per listing. Illustrative shape:
{"jobId": "a1f3c9d27b4e6058","title": "Registered Nurse - ICU","company": "Ascension Seton","salaryMin": 78000,"salaryMax": 96000,"salaryPeriod": "year","currency": "USD","employmentType": "full_time","location": "Austin, TX US","city": "Austin","state": "TX","postedAt": "2026-08-14T11:02:37Z","jobUrl": "https://www.ziprecruiter.com/c/Ascension-Seton/Job/Registered-Nurse-ICU/-in-Austin,TX?jid=a1f3c9d27b4e6058","query": "registered nurse","sourceUrl": "https://www.ziprecruiter.com/jobs-search?search=registered+nurse&location=Austin%2C+TX","collectedAt": "2026-08-20T09:14:02.118Z"}
A posting advertising a flat hourly rate comes back as
"salaryMin": 57, "salaryMax": null, "salaryPeriod": "hour"null, which is the honest answer rather than ZipRecruiter's estimate wearing the employer's name.
Each run also writes a SUMMARY record with the item count, the failure count and every search that
could not be read, with its error.
How much does it cost?
Pay per event, charged once per result row written to the dataset. The current rate is on this Actor's pricing tab, and a search that fails writes no rows and costs nothing.
One search term is one page fetch, so a run's cost tracks the number of queries you send.
Runs started from an Apify free plan stop at 250 requests and 250 results and report that in
the run status. Any paid plan runs the full input and your maxItems. The limit binds on requests as
well as rows because these fetches go through our own infrastructure, which Apify does not reimburse
for free-plan runs.
Using it with the Apify API
import { ApifyClient } from "apify-client";const client = new ApifyClient({ token: "<YOUR_APIFY_TOKEN>" });const run = await client.actor("usestring/ziprecruiter-jobs").call({queries: ["registered nurse", "cdl driver"],location: "Austin, TX"});const { items } = await client.dataset(run.defaultDatasetId).listItems();const annual = items.filter((job) => job.salaryPeriod === "year" && job.salaryMin);const median = annual.map((job) => job.salaryMin).sort((a, b) => a - b)[Math.floor(annual.length / 2)];console.log(`${annual.length} listings advertise annual pay, median floor ${median}`);
The run's SUMMARY record, in the same run's key-value store, lists any search that failed.
Is it legal to scrape ZipRecruiter?
This Actor reads public ZipRecruiter search result pages, the same ones an anonymous visitor gets. No account, no login, no cookie, no API key, and nothing behind a paywall or an authentication wall.
The output describes job listings and the employers that posted them. There are no candidate profiles, no resumes, no applicant data and no recruiter contact details anywhere in it.
Whether a particular use of the collected data is permitted depends on where you are and on the terms you have agreed to. This is background rather than legal advice, and it is worth reading ZipRecruiter's terms if your use case goes beyond research.
Related actors
- Indeed Job Listings Scraper is the direct comparison, and running both is the usual way to check one board's coverage against the other.
- Glassdoor Jobs Scraper adds employer ratings and reviews to the same roles.
- Wellfound Startup Jobs Scraper covers the startup end, including equity, which no general board publishes.
- Workday Job Board Scraper goes to the employer's own board when you already know which large companies you care about.
- LinkedIn Profile Scraper covers the candidate side of the same recruiting workflow.
FAQ
Do I need a ZipRecruiter account or API key? No. Only public search pages are read, with no account, login, cookie or key.
Is the salary advertised or estimated? Advertised, always. ZipRecruiter's own estimate for postings
that quote no pay is marked as not for display, and it is discarded here rather than passed off as the
employer's figure. That is why salaryMin is often null, and it is the correct answer.
Why is salaryMax sometimes null when salaryMin is set? Because the posting quotes a flat rate
such as $57 an hour. One figure is a rate, not a range, so the upper bound is left empty instead of
repeating the lower one.
How many listings does one search return? Around 20, which is what one results page carries. This Actor reads the first page per query and does not paginate, so ten search terms come to roughly 200 listings. Narrower terms are how you widen coverage.
Can I search several locations in one run? No. One run searches one location across all of its
queries. Start one run per location.
Can I get the full job description? No. The output is the fields ZipRecruiter exposes on the search
results page. Follow jobUrl for the description.
How fresh are the results? They are fetched live, and postedAt is ZipRecruiter's own timestamp
rather than a relative label, so you can filter on real recency instead of on a rounded day count.
Feedback
If a search returns nothing, or a pay figure looks wrong, open an issue from this Actor's Store page with the search term, the location and a link to the listing. Compensation formats are where the edge cases live, and a specific listing is what makes one fixable.