# ZipRecruiter Jobs Lookup from $0.05/search (`accountable_eel/ziprecruiter-jobs-lookup`) Actor

Run keyword + location job searches on ZipRecruiter through a real browser and get every posting on the first results page in one row: title, company, location, salary and apply link. No login required. You pay per search that finds results, and a search that finds nothing is free.

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

## Pricing

from $38.00 / 1,000 search with 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## ZipRecruiter Jobs Lookup from $0.05/search

You write job searches the way you'd type them into ZipRecruiter — `python developer @ Austin
TX` — and this actor opens each one in a real browser, waits for ZipRecruiter's results to
render, and returns every posting on that first results page in one row: title, company,
location, salary (when disclosed), and a permanent link.

No login. No API key. ZipRecruiter's search page sits behind a Cloudflare challenge to a plain
request, and even once that's cleared the listings are rendered client-side — so this actor
drives a real headless browser through Apify's UNBLOCKER proxy rather than parsing raw HTML. See
"Why this one" below.

### Who it's for

Recruiters, staffing agencies, and outbound teams building a US candidate or lead list who need
fresh postings by role and city without clicking through ZipRecruiter's search pages by hand. It's
also the data layer behind an internal hiring-trends dashboard, or a Clay/n8n workflow that
watches a role for new postings on a schedule.

This actor is one of several wide-net keyword + location job-search actors in this catalogue
(alongside `seek-jobs-search-lookup` for Australia and `xing-jobs-lookup` for the DACH region)
rather than a per-company listing. If you already know which companies to target,
`company-domain-enrichment`'s hiring-activity source answers a different question — this one
answers "who's hiring for *this role*, in *this US city*" when you don't have the company list
yet.

### Why this one

- **Clears a real Cloudflare + client-rendering wall.** A plain request to ZipRecruiter's search
  page returns Cloudflare's "Just a moment..." managed challenge. Routing that same request
  through Apify's UNBLOCKER proxy clears the challenge — but the page that comes back is a bare
  application shell with no listings anywhere in the raw HTML, because ZipRecruiter renders its
  results client-side after the page loads. This actor drives an actual headless browser through
  UNBLOCKER and waits for that rendering to finish before reading the page, rather than assuming
  a plain HTTP fetch is enough.
- **Reads structured data first, brittle markup second.** Where the rendered page carries
  structured job data (a Next.js data blob, or schema.org `JobPosting` markup), this actor reads
  that directly instead of depending on CSS class names that change with every frontend redesign.
- **Never billed for a search that finds nothing.** A search with no postings is a free
  `found: false` row, same as every other actor in this catalogue.
- **Honest about scope.** This is a v1: it reads the first rendered results page per search (no
  "load more" pagination yet), and it bills once per **search**, not once per posting — see
  "Price" for why, and the FAQ for what that means for you.

### What you get

One row per search. Every row's `data` object carries:

| Field | Type / format | Description |
| --- | --- | --- |
| `query` | text | The search line you passed in, unchanged. |
| `found` | boolean | `true` if the search returned at least one posting. `false` rows are never charged. |
| `data.searchKeywords` | text | The keywords actually sent to ZipRecruiter. |
| `data.searchLocation` | text | The location actually sent to ZipRecruiter. |
| `data.jobCount` | number | How many postings this search returned after your filters. |
| `data.jobs` | array | Every posting found, each with `jobId`, `title`, `company`, `location`, `remote`, `salaryText`, `employmentType`, `postedAt`, `url`, and `description` (truncated to 500 characters). |
| `scrapedAt` | date (ISO) | When this actor fetched the row. |

A search that returns no postings comes back as a single `found: false` row and is never charged.

### Price

Pay-per-event, billed once per **search** that finds at least one posting — never per run, and
never for a search that finds nothing. See `.actor/pay_per_event.json` (once Console monetization
is configured) for the exact live price.

Because this target needs a real browser session through a proxy for every search, pricing here
is per search rather than per posting — unlike this catalogue's plain-HTTP job-search actors
(`seek-`, `xing-`, `bayt-jobs-lookup`), which bill per posting because they don't carry that
per-search browser-session cost. A typical search returning 15-20 postings works out to a similar
effective cost per posting either way.

1,000 searches through this actor, all finding results: a flat multiple of the per-search price —
see the actor's live pricing page for the current number. The same searches done by hand, opening
each one in a browser and copying fields: hours of manual work.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `items` field is
   already pre-filled with a working example. Results land in the run's dataset as soon as each
   search is processed.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API
   token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~ziprecruiter-jobs-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"items":["python developer @ Austin TX"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a
   **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

Paste one search per line:

```
python developer @ Austin TX
registered nurse
sales manager @ Chicago IL
```

A line with no `@ location` uses the location you set once in **defaultLocation** — leave that
blank too and the search covers everywhere ZipRecruiter has listings.

**Narrowing the results** — applied to the postings after they arrive:

| Input | What it does |
| --- | --- |
| `maxJobsPerQuery` | Most postings to keep from the first results page. Default 20. |
| `titleKeywords` | Keep only titles containing one of these — `["engineer","analyst"]`. |
| `excludeTitleKeywords` | Drop titles containing one of these. Applied after the include list. |
| `remoteOnly` | Keep only roles whose location or title reads as remote. |

### Input

```json
{
  "items": [
    "python developer @ Austin TX"
  ]
}
```

`items` is a list of job searches — write each as "keywords @ location", or just the keywords and
set `defaultLocation` once for every line that doesn't name one.

### Sample output

```json
{
  "query": "python developer @ Austin TX",
  "found": true,
  "data": {
    "searchKeywords": "python developer",
    "searchLocation": "Austin TX",
    "jobCount": 1,
    "jobs": [
      {
        "jobId": "<ziprecruiter job id>",
        "title": "<job title>",
        "company": "<company>",
        "location": "<location>",
        "remote": false,
        "salaryText": "<pay range, when disclosed>",
        "employmentType": "<employment type>",
        "postedAt": "<posted date>",
        "url": "<job link>",
        "description": "<description, truncated>"
      }
    ]
  },
  "scrapedAt": "2026-09-08T00:00:00.000Z"
}
```

A miss comes back as a row with `"found": false` and is never charged.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously — call it directly from a script, a workflow tool, or an AI agent,
no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~ziprecruiter-jobs-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"items":["python developer @ Austin TX"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL
`https://api.apify.com/v2/acts/accountable_eel~ziprecruiter-jobs-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`,
Body Content Type `JSON`, JSON Body `{"items":["python developer @ Austin TX"]}` (swap in an
expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL
`https://api.apify.com/v2/acts/accountable_eel~ziprecruiter-jobs-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`,
Body `{"items":["{{search}}"]}`, mapping the row's search into the `items` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "ZipRecruiter
Jobs Lookup" — the agent will find and run this actor.

### Tips

- Set a city in `defaultLocation`, or in the search line itself, whenever you can — a narrower
  search returns more relevant results on the one page this actor reads.
- Keep "Max parallel browser sessions" low. Each search opens a full browser session through a
  proxy — a handful of concurrent sessions is realistic; dozens is not.
- `salaryText` is populated only when the posting discloses one — many US job ads don't state pay.
- This v1 reads only the first results page per search. If you need deeper coverage of a single
  high-volume role, split it into several narrower searches (by city, or by a title keyword)
  rather than expecting one search to page through hundreds of results.

### vs. alternatives

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** (`ziprecruiter-jobs-lookup`) | A flat price per search that finds results, nothing for a genuinely empty search | Every posting on ZipRecruiter's first results page for a keyword + location search — title, company, location, salary and apply link, filterable by title keyword and remote | Reads only the first results page (no deep pagination yet); bills per search rather than per posting, since each search needs its own browser session. |
| **crawlerbros/ziprecruiter-scraper-pro** | Community actor, 157 users in the last 30 days, $2 per 1,000 results | Also scrapes ZipRecruiter job search results | An established incumbent with more users and per-result pricing; this actor never charges for a miss. |
| **Doing it yourself** | Your time + a Cloudflare-bypass proxy, a headless browser, and re-testing when ZipRecruiter's frontend changes | The same data | This actor's proxy routing, browser automation, and extraction logic is the maintenance burden it absorbs. |

Prices for third-party tools are as researched in September 2026 and are not tracked here — check
the vendor before relying on the comparison.

### FAQ

**Why does this actor bill per search instead of per posting, unlike some of this catalogue's
other job-board actors?**
ZipRecruiter's search results only exist after a real browser renders the page — unlike the
plain-HTTP actors in this catalogue, each search here needs its own browser session through a
proxy, a real per-search cost. Billing per search reflects that directly rather than hiding it in
a lower posting price.

**Why does this need a proxy and a browser when some job-board actors in this catalogue don't?**
ZipRecruiter's search page returns a Cloudflare challenge to a plain request, and even past that
challenge, the results render client-side rather than arriving in the raw HTML. Both together mean
a real headless browser through Apify's UNBLOCKER proxy is required here.

**Does this handle rate limits or blocking?**
Each search retries automatically on a transient failure. Keep "Max parallel browser sessions" low
if you see repeated failures.

**Do I need to configure proxies myself?**
No. This actor is preconfigured to route through Apify's UNBLOCKER proxy group — that's not
something you need to set up in the Input tab.

**Is this GDPR-relevant?**
The data returned is business/professional information already published publicly by employers on
ZipRecruiter (job title, company, location, pay range) — not personal data about private
individuals.

**Can I schedule this to re-check the same searches on a cadence?**
Yes — set up an Apify Task with a schedule and point it at this actor with a saved input. Each
scheduled run is independent; a role that appeared in yesterday's run will appear again today if
it's still listed.

**Can an AI agent call this directly?**
Yes. It's registered on the Apify MCP server — an agent in Claude, Cursor, or another MCP client
can find and run it by name ("ZipRecruiter Jobs Lookup"), or you can call the REST endpoint shown
above from any script or workflow tool.

### Related actors

- [SEEK Jobs Search Lookup](https://apify.com/accountable_eel/seek-jobs-search-lookup) — the same
  keyword + location search shape, against Australia's largest job board, over plain HTTP.
- [XING Jobs Lookup](https://apify.com/accountable_eel/xing-jobs-lookup) — the DACH-region
  equivalent, against XING's professional network.
- [Company Domain Enrichment](https://apify.com/accountable_eel/company-domain-enrichment) —
  broader firmographic and hiring-activity enrichment once you know which companies to target.

# Actor input Schema

## `items` (type: `array`):

One search per line. Write it as "keywords @ location", or just the keywords and set a location below. You're charged only for a search that finds at least one posting — never per run, and never for a miss.

## `defaultLocation` (type: `string`):

Used for any line that's just keywords, e.g. "Austin TX", "Remote". Leave empty to search nationwide.

## `maxJobsPerQuery` (type: `integer`):

This actor reads the first results page per search (no deeper pagination yet). This caps how many of those postings are kept.

## `titleKeywords` (type: `array`):

Optional. Keep only roles whose title contains at least one of these words. Leave empty to keep every role.

## `excludeTitleKeywords` (type: `array`):

Optional. Drop any role whose title contains one of these words. Applied after the include list above.

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

Keep only roles whose location or title reads as remote.

## `maxConcurrency` (type: `integer`):

How many searches to run at once. Keep this low — each one opens a full browser session through a proxy, so more at once multiplies runtime cost fast.

## Actor input object example

```json
{
  "items": [
    "python developer @ Austin TX"
  ],
  "defaultLocation": "",
  "maxJobsPerQuery": 20,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "remoteOnly": false,
  "maxConcurrency": 2
}
```

# 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 = {
    "items": [
        "python developer @ Austin TX"
    ],
    "titleKeywords": [],
    "excludeTitleKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/ziprecruiter-jobs-lookup").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 = {
    "items": ["python developer @ Austin TX"],
    "titleKeywords": [],
    "excludeTitleKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/ziprecruiter-jobs-lookup").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 '{
  "items": [
    "python developer @ Austin TX"
  ],
  "titleKeywords": [],
  "excludeTitleKeywords": []
}' |
apify call accountable_eel/ziprecruiter-jobs-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,accountable_eel/ziprecruiter-jobs-lookup"
        }
    }
}
```

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/ZRpd6cFNAF3VzJfh7/builds/JF0EWD3lYxZ81ijQZ/openapi.json
