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

From $1/1K. Scrape ZipRecruiter jobs with titles, companies, salaries, locations, benefits, apply URLs and full descriptions. Search with filters or paste ZipRecruiter URLs directly, with automatic pagination and simple pay-per-result pricing.

- **URL**: https://apify.com/abotapi/ziprecruiter-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 6 total users, 0 monthly users, 92.6% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $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.
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 Scraper: Job Titles, Parsed Salary, Location & Apply Links

ZipRecruiter Scraper turns ziprecruiter.com job search into structured data. Get title, company, company logo, parsed salary (min, max, period), structured location, employment type, benefits, posted date and an apply link for every listing. Build a search from keywords and filters, or paste any ziprecruiter.com search or job URL straight from your browser, then export to JSON, CSV or Excel, or read the results through the API.

### Why This Scraper?

- **Two ways to find listings.** Build a search from keywords, location and filters, or paste finished ziprecruiter.com search or job URLs (multiple supported, each pages forward on its own).
- **About 50 fields per record, around 35 filled from the search results alone.** Parsed salary (min, max, period), structured location (city, state, county), employment type, company logo and an apply URL ship without opening a detail page; company website, full address, coordinates, industry, benefits text and the full description need Fetch full listing details.
- **Optional detail mode for the long form.** Switch it on for the full description text, a structured street address, industry, valid-through date, verified salary, and any contact email or phone number the employer pasted into the listing.
- **Filters that apply to the search and the results.** Date posted, employment type, remote-only, minimum and maximum salary, workplace type, external-apply only and search radius.
- **Forward pagination with de-duplication.** The actor reads the site's own result-count to know when a search ends, and never saves the same job id twice.
- **Continue a large pull across separate runs.** Point `resumeFromRunId` at a previous run or dataset and only the listings not already collected there are saved (and billed).
- **Resilient to a refused request.** The actor automatically opens a fresh connection and retries when one request is refused, so a single blocked page does not sink the whole run.

### Use Cases

