# ZipRecruiter Scraper (`mlg14/ziprecruiter-scraper`) Actor

Search public ZipRecruiter job listings by keyword, location or search URL. Export titles, companies, locations, work type (on-site/hybrid/remote), annual or hourly pay, benefits, links and optional full descriptions.

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

## Pricing

$1.00 / 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.

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

Scrape ZipRecruiter job listings by keyword and location, or supply one or more ZipRecruiter search URLs. Export ZipRecruiter job data to CSV, JSON, or Excel with titles, employers, locations, salary ranges, remote status, benefits, and links to listings. The actor offers a structured ZipRecruiter search alternative for repeatable job market research.

The actor opens ZipRecruiter's public search result pages in a real browser on a US residential connection, follows the result pages of each search, and returns one dataset row per unique job card. Optionally it opens every job to add the full description, employment type and posting age. Pay is reported as shown on the card (annual or hourly) with parsed numeric bounds.

### What data can you extract from ZipRecruiter?

The dataset is flat: each row represents a job returned by a search. Fields with no source value are omitted. Boolean fields are kept even when their value is `false`.

| Field | Description | Example |
| --- | --- | --- |
| `id` | ZipRecruiter listing token of the job card; used to remove duplicates. | `QVwKBIYx33oot9gJD1g2OQ` |
| `url` | ZipRecruiter search URL that opens this job (the `lk` parameter selects it). | `https://www.ziprecruiter.com/jobs-search?search=...&lk=QVwKBIYx33oot9gJD1g2OQ` |
| `title` | Job title. | `Lead Software Engineer` |
| `company` | Hiring company. | `Genworth` |
| `companyUrl` | ZipRecruiter company page. | `https://www.ziprecruiter.com/co/Genworth/Jobs/-in-Manhattan,NY` |
| `companyLogo` | Employer logo URL when the card has one. | An image URL |
| `location` | Location text of the job. | `Manhattan, NY` |
| `city` | City part of the location. | `Manhattan` |
| `state` | US state or Canadian province abbreviation. | `NY` |
| `workplaceType` | On-site, Hybrid or Remote label of the card. | `On-site` |
| `isRemote` | True when the card is labeled Remote or the location says remote. | `false` |
| `isHybrid` | True when the card is labeled Hybrid. | `false` |
| `salary` | Pay as displayed (annual or hourly). | `$120K - $234K/yr` |
| `salaryMin` | Lower pay bound as a number, in the unit of `salaryPeriod`. | `120000` |
| `salaryMax` | Upper pay bound as a number. | `234000` |
| `salaryPeriod` | `year`, `hour`, `month`, `week` or `day`. | `year` |
| `estimatedPay` | True when ZipRecruiter marks the pay as an estimate. | `false` |
| `benefits` | Benefit and employer-badge highlights on the card. | `Medical, Dental, Vision` |
| `quickApply` | Whether the card offers Quick apply. | `false` |
| `postedLabel` | Freshness badge (Posted today, New) when shown. | `Posted today` |
| `description` | Full job description (only with Include job description). | Plain text |
| `jobType` | Employment type, e.g. Full-time (only with Include job description). | `Full-time` |
| `daysPosted` | Days since posting (only with Include job description). | `4` |
| `inputUrl` | Search URL you supplied or the one built from keywords. | A `/jobs-search` URL |
| `searchUrl` | Normalized first-page search URL for this job. | A `/jobs-search` URL |
| `scrapedAt` | UTC timestamp of the run. | `2026-10-23T16:33:18Z` |

Fields without a value on the card are omitted. Amounts are US dollars as displayed; no currency conversion is done.

The `id` is the card token ZipRecruiter uses in its own links. Use it together with `scrapedAt` to track listings across runs; ZipRecruiter can re-post a job under a new token.

### How to scrape ZipRecruiter

1. Enter a job keyword such as `software engineer` and a location such as `New York, NY`. You can leave the location empty for a broader search.
2. Optionally choose employment type, a posting age, a search radius, remote eligibility, or annual salary bounds. To use an existing search, paste its `/jobs-search` URL into **Search URLs** instead.
3. Set **Maximum jobs** to the number of unique rows you want. Run the actor. It reads result pages of 20 jobs in one browser session and stops at the limit or when the results end.
4. Open the resulting dataset and download CSV, JSON, or Excel. The same rows are available through the dataset API for scheduled reporting.

For more than a small sample, provide several relevant searches. For example, a regional software hiring review can use different role titles and cities in one run. Search URLs take priority when both URL and keyword inputs are present. Results are deduplicated across every URL in that run.

