# ZipRecruiter Scraper — Jobs, Salaries & Companies (`tortuga/ziprecruiter-scraper`) Actor

Scrape ZipRecruiter jobs by keyword, location, filters or URL: title, company, salary range, remote, employment type, posted date, 1-Click Apply; optional full description and company facts.

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

## Pricing

from $2.40 / 1,000 job scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## ZipRecruiter Scraper — Jobs, Salaries & Companies

Scrape job ads from **ZipRecruiter** (ziprecruiter.com), one of the largest US job boards, by keyword and location,
with ZipRecruiter's own filters (radius, posted within, employment type, experience level, remote / hybrid /
on-site, salary range, 1-Click Apply), or from any ZipRecruiter search URL. For every job you get the title, company
with logo and ZipRecruiter company link, location split into city / state / ZIP, workplace type and a remote flag,
employment type, the pay line exactly as the site shows it **and** parsed min / max / period / currency plus the
yearly equivalent, posted date and "x days ago", the **1-Click Apply** flag, listed benefits (medical, dental,
401k...), company highlights from employee reviews and the snippet. Optionally it also opens each job page for the
**full description**, the employer's requisition id, original posting and expiry dates, occupation category and
**company facts** (website, industries, size, headquarters, about).

No ZipRecruiter account, no cookies, no browser. Export to JSON, CSV, Excel or use the API.

### What data does the ZipRecruiter Scraper return?

| Field | Example | Notes |
|---|---|---|
| `jobId`, `url` | `b2f2d9782214c2e5`, `https://www.ziprecruiter.com/c/Elite-Womens-Care-Center/Job/...?jid=b2f2d9782214c2e5` | `jobId` is the `jid` in ZipRecruiter job links; `listingKey` = ZipRecruiter's listing id |
| `title` | `Triage Nurse (LVN) Women's Health` | |
| `company`, `companyId`, `companyUrl`, `companyLogoUrl` | `Elite Women's Care Center`, ..., `https://www.ziprecruiter.com/co/Elite-Women's-Care-Center/Jobs` | |
| `location`, `city`, `state`, `stateCode`, `postalCode`, `country` | `Houston, TX`, `Houston`, `Texas`, `TX`, `77030`, `US` | ZIP when the ad has one |
| `workplaceType`, `isRemote` | `On-site, Remote`, `true` | On-site, Remote, Hybrid as shown on the card |
| `employmentType` | `Full-time, Part-time, Per diem` | Full-time, Part-time, Contractor, Per diem, Temporary, Internship, Other |
| `salaryText` | `$28 - $32/hr` | the pay line on the card; about 70% of jobs show pay |
| `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency` | `28`, `32`, `hour`, `USD` | hour, day, week, month or year |
| `salaryMinAnnual`, `salaryMaxAnnual` | `56000`, `64000` | ZipRecruiter's yearly equivalent, handy for comparing hourly and yearly pay |
| `salaryIsEstimated` | `false` | `true` when ZipRecruiter shows its own estimate instead of employer pay |
| `postedAt`, `postedAgo`, `isNew` | `2026-10-07T18:51:22Z`, `2 days ago`, `true` | |
| `quickApply` | `true` | the job has ZipRecruiter **1-Click Apply** (apply on ZipRecruiter); `false` = apply on the employer's site |
| `benefits` | `["Medical", "Vision", "Dental", "Retirement"]` | benefits ZipRecruiter lists on the card |
| `companyHighlights` | `["Good employer", "Respectful managers"]` | employee-review highlights shown on the card |
| `snippet` | `Position Summary The Triage Licensed Vocational Nurse...` | first ~200 characters of the ad |
| `searchKeyword`, `searchLocation`, `searchUrl`, `scrapedAt`, `detailsFetched` | | where and when the job was found |
| `description`, `descriptionHtml` | `Position Summary ...` | with **Include full job description** |
| `requisitionId`, `originalPostedAt`, `validThrough`, `occupationalCategory`, `industry`, `isReposted` | `R1001520`, `2026-10-05T02:49:19Z`, `2026-11-08`, `15-1132.00: Software Developers, Applications` | with details, when the job page has them |
| `companyWebsite`, `companyIndustries`, `companySize`, `companyHeadquarters`, `companyDescription` | `https://capitalone.com`, `["Real Estate"]`, `10000+ Employees`, `McLean, VA, US` | with details |
| `isExpired` | `false` | with details |

### How to scrape ZipRecruiter jobs by keyword and location

1. Enter one or more **Keywords** (job title, skill or company) and a **Location** such as `Houston, TX`, `Texas` or a
   ZIP code like `10001`. Leave the location empty for the whole US, or type `Remote` for remote jobs.
2. Optionally set filters: radius, posted within 1 / 5 / 10 / 30 days, employment type, experience level, remote /
   hybrid / on-site, minimum and maximum yearly salary, 1-Click Apply only.