- **Job board aggregation:** build or refresh your own jobs feed with structured salary, location and employment-type data.
- **Recruiting and sourcing:** pull open roles by keyword and location to find which companies are hiring for a given skill set.
- **Labor market and salary research:** collect parsed salary ranges across roles, regions and employment types for benchmarking.
- **Competitor and hiring-trend tracking:** monitor which roles a company is posting and how its listings change over time.
- **New-listing alerting:** watch a keyword or location and use `resumeFromRunId` to skip jobs already collected in that specific run or dataset.
- **Data science and NLP:** build a labeled dataset of job titles, descriptions and requirements for classification or matching models.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `id` | `"ad8a4fef327eab36"` |
| `title` | `"Full Stack Software Engineer"` |
| `company` | `"Sample Employer Inc"` |
| `companyWebsite` | `"sampleemployer.com"` (detail mode only) |
| `companyLogo` | `"https://www.ziprecruiter.com/svc/fotomat/.../sample123.png"` |
| `location` / `city` / `state` | `"Reston, VA"` / `"Reston"` / `"Virginia"` |
| `county` / `country` | `"Fairfax"` / `"United States"` |
| `locationType` / `workplaceType` | `"On-site"` / `"On-site"` |
| `remote` / `isRemote` | `false` / `false` |
| `salary` | `"$123K - $161K/yr"` |
| `salaryMin` / `salaryMax` | `123000` / `161000` |
| `salaryPeriod` / `salaryCurrency` | `"year"` / `"USD"` |
| `employmentType` / `employmentTypes` | `"FULL_TIME"` / `["FULL_TIME"]` |
| `benefits` | `["MEDICAL", "LIFE_INSURANCE"]` |
| `industry` / `occupationalCategory` | `"Technology"` / `"15-1133.00: Software Developers, Systems Software"` (detail mode only) |
| `snippet` | `"THE IMPACT YOU WILL MAKE The Full Stack Software Engineer role will offer you..."` |
| `descriptionText` | full listing description text (detail mode only) |
| `emails` / `phoneNumbers` | parsed from the listing text when an employer pasted one; usually empty |
| `datePosted` / `validThrough` | `"2026-04-24T00:00:00Z"` / `"2026-06-23"` (`validThrough` is detail mode only) |
| `applyUrl` / `sponsored` | ziprecruiter.com link carrying the job's `jid` / `false` |
| `isExternalApply` / `contactEmail` / `contactWebsite` | `false` (true when applying on the employer's own site) / employer contact from the listing / company site (the last two detail mode only, may be null) |
| `descriptionMarkdown` | description as Markdown (detail mode only) |
| `detailsFetched` / `descriptionIncomplete` | whether the detail page was read / whether the full description is missing |
| `inputUrl` | the link that produced the row: the pasted URL in URL mode, the built search link in search mode |
| `source` / `scrapedAt` | `"ZipRecruiter"` / `"2026-05-25T00:00:00.000Z"` |

Fields marked "detail mode only" are populated only when **Fetch detail pages** is on. Every plain search-mode run still carries `id`, `url`, `listingKey`, `title`, `company`, `location`, `salary`, `employmentTypes`, `benefits` and `snippet`; `emails` and `phoneNumbers` are always present as arrays but are usually empty, since ZipRecruiter exposes no structured contact field.

#### Output views

The dataset has four views in the Console: **Listings** (the search-level columns plus workplace type, external-apply flag and detail status), **Job details** (full description text and HTML, industry, address, valid-through date), **Contacts** (company, location, emails, contact email, phone numbers, company website) and **Changes (incremental)** (change type and changed fields, filled when incremental mode is on). All views read the same records; no data is dropped from the dataset.

### How to Use

1. Pick a **mode**: `search` (build a query from keywords and filters) or `url` (paste one or more ziprecruiter.com links).
2. For search mode, fill in keywords and location, then switch on any filters you need (date posted, employment type, remote-only, minimum salary, radius). For URL mode, paste your links; date posted and radius are ignored (they only shape the search URL), but employment type, remote-only and minimum salary still screen the results after they're fetched.
3. Set **Max pages per search** and **Max listings (total)** to control run size and cost, and turn on **Fetch detail pages** if you need the full description.
4. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it through the API.

**Basic keyword search:**

```json
{
  "mode": "search",
  "queries": ["software engineer"],
  "location": "Remote",
  "maxPages": 5,
  "fetchDetails": false
}
```

**Search with filters (full-time, posted in the last 5 days, minimum salary):**

```json
{
  "mode": "search",
  "queries": ["registered nurse"],
  "location": "Chicago, IL",
  "datePosted": "5",
  "employmentType": "full_time",
  "minSalary": 70000,
  "maxPages": 10,
  "fetchDetails": true
}
```

**Remote jobs only, multiple keywords:**

```json
{
  "mode": "search",
  "queries": ["data analyst", "data engineer", "data scientist"],
  "remoteOnly": true,
  "maxPages": 5,
  "maxListings": 300
}
```

Supported URL shapes: search URLs (`/jobs-search?search=...&location=...`), legacy keyword paths (`/Jobs/Software-Engineer`, mapped to a search) and single job URLs (`/c/<Company>/Job/<Title>/-in-<City>,<ST>?jid=<id>`). Remote and minimum-salary facets present in a pasted search URL are applied to the results. Note: in URL mode, leaving the URLs field untouched now scrapes one example of each shape instead of nothing.

**Paste finished ziprecruiter.com URLs (URL mode):**

```json
{
  "mode": "url",
  "urls": [
    "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=New+York%2C+NY",
    "https://www.ziprecruiter.com/jobs-search?search=warehouse&location=Dallas%2C+TX&days=5"
  ],
  "maxPages": 3
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/ziprecruiter-com-scraper").call(
    run_input={"mode": "search", "queries": ["software engineer"], "location": "Remote"}
)
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], job["company"], job["salary"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/ziprecruiter-com-scraper').call({
  mode: 'search',
  queries: ['software engineer'],
  location: 'Remote',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Employment-type filtering

ZipRecruiter treats the employment-type filter loosely: it narrows the search but also backfills the results with related jobs, so a part-time search can still surface some full-time roles. This scraper passes your choice to the search and also screens the results so the output favors your requested type, but keeps jobs whose type ZipRecruiter did not label rather than dropping them, which keeps results flowing in thin markets. If you need an exact match only, filter the `employmentTypes` field in your downstream pipeline.

#### Caps and resuming a large pull

- **`maxPages`** and **`maxListings`** cap a single run. Leave `maxPages` at `0` to walk every page ZipRecruiter serves for a search, bounded only by `maxListings` and the site's own result total.
- **`resumeFromRunId`** continues a pull across separate runs: paste a previous run's ID (or its dataset ID), and this run loads every job id already saved there and appends only the ones it hasn't seen.
- If a run is interrupted by an Apify platform migration, or you click **Resurrect** on a failed run, it picks back up automatically in the same run without re-saving, or re-charging for, listings it already collected. No input is needed for this.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` builds a query from the fields below; `url` reads from `urls`. |
| `queries` | array | none (prefill: `["software engineer"]`) | Keywords. One scrape per keyword. Empty browses without a keyword filter. |
| `location` | string | none (prefill: `"Remote"`) | City and state, ZIP, or "Remote". Left empty, the search runs nationwide. |
| `datePosted` | string | `any` | `any`, `1`, `5`, `10`, or `30` days. |
| `employmentType` | string | `any` | `any`, `full_time`, `part_time`, `contract`, `temporary`, `internship`. |
| `remoteOnly` | boolean | `false` | Only return jobs flagged remote. |
| `minSalary` | integer | `0` | Minimum advertised annual salary. `0` = no floor. |
| `radius` | integer | `0` | Search radius in miles around the location. `0` = site default. |
| `skipSponsored` | boolean | `false` | Drop sponsored placements (applied client-side). |
| `urls` | array | one example per URL shape (search, legacy path, job) | URL mode: one or more ziprecruiter.com search or job URLs. |
| `maxPages` | integer | `0` | Result pages to walk per search or URL (about 20 listings each). `0` = unlimited: walk every page ZipRecruiter serves for that search. |
| `maxItemsPerSearch` | integer | `0` | Extra ceiling per keyword or pasted link, on top of the even share of Max listings. `0` = off. |
| `maxListings` | integer | `20` | Total listings cap across all searches. `0` = no cap (still bounded by `maxPages`). |
| `fetchDetails` | boolean | `false` | Also open each job page for the full description, structured address, industry, valid-through date and verified salary. |
| `workplace` | string | `any` | `any`, `remote`, `hybrid`, `onsite`. A client-side screen over the site's workplace tag; `onsite` also keeps jobs with no tag. |
| `maxSalary` | integer | `0` | Drop jobs whose lowest advertised annual pay is above this figure (only per-year pay is screened). `0` = off. |
| `externalApplyOnly` | boolean | `false` | Keep only jobs whose apply button leads to an external page. |
| `descriptionMaxLength` | integer | `0` | Cut the text and Markdown descriptions to this many characters; the HTML description is set to null when longer. `0` = full text. |
| `omitEmptyFields` | boolean | `false` | Leave out fields that are null, empty text or an empty list. |
| `resumeFromRunId` | string | none | A previous run ID or dataset ID to continue a large pull across separate runs. Listings already collected there (matched by job id) are skipped. |
| `incrementalMode` | boolean | `false` | Recurring monitoring: return only NEW, UPDATED and REAPPEARED jobs after the first run. |
| `stateKey` | string | derived from the search | Name a monitoring campaign (incremental mode only). |
| `emitUnchanged` | boolean | `false` | Also return UNCHANGED jobs (incremental mode only). |
| `emitExpired` | boolean | `false` | Also return jobs no longer found, marked EXPIRED, after a complete walk (incremental mode only). |
| `ignoreFieldsForChanges` | array | none | Extra output fields that never count as a change (incremental mode only). |
| `proxy` | object | none in the schema (prefill: Apify Proxy, `RESIDENTIAL` group, country `US`); when this field is left out entirely, the actor applies that same residential US routing automatically | Connection settings. A residential connection is required; see Plan Requirement below. |
| `mcpConnectors` | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "id": "ad8a4fef327eab36",
  "listingKey": "sample-listing-key",
  "url": "https://www.ziprecruiter.com/c/Sample-Employer-Inc/Job/Full-Stack-Software-Engineer/-in-Reston,VA?jid=ad8a4fef327eab36",
  "title": "Full Stack Software Engineer",
  "company": "Sample Employer Inc",
  "companyWebsite": "sampleemployer.com",
  "companyLogo": "https://www.ziprecruiter.com/svc/fotomat/public-nosensitive-ziprecruiter-logos/company/sample123.png",
  "location": "Reston, VA",
  "city": "Reston",
  "state": "Virginia",
  "county": "Fairfax",
  "postalCode": "20190",
  "streetAddress": "1100 Sample Way",
  "country": "United States",
  "locationType": "On-site",
  "remote": false,
  "salary": "$123K - $161K/yr",
  "salaryMin": 123000,
  "salaryMax": 161000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "employmentTypes": ["FULL_TIME"],
  "employmentType": "FULL_TIME",
  "industry": "Technology",
  "occupationalCategory": "15-1133.00: Software Developers, Systems Software",
  "benefits": ["MEDICAL", "LIFE_INSURANCE"],
  "snippet": "THE IMPACT YOU WILL MAKE The Full Stack Software Engineer role will offer you the flexibility to make each day your own...",
  "descriptionText": "Full job description text appears here when Fetch detail pages is on.",
  "emails": [],
  "phoneNumbers": [],
  "datePosted": "2026-04-24T00:00:00Z",
  "validThrough": "2026-06-23",
  "isActive": true,
  "directApply": true,
  "applyUrl": "https://www.ziprecruiter.com/c/Sample-Employer-Inc/Job/Full-Stack-Software-Engineer/-in-Reston,VA?jid=ad8a4fef327eab36",
  "sponsored": false,
  "searchQuery": "software engineer",
  "searchLocation": "Remote",
  "pageNumber": 1,
  "source": "ZipRecruiter",
  "scrapedAt": "2026-05-25T12:00:00.000Z"
}
```

### Plan Requirement

ZipRecruiter sits behind an anti-bot challenge, and plain requests, including from datacenter IPs, are answered with HTTP 403. A datacenter connection does not work and this actor does not claim it does. It needs a US residential connection: the default proxy setting already routes through Apify residential pinned to the United States, which requires a paid Apify plan that includes residential access. Free plans have no residential access; on a free plan paste your own residential proxy URLs in the proxy field. The actor opens a fresh connection and retries when a request is refused. If you supply your own proxy configuration, make sure it is also a residential US connection.

If no working proxy can be set up, the log says "No working proxy is available, this run cannot continue." and the run ends as failed with the message that matches the real cause: a refused residential group says your Apify plan does not include proxy access (the only message that mentions the plan); Apify Proxy switched off with no proxy URLs says no proxy is selected; a temporary error while setting up the proxy, or an unreachable backup connection, says to try again in a few minutes. A run that opened no results page and no job page at all ends as failed with "Could not load any results page"; a run whose pages all loaded but whose jobs were all unchanged (incremental mode) finishes normally and saves its state. If your proxy has no RESIDENTIAL group the log warns "your proxy config does NOT include RESIDENTIAL group", and a page that is blocked or empty ends that search with "blocked or empty, stopping this search".

### FAQ

#### Which countries are covered?

United States only (ziprecruiter.com). The Canadian, UK and Irish ZipRecruiter sites are a deliberate gap: they use different page layouts and URL shapes that this actor has not been built or tested against.

#### Why can a filtered run return fewer jobs than Max listings?

`workplace`, `maxSalary`, `externalApplyOnly`, `skipSponsored`, `remoteOnly`, `minSalary` and the employment filter are screens over the loaded results, not site search parameters. Jobs they remove are not returned and not billed, and the search is then not treated as a complete read, so `emitExpired` will not mark jobs as gone after such a run. The state key (incremental mode) includes `workplace`, `maxSalary` and `externalApplyOnly` only when you set them; `maxItemsPerSearch`, `descriptionMaxLength` and `omitEmptyFields` only shape the output.

#### How much does it cost?

Billing has three events: a one-time actor start, one charge per listing delivered to the dataset, and (only with **Fetch detail pages**) one detail-enrichment charge per delivered row whose detail page was read. Listings that are suppressed, skipped as duplicates or already collected via `resumeFromRunId`, and incremental `UNCHANGED` rows (unless you turn on `emitUnchanged`) are not billed. The **Pricing** tab on the actor page shows the current rates. Use **Max listings** and **Max pages per search** to cap the size, and therefore the cost, of any run.

#### Is it legal to scrape ZipRecruiter?

This actor collects only publicly available job listing data. You are responsible for how you use it: follow ZipRecruiter's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Job descriptions, company logos and photos may carry their own copyright or trademark protections even though the listing itself is public.

#### Can I get only new listings on a schedule?

Yes, with **`incrementalMode`**. Turn it on and run the same search on a schedule. The first run returns every matching job as `NEW`. Later runs return only `NEW`, `UPDATED` and `REAPPEARED` jobs; unchanged jobs are neither returned nor billed. Each returned row carries `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt`. State is kept by the actor per search (mode, queries, location, filters, detail setting, URLs); set `stateKey` to name a campaign yourself.

A job counts as `UPDATED` when any field other than the ignored ones changed. Always ignored: `scrapedAt`, `pageNumber`, `sponsored`, `inputUrl`, `postedAt`, `datePosted`, `listingKey`, `companyLogo` (they change without any change to the job); they are still returned in each row. Add your own names with `ignoreFieldsForChanges`. Turn on `emitUnchanged` to also receive `UNCHANGED` rows, and `emitExpired` to receive jobs that vanished, marked `EXPIRED`. `EXPIRED` is produced only after a run that read the search up to the last page the site reports: not when Max listings or Max pages stopped it, not for a pasted single job link or a link that starts after page 1, not when a filter removed jobs, not with Resume, and not when a page or detail page failed.

Billing: every row delivered to the dataset (including `UNCHANGED` and `EXPIRED` rows when you turn them on) is billed as a normal result. With `fetchDetails`, the job-detail event is billed only for delivered rows, never for suppressed ones. A run that is stopped by a time limit or a failed state save does not advance the saved baseline, so the next run reports those jobs again.

`resumeFromRunId` is a different feature: it continues one interrupted pull by skipping the job ids already saved in a specific earlier run or dataset. It cannot be combined with a search that already has saved incremental state.

#### Why does a search return jobs that don't match my employment-type filter?

ZipRecruiter's own employment-type filter is loose: it narrows the search but also backfills the results with related jobs, so a part-time search can still surface full-time roles. This actor enforces your choice on top of the site's own filtering, but keeps jobs whose type the site left unlabeled rather than dropping them, so thin markets still return results. See "Employment-type filtering" above for how to enforce an exact match downstream.

#### Why did my run fail instead of returning an empty dataset?

Two situations end the run outright: no proxy configuration could be resolved when the run starts, or `resumeFromRunId` points at a run or dataset your Apify account can't read (checked before any scraping starts, so a bad ID doesn't burn a search). Every other failure, such as one search's connection being repeatedly refused, only ends that one search early; the run itself still completes and keeps any listings already collected.

#### Why did a run complete but return zero listings?

Confirm your keywords and location match a real ZipRecruiter search in your own browser, or try URL mode with a link pasted straight from the site. Also confirm your proxy configuration routes through a residential US connection; ZipRecruiter's anti-bot check answers other traffic with HTTP 403, and the run log then says "blocked or empty, stopping this search".

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more jobs data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>💼 <a href="https://apify.com/abotapi/totaljobs-com-scraper"><b>Totaljobs Scraper</b></a><br>Scrape UK job listings from Totaljobs.com by keyword, location, filters, or URL. Extract...</td><td>💼 <a href="https://apify.com/abotapi/jobsite-co-uk-scraper"><b>Jobsite UK</b></a><br>Collect UK job listings from Jobsite.co.uk at scale. Search by keyword, location...</td></tr>
<tr><td>💼 <a href="https://apify.com/abotapi/careerbuilder-scraper"><b>CareerBuilder Scraper</b></a><br>Scrape CareerBuilder.com job listings by search or URL. Returns title, company profile...</td><td>🏨 <a href="https://apify.com/abotapi/caterer-com-scraper"><b>Caterer.com Scraper</b></a><br>Scrape UK hospitality jobs from Caterer.com, including chef, hotel, restaurant, bar, and...</td></tr>
<tr><td>💼 <a href="https://apify.com/abotapi/dice-com-scraper"><b>Dice.com Scraper</b></a><br>Scrape tech job listings from Dice.com by keyword, filters, or URL. Extract job titles...</td><td>💼 <a href="https://apify.com/abotapi/simplyhired-com-scraper"><b>SimplyHired</b></a><br>Pull active SimplyHired jobs across the US, UK, Canada, Australia, Ireland, and India...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/ziprecruiter-com-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Search mode = pick keywords + location + filters below. URL mode = paste any ziprecruiter.com search or job URL refined in your browser.

## `queries` (type: `array`):

Free-text search terms (e.g. "software engineer", "registered nurse", "data analyst"). One scrape per keyword. Leave empty to browse without a keyword filter.

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

City + state, ZIP, or "Remote". Examples: "New York, NY", "Chicago, IL", "94103", "Remote". Leave empty to search nationwide.

## `datePosted` (type: `string`):

Only return jobs posted within this window. "Any time" walks the unfiltered SERP.

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

Filter by employment scope. "Any" walks the unfiltered SERP.

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

Only return jobs flagged as remote.

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

Only return jobs whose advertised pay is at or above this annual figure. Leave at 0 for no salary floor.

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

Distance around the location to include. Leave at 0 to use ZipRecruiter's default radius.

## `skipSponsored` (type: `boolean`):

Drop sponsored / promoted job cards. Applied as a client-side filter.

## `urls` (type: `array`):

One or more ziprecruiter.com URLs. Supported: a search URL (…/jobs-search?search=software+engineer\&location=Remote), a legacy keyword path (…/Jobs/Software-Engineer, mapped to a search), or a single job URL (…/c/Raytheon/Job/Software-Engineer-II-(Onsite)/-in-Tucson,AZ?jid=36f7f16aec6bade9). Multi-URL supported; each search URL pages forward independently. Remote and minimum-salary facets in a pasted URL are honoured. Leaving this field untouched in URL mode scrapes one example per shape.

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

How many SERP pages to walk per keyword × location combination (or per pasted URL). Each page returns up to 20 listings. Leave empty to walk every result page (bounded by `maxListings` and ZipRecruiter's own result total).

## `maxListings` (type: `integer`):

Stop after this many listings across all searches. 0 = no cap (still bounded by maxPages).

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

When ON, also fetch each job's detail page for the full HTML description, structured street address, industry, valid-through date, and verified salary. SERP cards already carry id, title, company, location, salary, employment type, benefits, snippet, and posted date, so most feeds work great with this OFF (faster and cheaper). Default OFF.

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

Extra ceiling for each keyword or each pasted link, on top of the even share of Max listings. 0 = no extra ceiling.

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

Keep only jobs the site tags as remote, hybrid or on-site. Applied to the loaded results, so a narrow choice can return fewer jobs than Max listings; the run is then not treated as a complete read of the search. 'On-site' also keeps jobs that carry no workplace tag.

## `maxSalary` (type: `integer`):

Drop jobs whose lowest advertised annual pay is above this figure. Only jobs paid per year are screened; hourly, weekly and unpriced jobs are kept. 0 = off.

## `externalApplyOnly` (type: `boolean`):

Keep only jobs whose apply button leads to an external employer or partner page (externalApplyUrl is set). Applied to the loaded results.

## `descriptionMaxLength` (type: `integer`):

Cut descriptionText, description and descriptionMarkdown to this many characters. descriptionHtml is set to null when it is longer than the limit (a cut HTML string would be invalid). Does not change what counts as UPDATED. 0 = full text.

## `omitEmptyFields` (type: `boolean`):

Drop fields whose value is null, empty text or an empty list from each row, for a smaller dataset. Does not change what counts as UPDATED.

## `proxy` (type: `object`):

ZipRecruiter requires US residential routing (Datacenter exit IPs are rejected by the upstream edge). The default below works on Apify Starter and higher. Free plans without residential access can paste their own residential proxy URLs in this field.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Listings already collected there (matched by job id) are skipped, so this run only saves the new ones. Leave empty for a normal fresh run.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring of the same search. The first run returns every matching job as NEW. Later runs return only NEW, UPDATED and REAPPEARED jobs; unchanged jobs are neither returned nor billed. Every returned row gets changeType, changedFields, firstSeenAt and lastSeenAt. A job counts as UPDATED when any field other than these ignored ones changed: scrapedAt, pageNumber, sponsored, inputUrl, postedAt, datePosted, listingKey, companyLogo (the scrape time, the results page the job landed on, the promoted flag, the start link, the posted time, the listing tracking key and the logo link change without any real change to the job). Everything else counts, for example title, salary, location, description, benefits, apply link, so a changed salary or description returns the job as UPDATED with the field names in changedFields. Ignored fields are still returned in each row. Add your own with the ignore list at the end of this section. State is kept per search (mode, queries, location, filters, detail setting, URLs), never per item limit or page limit. Use State key to name a campaign. To continue one interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to derive a key from the search settings, so different searches never mix state.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return jobs that have not changed since the last run, marked UNCHANGED. These rows are delivered and billed like any other row, so leave it off unless you want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return jobs seen in a previous run that are no longer found, marked EXPIRED. Only produced after a run that read the whole search up to the last page the site reports: not when Max listings or Max pages stopped it, not for a pasted single job link or a link that starts after page 1, not when any filter (sponsored, salary, employment type) removed jobs, not with Resume, and not when a page or detail page failed. These rows are delivered and billed like any other row, and they count toward Max listings: any beyond it are reported on a later run.

## `ignoreFieldsForChanges` (type: `array`):

Output field names that should NOT make a job count as UPDATED, even when they changed. Always ignored: scrapedAt, pageNumber, sponsored, inputUrl, postedAt, datePosted, listingKey, companyLogo (listed here so you know; you do not need to add them). A name without a dot matches that field at any nesting depth; a dotted name matches from the top of the row. Ignored fields are still returned in the output.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "software engineer"
  ],
  "location": "Remote",
  "datePosted": "any",
  "employmentType": "any",
  "remoteOnly": false,
  "minSalary": 0,
  "radius": 0,
  "skipSponsored": false,
  "urls": [
    "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=Remote",
    "https://www.ziprecruiter.com/Jobs/Software-Engineer",
    "https://www.ziprecruiter.com/c/Raytheon/Job/Software-Engineer-II-(Onsite)/-in-Tucson,AZ?jid=36f7f16aec6bade9"
  ],
  "maxListings": 20,
  "fetchDetails": false,
  "maxItemsPerSearch": 0,
  "workplace": "any",
  "maxSalary": 0,
  "externalApplyOnly": false,
  "descriptionMaxLength": 0,
  "omitEmptyFields": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `contacts` (type: `string`):

No description

## `changes` (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 = {
    "mode": "search",
    "queries": [
        "software engineer"
    ],
    "location": "Remote",
    "urls": [
        "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=Remote",
        "https://www.ziprecruiter.com/Jobs/Software-Engineer",
        "https://www.ziprecruiter.com/c/Raytheon/Job/Software-Engineer-II-(Onsite)/-in-Tucson,AZ?jid=36f7f16aec6bade9"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/ziprecruiter-com-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",
    "queries": ["software engineer"],
    "location": "Remote",
    "urls": [
        "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=Remote",
        "https://www.ziprecruiter.com/Jobs/Software-Engineer",
        "https://www.ziprecruiter.com/c/Raytheon/Job/Software-Engineer-II-(Onsite)/-in-Tucson,AZ?jid=36f7f16aec6bade9",
    ],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/ziprecruiter-com-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",
  "queries": [
    "software engineer"
  ],
  "location": "Remote",
  "urls": [
    "https://www.ziprecruiter.com/jobs-search?search=software+engineer&location=Remote",
    "https://www.ziprecruiter.com/Jobs/Software-Engineer",
    "https://www.ziprecruiter.com/c/Raytheon/Job/Software-Engineer-II-(Onsite)/-in-Tucson,AZ?jid=36f7f16aec6bade9"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/ziprecruiter-com-scraper --silent --output-dataset

```

## MCP server setup

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