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: title, company, city, employment type, remote flag, benefits, exact posted date and the employer’s apply URL. Every row flags whether the pay is employer-published or a ZipRecruiter estimate. $0.80/1,000 jobs - the lowest of any established ZipRecruiter scraper.
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
1
Monthly active users
6 hours ago
Last modified
Categories
Share
ZipRecruiter Scraper API
Pulls job postings off ZipRecruiter — title, company, city and state, employment type, remote flag, benefits, the exact posting date, the employer's real apply URL, and pay with a flag saying where the pay figure came from. No login, no ZipRecruiter account, no API key.
Search by keyword and location, filter the way the site does, or paste ZipRecruiter URLs.
The pay problem, and what this Actor does about it
ZipRecruiter shows a salary on most job cards. A lot of those salaries were never written by the
employer — they are ZipRecruiter's own model output. The site is upfront about this internally: every
pay figure it serves carries a source label. Most scrapers throw that label away and hand you one
salary column that silently mixes the two.
Measured on 1,413 postings returned by three real runs of this Actor (a ten-keyword national search, a ten-keyword Chicago search, and one nurse search in New York paged to exhaustion):
| Where the pay figure came from | Share | What this Actor does with it |
|---|---|---|
| Employer filled in a pay field | 49.0% | salaryMin / salaryMax, salarySource: employer_provided |
| Employer wrote pay in the job description | 18.5% | salaryMin / salaryMax, salarySource: employer_description_parsed |
| ZipRecruiter estimate, shown on the site labelled "Estimated" | 4.6% | estimatedSalaryMin / estimatedSalaryMax, salaryIsEstimate: true |
| ZipRecruiter model output the site never displays at all | 27.7% | estimatedSalaryMin / estimatedSalaryMax, salarySource: ziprecruiter_internal_model |
| No pay figure anywhere | 0.1% | all pay fields null |
So 67.5% of postings have pay the employer actually stated, and 32.3% carry a machine-generated number. Of the 392 rows in that last category, 392 of 392 — every single one — had no pay text on the ZipRecruiter page at all. The number exists only in the page's data payload.
99.9% of postings carry some pay number. A scraper that dumps them all into one salary column
reports "99.9% salary coverage". Two thirds of that is real.
The two figures are never merged. salaryMin/salaryMax are employer figures only. Estimates live
in estimatedSalaryMin/estimatedSalaryMax. Tick Only employer-published pay and the estimates
are dropped before anything is charged — you do not pay for rows you discard. (Verified: a 25-job run
with that box ticked saw 33 postings, returned 25, and was billed for 25.)
The split moves a lot with the search. Across the three runs the employer-published share was 57.7%
(national, ten keywords), 68.3% (Chicago, ten keywords) and 70.5% (New York nursing). Check
salarySource on your own run rather than assuming.
What you get per job
id, openSeatId, title, url
companyName, companyId, companyUrl, companyLogo
locationText, city, state, stateCode, postalCode, county, country, countryCode
locationTypes, locationTypeText, isRemote, isHybrid, isInPerson
employmentTypes, employmentTypeText
salarySource, salaryIsEstimate, salaryIsPublishedByEmployer, salaryMin, salaryMax,
salaryMinAnnual, salaryMaxAnnual, salaryInterval, salaryCurrency, salaryText,
estimatedSalaryMin, estimatedSalaryMax, estimatedSalaryMinAnnual, estimatedSalaryMaxAnnual,
estimatedSalaryInterval, estimatedSalaryText, salaryShownOnSiteAsEstimate
benefits, benefitsText
postedAt, repostedAt, postedAtRelative, isReposted, isActive, isNew, updatedAt
applyType, hasZipApply, applyUrl, applyRedirectUrl
shortDescription, and with Fetch full description and company rating turned on:
description, descriptionHtml, companyRating, companyRatingScale, companyReviewCount,
companyDescription, companyWebsite, companyIndustry, companyHq
searchQuery, searchLocation, searchUrl, page, position, scrapedAt
Two fields worth calling out
postedAt is a real timestamp, not "3 days ago". ZipRecruiter renders 29 days ago on the card;
the payload behind it has 2026-02-11T00:49:45Z. Both are returned — postedAt for filtering,
postedAtRelative for reference. repostedAt is separate, because a lot of postings are refreshed.
applyUrl is the employer's own URL, not a ZipRecruiter redirect. ZipRecruiter wraps the
destination in a job-redirect?match_token= link; the token is base64 and the real URL is inside it,
so this Actor unwraps it without following the redirect. You get
https://jobs.bswhealth.com/us/en/job/26013231/LVN-Clinic-Float-PRN rather than a tracking hop.
39.3% of the 1,413 postings measured were hosted on an external ATS, and applyUrl resolved on
97.1% of those. The rest are 1-Click Apply jobs hosted on ZipRecruiter itself, where
hasZipApply is true and applyRedirectUrl is the link. That external share swings by sector —
13% on a Denver dental search, 49% on a New York nursing search.
Filters
Everything here maps to a real ZipRecruiter facet, and every value was checked against the live site.
| Input | Values |
|---|---|
days | 1, 5, 10, 30 |
radius | 5, 10, 25, 50, 5000 (= 100+ miles) |
locationType | on-site / remote only / hybrid only |
employmentType | full-time, part-time, contract, contract-to-hire, temporary, as-needed (per diem), other |
experienceLevel | no experience, junior, mid, senior |
applyType | 1-Click Apply only |
salaryMin / salaryMax | annual USD |
Why the values are a fixed list and not free text. ZipRecruiter does not reject a filter value it
does not recognise. days=zzz returns HTTP 200 and the entire unfiltered result set — 650 jobs
instead of 72. A typo would quietly bill you for rows that do not match what you asked for. So the
Actor checks each value against the site's own vocabulary first and refuses the ones that are not in
it, with an uncharged diagnostic row telling you what was rejected.
Examples
Nursing jobs in Austin, posted in the last five days, employer pay only:
{"searchQueries": ["registered nurse"],"location": "Austin, TX","days": "5","onlyEmployerPublishedSalary": true,"maxItems": 200}
Remote software work nationwide, with full descriptions and employer ratings:
{"searchQueries": ["software engineer", "backend engineer"],"location": "United States","locationType": "only_remote","includeDescription": true,"maxItems": 100}
From a URL you already have open:
{"startUrls": ["https://www.ziprecruiter.com/jobs-search?search=welder&location=Houston%2C+TX&radius=50"]}
What this does not do
- US only. ziprecruiter.ca serves a different application that has none of this data, and ziprecruiter.co.uk 404s on the search route. Do not expect Canadian or UK jobs.
- About 750 jobs per search, not the whole board. ZipRecruiter stops paginating around page 41.
One real run — nurse jobs within 50 miles of New York — returned 753 unique postings against a
claimed 813 matches, and then ran dry. Split by city, job title or
dayswindow to go wider; there is no way to page past the site's own limit. - Company rating is often absent, and it needs the detail fetch. It is not on the search card, and it only exists for employers that have been reviewed — small businesses usually have none. On a 15-job dental run only 1 job had a rating. Turn on Fetch full description and company rating and expect a run roughly three times slower.
- Full descriptions land on about 80% of jobs with the detail fetch on; the rest are postings whose body ZipRecruiter does not serve to a logged-out visitor.
- Employer ratings are on a 0–10 scale, not five stars — that is ZipRecruiter's own scale.
companyRatingScalesays so on every row. - Some search pages come back empty and are retried. ZipRecruiter intermittently answers a perfectly valid page with a 24 KB shell at HTTP 200. The Actor retries those, and only gives up after two consecutive failures. That is a site behaviour, not a block.
- No résumés, no employer contact details, no applications submitted.
Cost
$0.0005 to start a run, then $0.80 per 1,000 jobs. A flat rate — no volume tiers to unlock and no plan gates, the same number on your first run as on your thousandth.
Sample rows, diagnostic rows, duplicate postings, failed pages, and rows dropped by Only employer-published pay are never charged.
Real runs: 5 jobs in 4.9 seconds; 400 jobs in 51 seconds; 753 jobs in 102 seconds.
FAQ
Is there an official ZipRecruiter API?
There is a partner API, but it is not open to the public — api.ziprecruiter.com/jobs/v1 needs a key
issued to approved partners and returns 502 without one. This Actor reads the same public search
pages a browser does.
Do I need a ZipRecruiter account or login? No. Everything here is what a logged-out visitor sees.
How do I scrape ZipRecruiter jobs by keyword and city?
Set searchQueries to the keyword and location to "City, ST". One search runs per keyword.
Can I get remote jobs only?
Yes — set locationType to only_remote. Setting location to "Remote" does not do the same thing;
ZipRecruiter treats that as a place name and returns very few results.
Are the salaries real?
Two thirds are. Across 1,413 measured postings, 49.0% came from a pay field the employer filled in
and 18.5% were written by the employer into the job description. The remaining 32.3% are
ZipRecruiter's own model. Every row says which, in salarySource, and the two kinds never share a
column. See the table at the top.
What is ziprecruiter_internal_model?
A pay range ZipRecruiter calculated and did not display on the site. It is in the page payload but
not on the page. It is returned here for completeness, in the estimatedSalary* fields only, and
never as salaryMin/salaryMax.
Why is postedAt months old when the card says "3 days ago"?
Because the card shows the repost date. postedAt is the original posting timestamp and
repostedAt is when it was last refreshed — both come straight from ZipRecruiter's payload.
How many jobs can one search return? Around 750. A real run took a New York nursing search to exhaustion and got 753 unique postings out of a claimed 813. ZipRecruiter stops serving results past page 41 regardless of how many matches it claims. Use narrower searches — by city, title or posting age — to cover more ground.
Can I get the full job description? Yes, tick Fetch full description and company rating. It costs one extra request per job.
Does it work for company pages?
Paste a specific job URL into startUrls and you get that job. Whole-company crawling is not
supported yet; search by company name in searchQueries instead.
Is scraping ZipRecruiter legal? This Actor reads public job listings. You are responsible for how you use the data, including ZipRecruiter's terms and any applicable data-protection rules. It does not touch résumés, candidate profiles or anything behind a login.