ZipRecruiter Scraper API - US Jobs, Salary & Apply URL
Pricing
from $0.80 / 1,000 job returneds
ZipRecruiter Scraper API - US Jobs, Salary & Apply URL
Scrape ZipRecruiter jobs in the US. Some employers post their own salary and some let ZipRecruiter estimate one. Every row says which. You also get the title, company, city, employment type and remote flag. Rows carry the exact posted date and the apply URL. $0.80 per 1,000 jobs.
Pricing
from $0.80 / 1,000 job returneds
Rating
0.0
(0)
Developer
Dami's Studio
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
2
Monthly active users
a day ago
Last modified
Categories
Share
ZipRecruiter Scraper: US jobs with the apply URL and pay marked published or estimated
Search ZipRecruiter by title and place, or paste a search URL, and every job comes back as a row: title, company, city and state, employment type, remote or on-site, the exact posted date, and the employer's own apply link rather than a redirect.
The awkward part first, and it is the reason this exists. A lot of the pay on ZipRecruiter is
ZipRecruiter's own estimate, not the employer's number. Rows keep the two apart: employer figures
live in salaryMin and salaryMax, estimates live in estimatedSalaryMin and estimatedSalaryMax,
and salarySource says exactly where the number came from. They are never mixed into one column.
| Input | Job titles or keywords with a US location, or ZipRecruiter URLs |
| Output | One row per job |
| Ceiling | 20,000 jobs per run. About 750 per search is ZipRecruiter's own limit |
| Account needed | None, and nothing to install |
| Price | $0.80 per 1,000 jobs, flat on every plan |
🏷️ What ZipRecruiter Scraper does
Give it keywords and a location, or paste ZipRecruiter search URLs and job pages. A pasted search URL keeps the filters already on it. Leave the keywords empty and set a location and you get everything in that place.
Filter values are checked before anything runs. ZipRecruiter ignores a value outside its own vocabulary and returns the unfiltered board at HTTP 200, so a typo would quietly get you every job in the country and bill you for it. Only values the site actually honours are accepted here, which is why the filters are pick-lists rather than free text.
onlyEmployerPublishedSalary is the strict version of the pay promise: it keeps only postings whose
figure came from the employer, either as a structured pay field or written into the description, and
drops ZipRecruiter's estimates. About 76% of postings survive it. Dropped rows never reach the
dataset and are not billed.
includeDescription adds the full description plus the employer's ZipRecruiter review score and
review count. It costs one extra request per job, so the run takes roughly three times as long. It
adds fields rather than rows, so the price does not move. Without it you still get a summary of
around 200 characters.
📥 What you give it
{"searchQueries": ["registered nurse"],"location": "Austin, TX","days": "5","locationType": "only_remote","employmentType": "full_time","maxItems": 200}
| Field | Default | What it is |
|---|---|---|
searchQueries | box starts at registered nurse | One search per entry. Leave empty and set a location for everything there. |
location | box starts at Austin, TX | A city and state, a state, or United States. US board only. |
startUrls | none | ZipRecruiter search URLs or individual job pages, pasted from the site. |
days | any time | Posted within the last 1, 5, 10 or 30 days. |
radius | ZipRecruiter's own 25 miles | 5, 10, 25, 50 or 5000 miles. Ignored for state-wide and country-wide searches. |
locationType | any | no_remote, only_remote or hybrid. |
employmentType | any | Full-time, part-time, contract, contract to hire, temporary, per diem or other. |
experienceLevel | any | no_experience, junior, mid or senior, as ZipRecruiter classifies them. |
applyType | any | has_zipapply keeps only postings you can apply to on ZipRecruiter itself. |
salaryMin, salaryMax | none | ZipRecruiter's own annual salary filter, which uses its own pay figure. |
onlyEmployerPublishedSalary | off | Keep only employer-published pay. Dropped rows are not billed. |
includeDescription | off | Full description and the company's review score. |
maxItems | box starts at 50 | Total jobs for the run, up to 20,000. |
egressMode | auto | Leave it alone. The other settings are there for debugging. |
proxyConfiguration | leave it alone | Optional. Only if you want the run to go out through a particular network of your own. |
📤 What you get back
A real row from a run on 20 September 2026, with the long redirect token cut short:
{"id": "EazrVIHe3SD2W9b_kEu68w","openSeatId": "e8a515fa6f1a5372","title": "RN, Registered Nurse - IMC MedSurgical A","url": "https://www.ziprecruiter.com/c/CHRISTUS-Health/Job/RN,-Registered-Nurse-IMC-MedSurgical-A/-in-San-Marcos,TX?jid=e8a515fa6f1a5372","companyName": "CHRISTUS Health","companyId": "christus health","companyUrl": "https://www.ziprecruiter.com/co/CHRISTUS-Health/Jobs","locationText": "San Marcos, TX","city": "San Marcos","state": "Texas","stateCode": "TX","postalCode": "78666","county": "Hays","countryCode": "US","locationTypes": ["IN_PERSON"],"locationTypeText": "On-site","isRemote": false,"isHybrid": false,"isInPerson": true,"employmentTypes": ["FULL_TIME"],"employmentTypeText": "Full-time","salarySource": "ziprecruiter_internal_model","salaryIsEstimate": true,"salaryIsPublishedByEmployer": false,"salaryMin": null,"salaryMax": null,"salaryMinAnnual": null,"salaryMaxAnnual": null,"salaryInterval": null,"salaryCurrency": "USD","salaryText": null,"estimatedSalaryMin": 31.25,"estimatedSalaryMax": 49.75,"estimatedSalaryMinAnnual": 62500,"estimatedSalaryMaxAnnual": 99500,"estimatedSalaryInterval": "HOUR","salaryShownOnSiteAsEstimate": false,"benefits": [],"benefitsText": null,"postedAt": "2024-02-27T20:43:20Z","repostedAt": "2026-09-14T20:43:20Z","postedAtRelative": "5 days ago","isReposted": false,"isActive": true,"isNew": false,"updatedAt": "2026-09-19T22:49:08.815300215Z","applyType": "OPEN_EXTERNAL","hasZipApply": false,"applyUrl": "https://careers.christushealth.org/opportunity/rn-registered-nurse-imc-medsurgical-a-325738?utm_campaign=ZipRecruiterPPC-RN","applyRedirectUrl": "https://www.ziprecruiter.com/job-redirect?match_token=Cp8BChZFYXpyVklIZTNTRDJXOWJfa0V1Njh3...","shortDescription": "Description CHRISTUS Santa Rosa Hospital - San Marcos, which originated in 1923 as Hays County Soldiers, Sailors and Marines Memorial Hospital, opened at its current Wonder World Drive location in","description": null,"companyRating": null,"companyReviewCount": null,"searchQuery": "registered nurse","searchLocation": "Austin, TX","searchUrl": "https://www.ziprecruiter.com/jobs-search?search=registered+nurse&location=Austin%2C+TX","page": 1,"position": 1,"scrapedAt": "2026-09-20T07:08:28.109Z"}
| Field | What it is |
|---|---|
salarySource | employer_provided, employer_description_parsed, ziprecruiter_estimate, ziprecruiter_internal_model or none. The first two are the employer's number. |
salaryIsPublishedByEmployer / salaryIsEstimate | The same split as two booleans, for filtering in a spreadsheet. |
salaryShownOnSiteAsEstimate | Whether ZipRecruiter labels it as an estimate on its own page, which is not always the same thing. |
applyUrl | The employer's own application page, decoded out of ZipRecruiter's redirect. applyRedirectUrl keeps the redirect if you want it. |
postedAt / repostedAt / postedAtRelative | The original posting date, when it was put back up, and ZipRecruiter's own wording. A row can carry an old postedAt and a recent repostedAt. |
description, companyRating, companyReviewCount, companyWebsite, companyIndustry, companyHq | Only with includeDescription on. |
searchQuery, searchLocation, searchUrl, page, position | Which search produced the row, and the exact URL used. |
Two dataset views are set up in the Console: overview, and salaryHonesty, which puts the published and estimated pay columns side by side.
🧾 Reading the output
| Row | How to spot it | Billed |
|---|---|---|
| A job | It has an id and no ok key | yes |
| The sample row | _sample: true and charged: false | no |
| A diagnostic | ok: false, _diagnostic: true and an errorCode | no |
Job rows carry no charged field. The flag is only on the sample and diagnostic rows, to mark them
as not real results. The sample row looks like a realistic posting in the Console table, so filter
on _sample rather than reading by eye.
| Code | What it means |
|---|---|
NO_RESULTS | That search matched nothing. |
BAD_URL | A link that is not a ZipRecruiter search or job page. |
JOB_UNAVAILABLE | A pasted job has expired or been taken down. |
UNKNOWN_FILTER_VALUE | A filter value ZipRecruiter does not honour, refused before the search ran. |
SEARCH_PAGE_UNAVAILABLE | A page of results did not come back. |
▶️ How to run it
- Open ZipRecruiter Scraper and click Try for free.
- Type a role into Job titles or keywords and a place into Location.
- Set Posted within, On-site / remote and the other filters if you want to narrow it.
- Set Maximum jobs. Start around 25 to see what the rows look like.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it from the Apify API.
💰 How much does it cost?
$0.80 per 1,000 jobs. Flat on every Apify plan, with no volume tiers.
One billed row per job delivered. The sample row is not billed, diagnostic rows are not billed, a
posting dropped by the employer-published-pay filter never reaches the dataset and is not billed,
and includeDescription adds fields rather than rows so it does not change the price.
💡 What people use it for
- Pay benchmarking that is actually defensible, because estimates and employer figures sit in separate columns.
- Recruiting-market research: who is hiring for a role in a metro, and how the postings are worded.
- Feeding an applicant tool with
applyUrl, which points at the employer rather than a redirect. - Watching a role daily with Posted within at 24 hours and diffing on
id. - Remote-market tracking, using
locationTypeand theisRemoteandisHybridflags.
🚧 What it does not do
- United States only. The Canadian and UK ZipRecruiter sites do not serve this data.
- About 750 jobs per search is ZipRecruiter's own depth. Split by city, role or date window to go wider rather than raising the cap.
- No estimate dressed up as a salary. If the employer published nothing,
salaryMinandsalaryMaxstaynulland the estimate stays in its own fields. - Full descriptions need the extra fetch, and even then not every posting carries one. Around four in five do.
- Company ratings need that fetch too. Without it
companyRatingandcompanyReviewCountarenull. - A single failed page of results is skipped and the run carries on, without a row in the dataset marking the gap. Compare the count you asked for against the count you got.
postedAtcan be years old on a reposted job. ReadrepostedAtalongside it.
🧭 Which jobs scraper do you need?
| If you want | Use |
|---|---|
| US jobs with pay marked published or estimated | This one |
| Startup jobs from Wellfound | Wellfound Jobs Scraper |
| Jobs in Germany, Austria and Switzerland | XING Jobs Scraper |
| Jobs in Germany, Austria, the UK, the Netherlands and Belgium | StepStone Jobs Scraper |
| Internships and fresher jobs in India | Internshala Scraper |
❓ Questions people ask
How do I get only real employer salaries? Turn on Only employer-published pay. Roughly three in four postings survive it, and the ones dropped are not billed.
What is the difference between the salary fields? salaryMin and salaryMax are the employer's
own figure. estimatedSalaryMin and estimatedSalaryMax are ZipRecruiter's model. A row never puts
one in the other's place.
Does the apply link go to the employer? Yes. applyUrl is the employer's page, decoded out of
ZipRecruiter's redirect. applyRedirectUrl keeps the original if you need it.
Why is description empty? Turn on Fetch full description and company rating. It triples
the run time, which is why it is off by default.
Can I paste a search I set up on the site? Yes. The filters on the URL are read and reused.
Is this legal? These are public job postings. Apify's write-up on scraping and the law is a good starting point, and we are not lawyers.
🆘 If something breaks
Open the Issues tab on the actor page. Send the search or URL you used and the run ID. The
errorCode on the diagnostic row usually names the problem on its own.