# ZipRecruiter Job Search Results & Salary Scraper (`vexaloom-labs/ziprecruiter-job-search-results-and-salary-scraper`) Actor

Extract job postings from ZipRecruiter.com search results - title, company, location, parsed salary range, city, state, and apply URL. Search by keyword and location or supply your own filtered search URL.

- **URL**: https://apify.com/vexaloom-labs/ziprecruiter-job-search-results-and-salary-scraper.md
- **Developed by:** [vexaloom-labs](https://apify.com/vexaloom-labs) (community)
- **Categories:** Jobs, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## ZipRecruiter Jobs & Salary Scraper

Collect **ZipRecruiter job postings and salary benchmarks** in one run. Search by keyword across locations, browse a single employer, paste search/job/company URLs, or ask for salary percentiles by role and city. Job rows carry the full description, structured pay with a source flag, benefits, company profile, dates and the real apply destination.

### What this actor does

- **Three modes:** search jobs, scrape exact URLs, or read salary benchmarks.
- **Multi-query × multi-location:** every keyword is searched in every location, deduplicated by ZipRecruiter's own job identity.
- **Company-scoped search:** set a company name or paste a `/co/<company>/Jobs` URL to get only that employer's postings.
- **Rich job rows:** full description text and HTML, structured pay (min/max, period, currency, annualized values, provided vs predicted), benefits, workplace and employment types, posting dates, education, skills, company industry/website/size/HQ/rating, and apply links.
- **Salary directory:** median, 10th/25th/75th/90th percentile, average annual and hourly pay, related titles and top-paying cities — something plain job-board scrapers usually skip.
- **Filters that work:** employment type, remote/hybrid/on-site, experience level, posted within N days, radius, and minimum salary (maximum is best effort).
- **HTTP-first and cheap:** search, detail and salary pages are fetched directly with a Safari TLS profile. A headed browser with a US residential exit is used automatically only when the source challenges the request.
- **No setup:** no cookies, account, API key or proxy configuration. Omit-empty everywhere: a value the source does not show is left out, never sent as `null`.

### Modes

| Mode | What it reads | Example input |
|---|---|---|
| `search` | Keyword × location searches, or a company name | `searchQueries: ["registered nurse"]`, `locations: ["Chicago, IL"]` |
| `byUrls` | Search, job, company or salary URLs you already have | `startUrls: ["https://www.ziprecruiter.com/c/..."]` |
| `salary` | Salary pages for role × location, or exact salary URLs | `salaryRoles: ["data analyst"]`, `salaryLocations: ["Austin, TX"]` |

### Output

Job records use `type: job_ziprecruiter` and `recordType: job`.

Identity: `id`, `jid`, `listingKey`, `openSeatId`, `matchId`, `url`, `title`.

Company: `company`, `companyId`, `companyUrl`, `companyLogo`, `companyWebsite`, `companyIndustries`, `companySize`, `companyHqLocation`, `companyRating`, `companyAbout`, `companyFoundedYear`.

Location: `location`, `city`, `state`, `country`, `countryCode`, `postalCode`, `county`, `locationId`, `workplaceTypes`, `isRemote`, `isHybrid`.

Pay: `salary`, `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency`, `salaryMinAnnual`, `salaryMaxAnnual`, `salarySource` (`provided`, `parsed`, `predicted` or `missing`), `isEstimatedSalary`.

Job: `description`, `descriptionHtml`, `shortDescription`, `employmentTypes`, `benefits`, `educationLevels`, `experienceMonths`, `postedAt`, `rollingPostedAt`, `postedAgo`, `validThrough`, `isActive`, `isNew`, `skills`, `occupationalCategory`, `industry`.

Apply and meta: `applyUrl`, `applyType`, `directApply`, `shareUrl`, `inputUrl`, `searchQuery`, `descriptionFetchFailed`, `scrapedAt`.

Salary records use `type: salary_ziprecruiter` and `recordType: salary`: `salaryRole`, `salaryLocation`, `salaryMedian`, `salaryP10`, `salaryP25`, `salaryP75`, `salaryP90`, `salaryAverage`, `salaryAverageHourly`, `salaryCurrency`, `salaryPeriod`, `salaryUrl`, `relatedTitles`, `topPayingCities`, `scrapedAt`.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | Select | `search` | `search`, `byUrls` or `salary` |
| `searchQueries` | String list | `[]` | Keywords searched in every location |
| `locations` | String list | `["United States"]` | Cities, states, ZIPs or nationwide |
| `companyName` | String | – | Only this employer's jobs; empty keywords browse all of them |
| `startUrls` | String list | `[]` | Search, job, company or salary URLs for `byUrls` |
| `jobType` | Select | `any` | `full_time`, `part_time`, `contract`, `contract_to_hire`, `temporary`, `as_needed`, `other`, `internship` |
| `workplace` | Select | `any` | `remote`, `hybrid`, `onsite` |
| `experienceLevels` | Multi-select | `[]` | `no_experience`, `junior`, `mid`, `senior` |
| `daysPosted` | Integer | `0` | Posted within N days (0–30); 0 = any |
| `radiusMiles` | Integer | `0` | Radius around each location (0–100); 0 = default |
| `salaryMin` | Integer | `0` | Annual minimum; 0 = any |
| `salaryMax` | Integer | `0` | Annual maximum (best effort); 0 = any |
| `fetchDescriptions` | Boolean | `true` | Fetch detail pages for full descriptions and pay |
| `includeCompanyProfile` | Boolean | `true` | Add industry, website, size, HQ, rating and about |
| `maxItems` | Integer | `50` | Total records across all searches |
| `maxSearches` | Integer | `100` | Cap on keyword × location searches |
| `salaryRoles` | String list | `[]` | Role titles for `salary` mode |
| `salaryLocations` | String list | `["United States"]` | Locations for `salary` mode |
| `salaryUrls` | String list | `[]` | Exact `/Salaries/...` URLs; overrides role/location pairs |

#### Example: keyword search with filters

```json
{
  "mode": "search",
  "searchQueries": ["registered nurse"],
  "locations": ["Chicago, IL"],
  "jobType": "full_time",
  "daysPosted": 7,
  "maxItems": 50
}
```

#### Example: several queries and locations

```json
{
  "mode": "search",
  "searchQueries": ["data engineer", "analytics engineer"],
  "locations": ["Texas", "Florida"],
  "workplace": "remote",
  "experienceLevels": ["senior"],
  "salaryMin": 120000,
  "maxItems": 200
}
```

#### Example: one company's openings

```json
{
  "mode": "search",
  "companyName": "Visa",
  "locations": ["United States"],
  "fetchDescriptions": true,
  "maxItems": 100
}
```

#### Example: exact URLs

```json
{
  "mode": "byUrls",
  "startUrls": [
    "https://www.ziprecruiter.com/jobs-search?search=fire+alarm+technician&location=New+York%2C+NY&days=7",
    "https://www.ziprecruiter.com/c/Company/Job/Title/-in-Chicago,IL?jid=abcdef1234567890",
    "https://www.ziprecruiter.com/co/Visa/Jobs"
  ],
  "maxItems": 100
}
```

#### Example: salary benchmarks

```json
{
  "mode": "salary",
  "salaryRoles": ["software engineer", "data analyst"],
  "salaryLocations": ["New York, NY", "Austin, TX"],
  "maxItems": 10
}
```

### Use cases

- **Talent intelligence:** follow hiring volume and role mix for a company, city or industry.
- **Compensation research:** compare provided versus predicted pay and build salary bands from percentiles.
- **Recruiting pipelines:** export clean rows with real apply links and workplace types.
- **Remote-work tracking:** measure remote, hybrid and on-site mixes across markets.
- **Job-board products:** refresh an internal board with scheduled runs and deduplicate on `jid`.

### Limitations

- ZipRecruiter's public data has no latitude/longitude, so this actor does not invent coordinates.
- Search results arrive in ZipRecruiter's own order; the public site ignores sort parameters, so no sort input is offered.
- One search is capped at roughly 10,000 results. Split broad topics by keyword, location or filter for more.
- The salary maximum filter is best effort because ZipRecruiter's ceiling parameter does not narrow reliably; the minimum filter is server-side and is also checked on each row.
- `salarySource: predicted` means ZipRecruiter estimated the range, not that the employer published it. Values are kept and flagged rather than silently mixed with provided pay.
- US and Canada listings on ziprecruiter.com are covered. The separate UK and Ireland sites are not.
- A very broad run can hit detail-page rate limits; the actor pauses, backs off and keeps card rows with `descriptionFetchFailed` instead of failing the whole run.

### FAQ

**Do I need a ZipRecruiter account, cookies or a proxy?**
No. Only public pages are read, and the actor chooses its own network path. There is nothing to configure.

**What is the difference between the three modes?**
`search` discovers jobs from keywords, locations or a company. `byUrls` reads exact search, job, company or salary URLs. `salary` reads salary benchmark pages and returns percentile/average pay.

**Why do some rows have no salary?**
ZipRecruiter does not publish pay for every posting. When it shows an estimated range, the row is marked `salarySource: predicted` and `isEstimatedSalary: true`; when it shows nothing, the pay fields are omitted rather than filled with zero.

**Do I get the full job description?**
Yes when `fetchDescriptions` is on. It returns both plain text and HTML. Turn it off for faster, cheaper card-only runs.

**What does `descriptionFetchFailed: true` mean?**
The detail page could not be read for that posting, so the row was kept from search-card data. Re-run those job URLs later if you need the description.

**Why is the same job missing from a second search?**
Jobs are deduplicated by ZipRecruiter's job id across every query and location in the run, so a posting found twice is delivered once. Running the same search on another day can return different rows as ZipRecruiter re-ranks and refreshes listings.

**How current is the salary data?**
Salary pages are ZipRecruiter's own estimates and are returned as shown, including median, percentiles, average annual/hourly pay, related titles and top-paying cities. They are benchmarks, not offers.

**Can I filter for 1-Click Apply jobs?**
There is no reliable server-side filter for it, so it is not exposed as an input. `applyType` on each row tells you whether the application is external, internal or quick-apply.

**How many results can I get?**
`maxItems` supports up to 10,000 per run, and that also matches ZipRecruiter's per-search ceiling. For larger datasets, split the work into multiple runs by keyword or location.

**Is this affiliated with ZipRecruiter?**
No. It is an independent tool that reads ZipRecruiter's public job and salary data.

# Actor input Schema

## `mode` (type: `string`):

Search job postings, scrape URLs you already have, or get salary benchmarks for a role and location.

## `searchQueries` (type: `array`):

Job titles or keywords, e.g. `registered nurse` or `software engineer`. Every keyword is searched in every location below.

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

Cities, states, ZIP codes or `United States` for a nationwide search. Every keyword is searched in every location.

## `companyName` (type: `string`):

Only jobs from this employer, e.g. `Visa`. Leave the keywords empty to browse every open role at the company.

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

Search, job, company or salary URLs. Use this with `Scrape specific URLs`; filters already in a search URL are kept.

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

Employment-type filter for keyword searches.

## `workplace` (type: `string`):

Work arrangement for keyword searches.

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

Restrict to one or more seniority levels. Empty means no filter.

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

Only jobs posted within the last N days. 0 means any time.

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

Distance around each location. 0 uses ZipRecruiter's default.

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

Only jobs whose advertised annual pay reaches this amount. 0 means no lower bound.

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

Only jobs whose advertised annual pay stays at or below this amount. Best effort; 0 means no upper bound.

## `fetchDescriptions` (type: `boolean`):

Fetch each posting's detail page for the complete description, benefits, company profile and apply details. Off gives faster card-only rows.

## `includeCompanyProfile` (type: `boolean`):

Add industry, website, size, headquarters, rating and about text from the posting's company data.

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

Maximum number of job or salary records to return across all searches and URLs.

## `maxSearches` (type: `integer`):

Cap on keyword × location combinations in search mode.

## `salaryRoles` (type: `array`):

Job titles for salary benchmarks, e.g. `registered nurse` or `data analyst`. Used in salary mode.

## `salaryLocations` (type: `array`):

Cities, states or `United States` for salary benchmarks. Every role is looked up in every location.

## `salaryUrls` (type: `array`):

Exact ZipRecruiter `/Salaries/...` URLs. When set, these are used instead of role and location pairs.

## Actor input object example

```json
{
  "mode": "search",
  "searchQueries": [
    "registered nurse"
  ],
  "locations": [
    "Chicago, IL"
  ],
  "companyName": "",
  "startUrls": [],
  "jobType": "any",
  "workplace": "any",
  "experienceLevels": [],
  "daysPosted": 0,
  "radiusMiles": 0,
  "salaryMin": 0,
  "salaryMax": 0,
  "fetchDescriptions": true,
  "includeCompanyProfile": true,
  "maxItems": 3,
  "maxSearches": 1,
  "salaryRoles": [
    "registered nurse"
  ],
  "salaryLocations": [
    "Chicago, IL"
  ],
  "salaryUrls": []
}
```

# Actor output Schema

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

Job rows (type `job_ziprecruiter`) with description, pay, company, location, benefits, dates and apply details.

## `salaries` (type: `string`):

Salary rows (type `salary_ziprecruiter`) with median, percentile, average, related titles and top-paying cities.

# 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 = {
    "mode": "search",
    "searchQueries": [
        "registered nurse"
    ],
    "locations": [
        "Chicago, IL"
    ],
    "startUrls": [],
    "jobType": "any",
    "workplace": "any",
    "experienceLevels": [],
    "daysPosted": 0,
    "radiusMiles": 0,
    "salaryMin": 0,
    "salaryMax": 0,
    "fetchDescriptions": false,
    "includeCompanyProfile": false,
    "maxItems": 3,
    "maxSearches": 1,
    "salaryRoles": [
        "registered nurse"
    ],
    "salaryLocations": [
        "Chicago, IL"
    ],
    "salaryUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("vexaloom-labs/ziprecruiter-job-search-results-and-salary-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 = {
    "mode": "search",
    "searchQueries": ["registered nurse"],
    "locations": ["Chicago, IL"],
    "startUrls": [],
    "jobType": "any",
    "workplace": "any",
    "experienceLevels": [],
    "daysPosted": 0,
    "radiusMiles": 0,
    "salaryMin": 0,
    "salaryMax": 0,
    "fetchDescriptions": False,
    "includeCompanyProfile": False,
    "maxItems": 3,
    "maxSearches": 1,
    "salaryRoles": ["registered nurse"],
    "salaryLocations": ["Chicago, IL"],
    "salaryUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("vexaloom-labs/ziprecruiter-job-search-results-and-salary-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 '{
  "mode": "search",
  "searchQueries": [
    "registered nurse"
  ],
  "locations": [
    "Chicago, IL"
  ],
  "startUrls": [],
  "jobType": "any",
  "workplace": "any",
  "experienceLevels": [],
  "daysPosted": 0,
  "radiusMiles": 0,
  "salaryMin": 0,
  "salaryMax": 0,
  "fetchDescriptions": false,
  "includeCompanyProfile": false,
  "maxItems": 3,
  "maxSearches": 1,
  "salaryRoles": [
    "registered nurse"
  ],
  "salaryLocations": [
    "Chicago, IL"
  ],
  "salaryUrls": []
}' |
apify call vexaloom-labs/ziprecruiter-job-search-results-and-salary-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vexaloom-labs/ziprecruiter-job-search-results-and-salary-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/vxRSmgrG9noDPw1BM/builds/MiuZfLBEFxbiXz4Uj/openapi.json