3. Set **Max jobs** and run.

```json
{
  "keywords": ["registered nurse", "lvn"],
  "location": "Houston, TX",
  "radius": 50,
  "postedWithin": "5",
  "employmentTypes": ["full_time", "part_time"],
  "maxItems": 500
}
```

You can also paste ZipRecruiter URLs into **Start URLs**: search pages with any filters you set on the site
(`https://www.ziprecruiter.com/jobs-search?search=...&location=...`, also the `/Jobs/<Keyword>/-in-<City>,<ST>`
pages), and single job pages, which are returned with full details:

```json
{
  "startUrls": [
    { "url": "https://www.ziprecruiter.com/jobs-search?search=accountant&location=Chicago%2C+IL&radius=10&days=5" },
    { "url": "https://www.ziprecruiter.com/Jobs/Python-Developer/-in-New-York,NY" },
    { "url": "https://www.ziprecruiter.com/c/Capital-One/Job/Machine-Learning-Engineer-4-(Python,-AWS,-SQL,-GenAI)-(Enterprise-Platforms-Technology)/-in-New-York,NY?jid=da45851f325b78c7" }
  ],
  "maxItems": 300
}
```

#### Output example

```json
{
  "jobId": "3ea3edb543aab12a",
  "title": "Software Engineer - AI Trainer",
  "company": "DataAnnotation",
  "companyUrl": "https://www.ziprecruiter.com/co/DataAnnotation/Jobs",
  "location": "Edison, NJ",
  "city": "Edison",
  "stateCode": "NJ",
  "country": "US",
  "workplaceType": "On-site, Remote",
  "isRemote": true,
  "employmentType": "Contractor",
  "salaryText": "$75 - $150/hr",
  "salaryMin": 75,
  "salaryMax": 150,
  "salaryPeriod": "hour",
  "salaryCurrency": "USD",
  "salaryMinAnnual": 150000,
  "salaryMaxAnnual": 300000,
  "salaryIsEstimated": false,
  "postedAt": "2026-10-02T21:34:59Z",
  "postedAgo": "7 days ago",
  "quickApply": false,
  "benefits": [],
  "snippet": "About the Role: We are looking for existing software engineers and developers to help advance AI development...",
  "url": "https://www.ziprecruiter.com/c/DataAnnotation/Job/Software-Engineer-AI-Trainer/-in-Edison,NJ?jid=3ea3edb543aab12a",
  "searchKeyword": "python developer",
  "searchLocation": "New York, NY",
  "detailsFetched": false
}
```

### How many jobs can I get per search?