### Input

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | Array of URLs | New York software engineer search | ZipRecruiter `/jobs-search` URLs. All filters in the URL (`days`, `radius`, `refine_by_employment`, `refine_by_location_type`, ...) are kept and every result page is followed. |
| `search` | String | `software engineer` | Role or keyword when no start URL is supplied. |
| `location` | String | `New York, NY` | City, state or postal code. |
| `jobType` | Enum | `any` | `any`, `full_time`, `part_time`, `contract`, `internship`, or `temporary`. Used with keyword input. |
| `daysPosted` | Integer | `0` | Posted within this many days (site steps: 1, 5, 10, 30; other values round up). Zero means any age. |
| `radiusMiles` | Integer | `0` | Radius around the location, applied by ZipRecruiter. Zero uses its default. |
| `remoteOnly` | Boolean | `false` | Only remote jobs (ZipRecruiter's remote filter). |
| `salaryMin` | Integer | Unset | Keep jobs whose listed annual pay reaches this amount. Jobs without a listed annual salary are dropped. |
| `salaryMax` | Integer | Unset | Keep jobs whose listed annual pay starts at or below this amount. Jobs without a listed annual salary are dropped. |
| `maxItems` | Integer | `30` | Maximum unique jobs across all searches, 1 to 1,000. |
| `maxPages` | Integer | `25` | Result pages read per search (20 jobs per page). |
| `includeDetails` | Boolean | `false` | Open each job for description, employment type and posting age. About 3 seconds more per job. |
| `proxyConfiguration` | Object | Apify Residential, US | ZipRecruiter only opens for a US residential IP; this is used automatically. |

Leave everything empty to run the default search. URL inputs must be ZipRecruiter `/jobs-search` pages on www.ziprecruiter.com.

### Output example

A real row from a golden run (inputUrl and searchUrl omitted for brevity):

```json
{
  "id": "QVwKBIYx33oot9gJD1g2OQ",
  "url": "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=New+York%2C+NY&lk=QVwKBIYx33oot9gJD1g2OQ",
  "title": "Lead Software Engineer",
  "company": "Genworth",
  "companyUrl": "https://www.ziprecruiter.com/co/Genworth/Jobs/-in-Manhattan,NY",
  "location": "Manhattan, NY",
  "city": "Manhattan",
  "state": "NY",
  "workplaceType": "On-site",
  "isRemote": false,
  "isHybrid": false,
  "salary": "$120K - $234K/yr",
  "salaryMin": 120000,
  "salaryMax": 234000,
  "salaryPeriod": "year",
  "estimatedPay": false,
  "benefits": "People enjoy working here, Good training, Respectful managers",
  "quickApply": false,
  "scrapedAt": "2026-10-23T16:33:18Z"
}
```

### Use cases

- **Recruiting research:** collect advertised roles in a city and compare employers, location types, and posting age without manually opening every result card.
- **Compensation sampling:** collect available annual ranges for a role and location, then analyze the distribution in a spreadsheet. Filter out rows without salary before calculating averages.
- **Remote job tracking:** use `remoteOnly` to monitor remote eligible roles, then inspect `jobType` to distinguish remote and remote optional labels.
- **Regional hiring reports:** run related searches in several cities and group results by `inputUrl`, `city`, or `state` to compare the visible listings.
- **Benefits surveys:** use the optional benefits text to identify which benefits appear in job cards for a chosen market. Treat missing benefits as unknown, not as a statement that none are offered.
- **Posting monitoring:** schedule repeat runs and compare listing tokens and posting ages to flag newly observed jobs. A change in search ranking can also affect what appears in each run.

These are observations about public job cards. They are not a complete count of every open role, a salary survey of all employers, or proof that a listing remains open. Validate important decisions against the live listing.

### How much does it cost to scrape ZipRecruiter?

The actor charges **$0.001 per delivered job row, which is $1.00 per 1,000 results**, and platform usage is included.

| Delivered rows | Result charge |
| ---: | ---: |
| 30 | $0.03 |
| 500 | $0.50 |
| 5,000 | $5.00 |

A run reads 20 jobs per page, so 500 jobs means 25 pages in one session. Detail mode (`includeDetails`) does not change the price per row but makes the run slower.

### Tips for best results

Use a concrete role and location for comparable results; broad terms pull in adjacent roles. Start with 20 to 40 jobs to confirm the search is useful, then raise `maxItems`.

ZipRecruiter shows at most a few dozen result pages per search. For larger collections, split by city, role phrase or `daysPosted` and let the actor deduplicate within the run.

Use `daysPosted` with 1 or 5 for daily monitoring of new jobs; it is applied by ZipRecruiter, not after fetching. Filters in a pasted URL work the same way. `salaryMin` and `salaryMax` are checked on the card's annual pay range, so jobs without a listed salary are removed when you set them.

Leave **Include job description** off unless you need descriptions; it adds about 3 seconds per job.

### Limits

ZipRecruiter protects its search pages with a Cloudflare browser check. The actor passes the automatic check with a real browser on a US residential IP. It never solves interactive CAPTCHAs; if one is shown on every retry, the run fails with a clear message instead of returning empty data.

Only the US site (www.ziprecruiter.com) is supported. Runs take 1 to 2 minutes for 30 jobs because the browser must start and pass the check first. The `url` field opens the job inside a ZipRecruiter search page; direct employer apply links are not exposed. Pay, benefits, employment type and posting age appear only when the employer or ZipRecruiter shows them. The actor does not log in or read applicant data.

### Automated workflows

A scheduled run can feed a weekly report or a job monitoring pipeline. For example: “Collect up to 30 software engineering jobs across New York, Austin, and Chicago, then group the results by city and remote status.” Another useful request is: “Track remote eligible data engineering listings every morning and compare listing tokens with yesterday's dataset.” Define the searches explicitly so each run can be compared on the same basis.

### FAQ

#### Is it legal to use this data?

The actor collects public job card information. You are responsible for using it in line with applicable law, ZipRecruiter's terms, and privacy requirements. Respect reasonable collection volume and do not use the data to misuse personal information. No applicant profiles or login data are collected.

#### Do I need to configure a proxy?

No. The actor uses Apify Residential proxies in the US automatically, because ZipRecruiter blocks datacenter addresses. Residential traffic is included in the result price.

#### How fast is a run?

About 1 to 2 minutes for 30 jobs, including browser start and the security check. Each further page of 20 jobs takes roughly 10 to 20 seconds.

#### Can I schedule and monitor searches?

Yes. Save the input, schedule the actor, and compare successive datasets. Use `scrapedAt` and `inputUrl` when merging runs. Check run status as well as row count; a smaller dataset may reflect a narrower accessible search window or a source change.

#### Can I export to a spreadsheet?

Yes. Download the dataset as CSV or Excel, or use the dataset API to transfer JSON rows into a sheet. Numeric salary bounds are separate from the formatted salary string, making them easier to sort and aggregate. Filter missing salary fields before calculating statistics.

#### Why are some fields missing?

A field is omitted when the card does not show it. Salary, benefits and logos vary by employer. `isRemote: false` means the card is not labeled Remote.

#### Why did my search return fewer than `maxItems`?

The search ran out of result pages or filters such as salary removed rows. Try neighboring cities or related role phrases.

### Integrations

Use the default dataset through the Apify API, webhooks, or scheduled runs. You can connect the output to automation services, spreadsheets, databases, and reporting systems that accept CSV or JSON. Keep `id`, `inputUrl`, and `scrapedAt` when storing history so you can trace the origin and timing of each observation.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request. Include the input and run URL when a search stops unexpectedly or a field changes.

# Actor input Schema

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

ZipRecruiter search URLs (www.ziprecruiter.com/jobs-search?search=...\&location=...). All filters in the URL are used and every result page is followed. Overrides the keyword inputs.

## `search` (type: `string`):

Job role to search for when startUrls is empty.

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

City, state, or postal code for the keyword search. If omitted, the search is nationwide.

## `jobType` (type: `string`):

Employment type filter, applied by ZipRecruiter itself (keyword searches only).

## `daysPosted` (type: `integer`):

Only jobs posted within this many days (ZipRecruiter supports 1, 5, 10 or 30; other values are rounded up). Zero means any age.

## `radiusMiles` (type: `integer`):

Search radius around the location, applied by ZipRecruiter. Zero uses the site default (25).

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

Only remote jobs (ZipRecruiter's 'remote' filter).

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

Only keep jobs whose listed annual pay reaches at least this amount (checked on the card's salary range; jobs without a listed annual salary are dropped).

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

Only keep jobs whose listed annual pay starts at or below this amount (checked on the card's salary range; jobs without a listed annual salary are dropped).

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

Stop after this many unique job listings across all searches.

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

Proxy settings. ZipRecruiter only opens for a US residential IP, so the actor always uses Apify Residential proxies (country US) unless you provide your own proxy.

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

Open each job to add the full description, employment type and posting age. Slower (about 3 seconds per job) and uses more proxy traffic.

## `maxPages` (type: `integer`):

Result pages to read per search (20 jobs per page).

## Actor input object example

```json
{
  "startUrls": [
    "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=New+York%2C+NY"
  ],
  "search": "software engineer",
  "location": "New York, NY",
  "jobType": "any",
  "daysPosted": 0,
  "radiusMiles": 0,
  "remoteOnly": false,
  "maxItems": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "includeDetails": false,
  "maxPages": 25
}
```

# Actor output Schema

## `jobs` (type: `string`):

Unique job listings in the default dataset.

# 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 = {
    "startUrls": [
        "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=New+York%2C+NY"
    ],
    "search": "software engineer",
    "location": "New York, NY",
    "maxItems": 30,
    "includeDetails": false,
    "maxPages": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/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 = {
    "startUrls": ["https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=New+York%2C+NY"],
    "search": "software engineer",
    "location": "New York, NY",
    "maxItems": 30,
    "includeDetails": False,
    "maxPages": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("mlg14/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 '{
  "startUrls": [
    "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=New+York%2C+NY"
  ],
  "search": "software engineer",
  "location": "New York, NY",
  "maxItems": 30,
  "includeDetails": false,
  "maxPages": 25
}' |
apify call mlg14/ziprecruiter-scraper --silent --output-dataset

```

## MCP server setup

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