# Scraper for ZipRecruiter — Jobs, Pay & Benefits (`logiover/ziprecruiter-jobs-scraper`) Actor

Export ZipRecruiter job listings by keyword and location: title, company, structured pay (min/max, period, annualised, employer vs estimate), benefits, remote/on-site, county and ZIP, 1-Click Apply flag and post dates. Optional full descriptions. No login. Not affiliated with ZipRecruiter.

- **URL**: https://apify.com/logiover/ziprecruiter-jobs-scraper.md
- **Developed by:** [Logiover](https://apify.com/logiover) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 results

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Scraper for ZipRecruiter — Jobs, Pay & Benefits

> **Independent tool — not affiliated with, endorsed by, or sponsored by ZipRecruiter, Inc.** "ZipRecruiter" is a trademark of its owner and is used here only to describe the public website this Actor reads.

**ZipRecruiter Jobs Scraper** exports job listings from [ziprecruiter.com](https://www.ziprecruiter.com) by keyword and location — with **structured pay** (min / max / period / annualised, and *where the number came from*), benefits, remote / on-site / hybrid, location down to **county and ZIP code**, employment type, 1-Click Apply flag, company, and posting / refresh dates. Give it a list of keywords and a list of locations and it searches every combination, walks every result page, removes duplicates and hands you a clean dataset you can export to **Excel, CSV, JSON, Google Sheets** or pull through the API.

No ZipRecruiter account, no login, no API key. It reads the same public search pages a job seeker sees.

***

### ✨ What you get from every job

- **Job** — title, ZipRecruiter job ID, canonical job URL, apply URL, short description, "new" and "urgently hiring" labels
- **Company** — name, ZipRecruiter company ID, company jobs page, logo, worker-survey highlights ("Respectful managers", "Good training"…)
- **Pay** — the text exactly as shown (`$24 - $28/hr`), plus numeric **min / max**, **pay period** (hour, day, week, month, year), **annualised min / max**, currency and a **pay source** column that tells you whether the employer published it, it was read from the job description, or it is a ZipRecruiter estimate
- **Location** — city, county, state, state code, ZIP code, country, plus workplace type (On-site, Remote, Hybrid) and an `isRemote` flag
- **Terms** — employment type (Full-time, Part-time, Contractor, Temporary, Per diem), benefits (Medical, Dental, Vision, Life, Retirement, PTO)
- **Timing** — original posting date, last refresh date, "posted 3 days ago" text, last update timestamp, active flag
- **Apply** — whether the job takes ZipRecruiter **1-Click Apply** or sends you to the employer's site
- **Optional deep mode** — full job description, industry, **O\*NET occupation code**, education requirement, closing date, **company website**, company description and employer rating (1–10)

### 🎯 Who uses ZipRecruiter data and why

- **Staffing agencies & recruiters — lead generation.** Every row is a company that is hiring *right now*, with the role, the location and often the pay. Filter by city and role, then reach out to the hiring company before your competitors do.
- **Compensation & HR analysts — pay benchmarking.** Annualised min/max per role and city, with the pay source column so you can keep only employer-published figures for a clean benchmark.
- **Labour-market research.** Track posting volume, remote share, contract vs full-time mix, and benefits coverage by occupation and metro, week after week.
- **Job boards & aggregators.** Feed niche boards and newsletters with fresh, de-duplicated listings and canonical links.
- **Sales intelligence.** Hiring is a buying signal: a company hiring 20 truck drivers or 10 nurses is growing. Pair the `company` column with [Company Firmographics Scraper](https://apify.com/logiover/company-firmographics-scraper) or [Website Contact Scraper](https://apify.com/logiover/website-contact-scraper) to reach decision makers.

### 🚀 How to scrape ZipRecruiter jobs

1. Enter one or more **Keywords** (job titles or skills) — e.g. `registered nurse`, `truck driver`, `python developer`.
2. Enter one or more **Locations** — `City, ST`, a state code (`TX`), a ZIP code (`78701`), `Remote` or `United States`. Leave it empty to search the whole United States.
3. Optionally narrow it down: **posted within**, **employment type**, **remote only**, **1-Click Apply only**, **minimum salary**, **radius**.
4. Set **Max jobs** and press **Start**. Each keyword is searched in each location; the log names every search and how many listings ZipRecruiter reported for it.
5. Download the results from the **Storage / Dataset** tab, or connect them to Google Sheets, Zapier, Make or a webhook.

### 📥 Input examples

**Nurses in two cities, posted in the last 5 days**

```json
{
  "keywords": ["registered nurse", "LPN"],
  "locations": ["Houston, TX", "Dallas, TX"],
  "postedWithin": "5",
  "maxItems": 2000
}
```

**Remote software jobs paying $150k+ anywhere in the US**

```json
{
  "keywords": ["software engineer", "data engineer", "devops engineer"],
  "locations": ["United States"],
  "remoteOnly": true,
  "minSalary": 150000,
  "maxItems": 3000
}
```

**Your own ZipRecruiter search URLs, with full descriptions and company websites**

```json
{
  "startUrls": [
    { "url": "https://www.ziprecruiter.com/jobs-search?search=electrician&location=Phoenix%2C+AZ&days=10" }
  ],
  "fetchDetails": true,
  "maxItems": 200
}
```

All input fields:

| Field | What it does |
|---|---|
| `keywords` | Job titles or skills. Each one is searched in each location. Empty = all jobs in the locations. |
| `locations` | `City, ST`, state code, ZIP, `Remote` or `United States`. Empty = whole US. |
| `startUrls` | ZipRecruiter search URLs (filters in the URL are kept). |
| `radius` | 5, 10, 25 (default), 50 or 100 miles around each location. |
| `postedWithin` | Any time, last 24 hours, 5, 10 or 30 days. |
| `employmentType` | Any, Full-time, Part-time, Contract, Temporary. |
| `remoteOnly` | Only jobs tagged remote. |
| `quickApplyOnly` | Only jobs that take ZipRecruiter 1-Click Apply. |
| `minSalary` | Only jobs whose annualised pay range reaches this amount. |
| `maxItems` | Total unique jobs to collect (0 = no limit). |
| `maxPagesPerSearch` | Cap pages (20 jobs each) per keyword × location (0 = all). |
| `fetchDetails` | Open every job page for the full description, industry, O\*NET code, education, company website and rating. |
| `concurrency` | Result pages loaded in parallel (default 40, max 50). |
| `proxyConfiguration` | Keep the default **UNBLOCKER** group — ZipRecruiter refuses ordinary proxies. |

Every filter offered here was checked against the live site and changes the result set. Filters that ZipRecruiter ignores (for example hybrid-only or internship) are deliberately not offered.

### 📤 Output example

A real row from a test run (truck driver jobs in Chicago, IL):

```json
{
  "jobId": "66a23d0a69f03797",
  "listingKey": "xMCft84-e40fJGTzftEeJQ",
  "title": "Delivery Driver Non CDL",
  "company": "The Chefs' Warehouse",
  "companyId": "0+yOssKc5YiowTxF6XFpdZJQNLQ=",
  "companyUrl": "https://www.ziprecruiter.com/co/The-Chefs'-Warehouse/Jobs",
  "companyLogo": "https://www.ziprecruiter.com/svc/fotomat/public-nosensitive-ziprecruiter-logos/company/27f017ff.jpeg",
  "location": "Chicago, IL",
  "city": "Chicago",
  "county": "Cook",
  "state": "Illinois",
  "stateCode": "IL",
  "postalCode": "60608",
  "country": "United States",
  "countryCode": "US",
  "locationType": "On-site",
  "isRemote": false,
  "employmentType": "Full-time",
  "payText": "$24 - $28/hr",
  "payMin": 24,
  "payMax": 28,
  "payPeriod": "hour",
  "payCurrency": "USD",
  "payMinAnnual": 48000,
  "payMaxAnnual": 56000,
  "paySource": "Employer",
  "payIsEstimated": false,
  "benefits": "Medical, Dental, Life, Retirement, PTO",
  "workplaceHighlights": "Good employer, Recommended by parents, Respectful managers, Uninterrupted breaks",
  "isNew": false,
  "isUrgent": false,
  "postedAt": "2026-10-02T19:56:02.000Z",
  "refreshedAt": "2026-10-02T19:56:02.000Z",
  "postedAgo": "7 days ago",
  "updatedAt": "2026-10-10T10:47:40.353Z",
  "isActive": true,
  "shortDescription": "About The Chefs' Warehouse The Chefs' Warehouse, Inc. ( combines exceptional quality and brand building with broadline food service distribution for more than 40 years. With Protein, Specialty,",
  "isQuickApply": true,
  "applyType": "ZipRecruiter 1-Click Apply",
  "applyUrl": "https://www.ziprecruiter.com/job-redirect?match_token=Cp8BChZ4TUNmdDg0LWU0MGZKR1R6ZnRFZUpREiQwMWExMjcxNy0wNmIyLTc3YjAtOGQ3MC0xNWUzMjMzMTIwMjcaW0FBRTVtSDVxS0NFTXFtSTNqLVhxR1JnXzNiVjV5czhnVmQzVHRYQmlGam9OTDJwMVdNT0o3STUzYkpqQlFRRTctN29rbXNXaHRNYVd1VG5XQ0IyeVpmVmU3b00gqNgCEAEYqNgC",
  "jobUrl": "https://www.ziprecruiter.com/c/The-Chefs-Warehouse/Job/Delivery-Driver-Non-CDL/-in-Chicago,IL?jid=66a23d0a69f03797",
  "searchKeyword": "truck driver",
  "searchLocation": "Chicago, IL",
  "searchTotalResults": 519,
  "page": 3,
  "position": 60,
  "scrapedAt": "2026-10-10T18:33:15.501Z"
}
```

#### All output fields

| Field | Type | Description |
|---|---|---|
| `jobId` | string | ZipRecruiter's job ID (the jid in the job URL). Stable across searches; used for de-duplication. |
| `listingKey` | string | ZipRecruiter's listing key for this job card. |
| `title` | string | Job title as posted. |
| `company` | string | Hiring company name. |
| `companyId` | string | ZipRecruiter's company identifier. |
| `companyUrl` | string | ZipRecruiter page listing all jobs from this company. |
| `companyLogo` | string | Logo image URL, when the company has one. |
| `location` | string | Location as displayed, e.g. "Austin, TX". |
| `city` | string | City of the job. |
| `county` | string | County of the job, when resolved. |
| `state` | string | State name. |
| `stateCode` | string | Two-letter state code. |
| `postalCode` | string | ZIP code, when the posting is pinned to one. |
| `country` | string | Country name. |
| `countryCode` | string | Two-letter country code. |
| `locationType` | string | On-site, Remote, Hybrid or Remote optional, as ZipRecruiter displays it. |
| `isRemote` | boolean | True when the job is tagged remote. |
| `employmentType` | string | Full-time, Part-time, Contractor, Temporary, Per diem…; empty when the employer did not say. |
| `payText` | string | Pay exactly as shown on the job card, e.g. "$32 - $39/hr". Empty when ZipRecruiter shows no pay. |
| `payMin` | number | Lower pay bound in the pay period below. |
| `payMax` | number | Upper pay bound in the pay period below. |
| `payPeriod` | string | hour, day, week, month or year. |
| `payCurrency` | string | Pay currency (USD). |
| `payMinAnnual` | number | Lower bound annualised by ZipRecruiter (hourly × 2,000, weekly × 50). |
| `payMaxAnnual` | number | Upper bound annualised by ZipRecruiter. |
| `paySource` | string | Where the pay figure comes from: Employer, Job description, ZipRecruiter estimate, or Not published (ZipRecruiter estimate). |
| `payIsEstimated` | boolean | True when the pay figures are ZipRecruiter estimates rather than published by the employer. |
| `benefits` | string | Benefits detected for the job, e.g. "Medical, Dental, Vision, Retirement, PTO". |
| `workplaceHighlights` | string | Worker-survey highlights ZipRecruiter shows for the employer, e.g. "Respectful managers". |
| `isNew` | boolean | True when ZipRecruiter labels the job as new. |
| `isUrgent` | boolean | True when the employer marked the job urgent. |
| `postedAt` | string | When the job was first posted (ISO 8601, UTC). |
| `refreshedAt` | string | When the posting was last refreshed or reposted (ISO 8601, UTC). |
| `postedAgo` | string | Relative age shown on the card, e.g. "3 days ago". |
| `updatedAt` | string | When ZipRecruiter last updated the job record (ISO 8601). |
| `isActive` | boolean | Whether the listing is still active. |
| `shortDescription` | string | Description snippet from the job card (about 200 characters). |
| `isQuickApply` | boolean | True when the job accepts ZipRecruiter 1-Click Apply; false when applying happens on the employer site. |
| `applyType` | string | "ZipRecruiter 1-Click Apply" or "Employer website". |
| `applyUrl` | string | ZipRecruiter's apply link for the job (redirects to the employer site for external jobs). |
| `jobUrl` | string | Canonical ZipRecruiter job page. |
| `searchKeyword` | string | Keyword of the search that found this job. |
| `searchLocation` | string | Location of the search that found this job. |
| `searchTotalResults` | number | Number of listings ZipRecruiter reported for that search. |
| `page` | number | Result page the job was found on. |
| `position` | number | Rank of the job in that search. |
| `scrapedAt` | string | When this row was collected (ISO 8601). |
| `description` | string | Full job description as plain text. Only with "Open every job page". |
| `industry` | string | Industry of the posting. Only with "Open every job page". |
| `occupationalCategory` | string | O\*NET-SOC occupation code and title, e.g. "29-1141.00: Registered Nurses". Only with "Open every job page". |
| `validThrough` | string | Date the posting is set to expire. Only with "Open every job page". |
| `education` | string | Education requirement, e.g. "Associate degree (required)". Only with "Open every job page". |
| `companyWebsite` | string | Employer's own website, when ZipRecruiter knows it. Only with "Open every job page". |
| `companyDescription` | string | One-line company description. Only with "Open every job page". |
| `employerRating` | number | Worker rating of the employer on a 1–10 scale (Breakroom survey). Only with "Open every job page". |
| `employerRatingCount` | number | Number of worker responses behind the rating. Only with "Open every job page". |

Empty cells are honest: they mean ZipRecruiter did not publish that value for that job. A missing pay range is never turned into `0`, and a missing benefit list is never guessed.

### 💵 About the pay columns

ZipRecruiter attaches a pay range to almost every job, but not every range comes from the employer. The `paySource` column keeps them apart:

| `paySource` | Meaning | Share in our test runs |
|---|---|---|
| `Employer` | The employer published the pay. | 42% |
| `Job description` | ZipRecruiter read the pay from the description text. | 31% |
| `Not published (ZipRecruiter estimate)` | The employer gave no pay; the numbers are ZipRecruiter's internal estimate, not shown on the card. | 21% |
| `ZipRecruiter estimate` | A ZipRecruiter estimate that is shown on the card. | 5% |

`payIsEstimated` is `true` for the last two. For salary benchmarking, filter on `payIsEstimated = false`. `payMinAnnual` / `payMaxAnnual` use ZipRecruiter's own annualisation (hourly × 2,000, weekly × 50), so hourly, weekly and yearly jobs can be compared on one scale.

### 📊 How many results can I get?

ZipRecruiter caps every single search at its own result count — typically **400 to 900 listings per keyword × location** (measured: "truck driver" in Dallas, TX reported 728; "registered nurse" in Chicago, IL 670; "software engineer" in Dallas, TX 454). Pages past that point come back empty, so **volume comes from the grid**:

| Grid | Searches | Typical yield |
|---|---|---|
| 1 keyword × 1 city | 1 | 400–900 jobs |
| 3 keywords × 1 city | 3 | ~1,500–2,000 jobs |
| 2 keywords × 2 cities | 4 | ~2,000–2,500 jobs |
| 10 keywords × 10 cities | 100 | ~40,000–60,000 jobs |
| 20 keywords × 50 metros | 1,000 | several hundred thousand listings |

Jobs that appear in more than one search (a remote job, a 50-mile overlap between two cities) are kept only once — de-duplication is by ZipRecruiter's job ID across the whole run. With a **Max jobs** limit and several searches, each search gets a fair share first, so three keywords give you a mix rather than whichever search answered first.

Measured on the Apify platform (October 2026):

| Run | Result | Time | Duplicates |
|---|---|---|---|
| 3 keywords × Dallas, TX | **1,200 unique jobs** | 5 min 51 s | 0 |
| 2 keywords × Atlanta, GA + Chicago, IL | **1,500 unique jobs** (430 / 356 / 366 / 366 per search) | 6 min 52 s | 0 |
| 2 keywords × Los Angeles, CA + Miami, FL (12 parallel) | **1,100 unique jobs** (353 / 271 / 269 / 213 per search) | 4 min 3 s | 0 |
| 2 keywords × Houston, TX + Phoenix, AZ (default settings, after the speed update) | **1,000 unique jobs** | **1 min 22 s** | 0 |
| Store example input | 50 jobs | 35–41 s | 0 |

### ⚡ Speed and cost

- **1,000 jobs in about 80 seconds** with the default settings — measured 81.6 s for 1,000 unique jobs (2 keywords × Houston, TX + Phoenix, AZ). The same input took 269 s before the speed update.
- **Rows stream in as pages land.** On large runs the first rows appear within ~5 s (dozens of pages start at once); a small run waits for the proxy's first answer, typically 20–30 s. The 50-job example run finishes in 35–40 s.
- **How:** 40 result pages are loaded at once and every page the limit needs is queued at the start, so no worker waits. ZipRecruiter is reached through Apify's unblocking proxy, which answers a page in 20–40 s and occasionally not at all — a page that is slow gets a duplicate request on a fresh connection and the first answer wins. The run stops the instant **Max jobs** is reached and cancels whatever is still loading.
- **Platform usage about $0.06 per 1,000 jobs** (proxy + compute), on top of the per-result price. The unblocking proxy bills only pages it delivers; duplicate requests that lose the race are cancelled before they are billed.
- **Deep mode (`fetchDetails`) opens one extra page per job**, so it is much slower. Use it for a focused list (a few hundred jobs), not for bulk exports.
- **Memory:** 512 MB is plenty — peak measured at 106 MB with 40 pages in parallel.

### 💡 Tips

- **Daily monitoring:** schedule the Actor with `postedWithin: "1"` and the same keywords and locations; you get only the last 24 hours of postings each day.
- **Bigger lists:** add metros instead of widening the radius — ten 25-mile searches return far more distinct jobs than one 100-mile search.
- **Only real salaries:** filter the dataset on `payIsEstimated = false`.
- **Remote jobs:** either set `remoteOnly: true` or use `Remote` as a location.
- **Company lists:** export the **Companies hiring** view — one row per job with company, logo, ZIP and the company's ZipRecruiter page.
- **Paste a URL:** anything you set up on ziprecruiter.com (filters included) works as a start URL.

### ❓ FAQ

**Do I need a ZipRecruiter account or API key?**
No. The Actor reads public search result pages — no login, no cookies, no API key.

**Is there an official ZipRecruiter API for this?**
ZipRecruiter's APIs are for partners and employers posting jobs; there is no open API for searching listings. This Actor fills that gap with a no-code export.

**Why does it need the UNBLOCKER proxy?**
ZipRecruiter answers ordinary datacenter and residential proxies with HTTP 403. Apify's UNBLOCKER group gets through reliably; the Actor selects it automatically. If you remove it, the log warns you.

**How fast is it?**
About 80 seconds for 1,000 jobs with the default settings; rows appear in the dataset as each page lands. Larger runs scale with the number of searches; raise **Parallel requests** (up to 50) for very large grids.

**What happens if ZipRecruiter blocks a request?**
A refused or failed page is retried on a fresh exit IP (up to four attempts). If nothing at all can be fetched, the run **fails with the reason** instead of reporting success with zero rows, so an empty result always means "no matching jobs", never "blocked". Every run writes a `SUMMARY` record (outcome, pages read, per-search counts) to the key-value store.

**Why did a search return fewer jobs than ZipRecruiter's headline number?**
ZipRecruiter caps each search at its own result count and some listings are duplicates of jobs already collected from another search. Add more keywords or locations for more volume.

**How accurate is the pay data?**
The numbers are exactly what ZipRecruiter holds for the job. Use `paySource` / `payIsEstimated` to tell employer-published pay from ZipRecruiter estimates (see the pay section above).

**Can I get the full job description?**
Yes — turn on **Open every job page**. It also adds industry, O\*NET occupation, education requirement, closing date, company website and employer rating.

**Can I scrape jobs outside the United States?**
This Actor targets ziprecruiter.com (US listings). Use `Remote` as a location for remote roles open to US candidates.

**How do I run it every day?**
Create a **Schedule** in Apify Console with the same input and `postedWithin: "1"`. Combine with a webhook or the Google Sheets integration to get new jobs delivered automatically.

**Can I export to Excel, CSV or Google Sheets?**
Yes. Every dataset exports to JSON, CSV, Excel, XML, RSS and HTML, and connects to Google Sheets, Zapier, Make, n8n, Airtable and webhooks.

**Will the same job appear twice?**
No. Jobs are de-duplicated by ZipRecruiter's job ID across all searches in a run. Across separate runs, use `jobId` as your key.

**Is scraping ZipRecruiter legal?**
The Actor only reads publicly available job listings, without logging in or bypassing any access control. Job postings are published to be seen. You are responsible for how you use the data — respect ZipRecruiter's terms, applicable law and privacy rules (GDPR/CCPA) where personal data is involved; job listings themselves rarely contain any.

### 🔌 Use it through the API

**JavaScript (apify-client)**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('logiover/ziprecruiter-jobs-scraper').call({
    keywords: ['registered nurse'],
    locations: ['Houston, TX', 'Dallas, TX'],
    postedWithin: '5',
    maxItems: 1000,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]);
```

**Python (apify-client)**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("logiover/ziprecruiter-jobs-scraper").call(run_input={
    "keywords": ["truck driver"],
    "locations": ["Atlanta, GA", "Chicago, IL"],
    "maxItems": 1500,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], job["company"], job["payText"])
```

**HTTP (run synchronously and get the items)**

```bash
curl -X POST "https://api.apify.com/v2/acts/logiover~ziprecruiter-jobs-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords":["electrician"],"locations":["Phoenix, AZ"],"maxItems":200}'
```

### 🔗 Related scrapers

- [Indeed Jobs Scraper](https://apify.com/logiover/indeed-jobs-scraper) — job listings and salary data from Indeed
- [LinkedIn Jobs Scraper](https://apify.com/logiover/linkedin-jobs-scraper) — LinkedIn job postings, no login
- [Built In Tech Jobs Scraper](https://apify.com/logiover/built-in-tech-jobs-scraper) — startup and tech jobs
- [Greenhouse Job Board Scraper](https://apify.com/logiover/greenhouse-job-board-scraper) — every open role from Greenhouse career pages
- [Website Contact Scraper](https://apify.com/logiover/website-contact-scraper) — emails and phones from the company websites you collect here

### ⚖️ Legal & trademark

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by ZipRecruiter, Inc. or any of its subsidiaries. All trademarks mentioned are the property of their respective owners. The Actor collects publicly available information only; it does not log in, does not bypass access controls, and does not collect private user data. You are responsible for using the output in line with the website's terms and with applicable law.

### 💬 Support

Missing a field or hit a problem? Open an issue on the Actor's **Issues** tab and include the run ID — every run's `SUMMARY` record shows exactly what happened.

### 📝 Changelog

#### 2026-10-10 — speed update

- Faster: 1,000 jobs in ~80 s instead of ~270 s. 40 pages in parallel by default (max 50), all pages for the limit queued up front, slow pages hedged with a duplicate request (first answer wins, the loser is cancelled and not billed), one spare page per search so the slowest page is never waited for, and an immediate stop with in-flight requests cancelled once Max jobs is reached.
- Rows stream into the dataset page by page; the status line shows live progress.
- Fixed: with many pages finishing at once, a run could save more rows than Max jobs. The limit is now exact.

#### 2026-10-10

- First release: keyword × location grid, all verified ZipRecruiter filters, structured pay with source, benefits, workplace type, county/ZIP location, 1-Click Apply flag, posting and refresh dates.
- Reads both page formats ZipRecruiter serves (it switches between them at random).
- Fair share of the row limit per search; de-duplication across searches by job ID.
- Optional deep mode: full description, industry, O\*NET occupation, education, closing date, company website, employer rating.
- Runs that cannot reach ZipRecruiter fail with the reason instead of returning an empty success.

# Changelog

This Actor's version history is a separate document: https://apify.com/logiover/ziprecruiter-jobs-scraper/changelog.md

# Actor input Schema

## `keywords` (type: `array`):

Job titles or skills to search, e.g. "registered nurse", "truck driver", "python developer". Each keyword is searched in each location below. Leave empty to list all jobs in the locations.

## `locations` (type: `array`):

Where to search: "City, ST" (Austin, TX), a state code (TX), a ZIP code (78701), "Remote" or "United States". Leave empty to search the whole United States. One search covers one location within the radius below.

## `startUrls` (type: `array`):

Optional. Paste search result URLs from ziprecruiter.com (https://www.ziprecruiter.com/jobs-search?search=...\&location=...) — any filters already in the URL are kept. Pages are walked automatically.

## `radius` (type: `string`):

How far from each location to look.

## `postedWithin` (type: `string`):

Only jobs posted or refreshed within this window. "Last 24 hours" is the setting for a daily scheduled run.

## `employmentType` (type: `string`):

ZipRecruiter's own employment-type filter.

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

Keep only jobs ZipRecruiter tags as remote.

## `quickApplyOnly` (type: `boolean`):

Keep only jobs that accept ZipRecruiter's 1-Click Apply.

## `minSalary` (type: `integer`):

ZipRecruiter's salary filter: keeps jobs whose annualised pay range reaches at least this amount (e.g. 100000). 0 turns it off.

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

Stop after this many unique jobs across all searches. 0 means no limit — one search returns up to ~900 jobs, so add keywords and locations for more.

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

Cap on result pages (20 jobs each) per keyword × location. 0 means every page ZipRecruiter has for that search.

## `fetchDetails` (type: `boolean`):

Adds the full job description, industry, O\*NET occupation, education requirement, closing date, company website and employer rating. One extra page per job, so it is roughly 20× slower and costs more proxy traffic — leave off for large exports.

## `concurrency` (type: `integer`):

How many result pages to load at once. Each page takes 7–60 seconds through the unblocking proxy, so parallelism is what makes runs fast. Slow pages are re-requested automatically (hedged), and the run stops the moment Max jobs is reached.

## `proxyConfiguration` (type: `object`):

Keep the UNBLOCKER group. ZipRecruiter refuses ordinary datacenter and residential proxies with HTTP 403.

## Actor input object example

```json
{
  "keywords": [
    "registered nurse"
  ],
  "locations": [
    "Houston, TX"
  ],
  "startUrls": [],
  "radius": "25",
  "postedWithin": "any",
  "employmentType": "any",
  "remoteOnly": false,
  "quickApplyOnly": false,
  "minSalary": 0,
  "maxItems": 50,
  "maxPagesPerSearch": 0,
  "fetchDetails": false,
  "concurrency": 40,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All jobs collected in this run.

# 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 = {
    "keywords": [
        "registered nurse"
    ],
    "locations": [
        "Houston, TX"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "UNBLOCKER"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("logiover/ziprecruiter-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 = {
    "keywords": ["registered nurse"],
    "locations": ["Houston, TX"],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["UNBLOCKER"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("logiover/ziprecruiter-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 '{
  "keywords": [
    "registered nurse"
  ],
  "locations": [
    "Houston, TX"
  ],
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  }
}' |
apify call logiover/ziprecruiter-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,logiover/ziprecruiter-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/lriYE1KmTKnN6sAOj/builds/0lQg2e2yhR7zTBW8G/openapi.json