ZipRecruiter itself lists only about **150 to 1,100 jobs for any one search** (the "N jobs" number on the results
page), even when far more ads match. When ZipRecruiter reports that it cut a search, **Get more than ZipRecruiter
lists per search** (on by default) also runs narrower searches: by employment type, then experience level, then
remote / hybrid / on-site, then posting date. Each one lists its own jobs, and repeats are dropped (never charged).
In a test, "registered nurse" in Texas, which ZipRecruiter lists as 734 jobs, returned **3,000 distinct jobs** (the
run's limit) from 370 page requests. A search ZipRecruiter did not cut is simply read to the end.

### Can I get the full job description and company details?

Yes. Turn on **Include full job description**. For each job the actor opens the public job page and adds the full
description (plain text and HTML), the employer's requisition / reference id, the original posting date, the date the
ad expires, the occupation category, and the company box: website, industries, size, headquarters and description.
It is one extra request per job (a second, small one when ZipRecruiter has moved the job to a new link). Single job
URLs in Start URLs always come with details. Apply links that ZipRecruiter hands out only through its apply flow,
and anything that needs a ZipRecruiter login, are not collected.

### Does it work without login? Is a proxy needed?

It works without any login or cookies: it reads the same public search and job pages you see in the browser.
ZipRecruiter is protected by Cloudflare, so a proxy is needed. The default, Apify **RESIDENTIAL** proxy in the US,
passed 92-93% of requests on the first try in our tests and 100% after the automatic retries on a new IP. Datacenter
IPs work part of the time; if they get blocked the actor switches to residential IPs by itself.

### Monitor changes on a schedule

Turn on **Only new or changed items** to watch a search: each run remembers what earlier runs returned and outputs
only jobs that are new or whose title or pay line changed. Unchanged jobs are dropped before any job page is opened,
so they are not charged. **Max jobs** then sets how many jobs are checked per run; set **Posted within** to 1 day so
the window holds the newest jobs. Save the input as a task and add a Schedule in Apify (for example daily); give each
watch its own **Monitoring key** if several schedules use the same search. Every output item has `changeType` (`new`
or `changed`), and changed jobs have `previousValues` with the old values, e.g. `{"salaryText": "$75 - $150/hr"}`.

### Pricing

Pay per job: **$0.0024 per job** ($2.40 per 1,000). With **Include full job description** (or for a job URL) each
job adds a **$0.0008 detail** event, so $3.20 per 1,000 jobs with descriptions and company facts. No start fee and no
subscription.

### Proxy note and cost

Residential proxy traffic and compute are billed by Apify on top of the per-job price (usage paid by you). Measured:
a page of 20 jobs is about 40 KB of residential traffic, a job page with details about 30 KB. Platform usage was about
**$0.03 per 1,000 jobs** for a plain search, about $0.055 per 1,000 when large searches are split (more pages repeat
jobs), and about $0.27 per 1,000 jobs with details. Speed: about 25 jobs per second from search pages and 2-3 job
pages per second with details.

### Is it legal to scrape ZipRecruiter?

The actor reads only public job ads that anyone can open on ZipRecruiter without an account: what employers publish,
company names, pay information and the company facts ZipRecruiter shows next to the ad. It does not log in and does
not collect job seekers' data. Apply links and contact details that ZipRecruiter reveals only in its apply flow are
not collected. You are responsible for using the data in line with ZipRecruiter's terms and the privacy laws that
apply to you.

### Support

Found a bug or need a field added? Open an issue in the **Issues** tab; it is usually answered within a day.

# Actor input Schema

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

One search per keyword, e.g. "registered nurse", "python developer", "warehouse". Leave empty to list jobs matching only the location and filters.

## `location` (type: `string`):

Where, as you would type it on ZipRecruiter: a city ("Houston, TX"), a state ("Texas") or a ZIP code ("10001"). "Remote" = remote jobs. Leave empty for the whole US.

## `radius` (type: `integer`):

Distance around a city or ZIP code. ZipRecruiter's default is 25 miles; the site offers 5, 10, 25, 50 and 100.

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

Only jobs posted (or refreshed) within this many days.

## `employmentTypes` (type: `array`):

Only these employment types. Several = any of them.

## `experienceLevels` (type: `array`):

Only jobs for these experience levels. Several = any of them.

## `remoteFilter` (type: `string`):

Only remote, hybrid or on-site jobs (ZipRecruiter's Remote filter).

## `salaryMin` (type: `integer`):

Only jobs paying at least this much per year (ZipRecruiter's salary filter; it also uses its own pay estimates).

## `salaryMax` (type: `integer`):

Only jobs paying at most this much per year.

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

Only jobs you can apply to on ZipRecruiter with 1-Click Apply.

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

Optional: ZipRecruiter search URLs with any filters set on the site, e.g. https://www.ziprecruiter.com/jobs-search?search=nurse\&location=Houston%2C+TX\&days=5, or single job URLs (https://www.ziprecruiter.com/c/<company>/Job/<title>/-in-<city>?jid=<id>), which are returned with full details.

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

Stop after this many jobs in total (you pay per job saved).

## `maxItemsPerSearch` (type: `integer`):

Optional cap for each keyword / start URL, so one search does not use the whole Max jobs budget.

## `splitLargeSearches` (type: `boolean`):

ZipRecruiter lists only about 600-1,100 jobs for any search. When it reports that it cut a search, this runs narrower searches (by employment type, experience level, remote/on-site and posting date) that each list their own jobs, so you get many more. Repeats are dropped and not charged.

## `includeDetails` (type: `boolean`):

Open each job page for the full description (text and HTML), the employer's requisition id, original posting date, expiry date, occupation category and company facts (website, industries, size, headquarters, about). One extra request and one 'detail' event per job.

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new jobs or jobs whose title or pay line changed. Use with a Schedule; unchanged jobs are not charged. With this on, Max jobs is the number of jobs checked per run; set 'Posted within' to watch the newest jobs.

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input

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

Apify RESIDENTIAL proxy in the US (the default) passes ZipRecruiter's Cloudflare check; datacenter IPs are challenged more often (the actor then switches to residential by itself). Residential traffic is billed on your Apify plan: about 40 KB per page of 20 jobs (about $0.02 per 1,000 jobs) and about 30 KB per job page with details.

## Actor input object example

```json
{
  "keywords": [
    "python developer"
  ],
  "location": "New York, NY",
  "postedWithin": "",
  "remoteFilter": "",
  "quickApplyOnly": false,
  "maxItems": 100,
  "splitLargeSearches": true,
  "includeDetails": false,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

No description

# 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": [
        "python developer"
    ],
    "location": "New York, NY",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/ziprecruiter-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": ["python developer"],
    "location": "New York, NY",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/ziprecruiter-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": [
    "python developer"
  ],
  "location": "New York, NY",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call tortuga/ziprecruiter-scraper --silent --output-dataset

```

## MCP server setup

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