Scrape ZipRecruiter job postings without an API key

Last updated

ZipRecruiter has no public job search API: its ZipSearch feed was switched off on March 31, 2025, so the practical route is Datapika's job board scraper on Apify. Set sites to zip_recruiter, add a search term and a US or Canadian location, and every posting comes back as one JSON row with title, company, salary range, direct apply URL, and full description, no login or key required. Rows are billed at $0.005 each ($5 per 1,000), and the actor has logged 27,458 runs for 2,471 users. Export JSON, CSV, or Excel, or read the dataset over the API or MCP.

What fields does a ZipRecruiter job row contain?

Each ZipRecruiter posting lands in the dataset as a single item that shares its schema with every other board the actor supports, so a ZipRecruiter row and an Indeed row sit in the same CSV without column remapping. The core of the row is the job identity: a board-issued id, title, company, location, the job_url on ZipRecruiter, and job_url_direct pointing at the employer's own application page when the listing exposes one. Pay data is split into salary_min, salary_max, salary_currency, and salary_interval, with salary_source telling you whether the numbers came from the board's structured data or were parsed out of the description text; enforceAnnualSalary converts hourly and monthly figures to yearly so a $28 per hour warehouse role and a $58,000 salaried role compare on one axis. The description arrives as Markdown by default or HTML if you set descriptionFormat, and any email addresses found inside it are lifted into an emails array. Every row also records search_term, matched_search_term, and a scraped_at ISO timestamp.

  • Identity: id, title, company, location, site set to zip_recruiter, date_posted
  • Links: job_url on ZipRecruiter plus job_url_direct to the employer's careers page when available
  • Pay: salary_min, salary_max, salary_currency, salary_interval, and salary_source (direct_data or description)
  • Work terms: job_type (fulltime, parttime, contract, internship, temporary) and is_remote
  • Text: full description in Markdown or HTML, with extracted emails as a separate array
  • Provenance: search_term, matched_search_term, and scraped_at on every row

What is different about scraping ZipRecruiter compared with other boards?

ZipRecruiter behaves unlike the request-based boards in three ways you should plan around. First, coverage is limited to the United States and Canada, with no country switch equivalent to countryIndeed. Second, it is fetched through a real browser with an anti-bot warm-up phase, so ZipRecruiter rows typically land 1 to 3 minutes after the run starts, while Indeed and LinkedIn rows appear in 5 to 20 seconds. Results stream per board, so you can read the fast boards while ZipRecruiter is still loading, and the v1.0.49 release halved the warm-up retry budget to shorten unlucky runs. Third, ZipRecruiter rate-limits datacenter IPs, so keep proxyConfiguration on its residential default; runs also get 4 GB of memory for the browser session. On freshness, ZipRecruiter loads new listings once a day in a batch, so the youngest posting you see is often 12 to 48 hours old at scrape time. A 24-hour hoursOld window on ZipRecruiter alone will look thin, and 48 hours is the honest minimum. Unlike LinkedIn and Indeed, ZipRecruiter has no documented filter combinations that cancel each other out.

  • US and Canada only; there is no country parameter for ZipRecruiter
  • Browser-fetched with anti-bot warm-up: expect 1 to 3 minutes before rows appear
  • Datacenter IPs get rate-limited, so keep the residential proxy default
  • Daily batch loading means the youngest posting is often 12 to 48 hours old; use hoursOld of 48 or more
  • No documented restrictions on pairing hoursOld with isRemote, jobType, or easyApply, unlike LinkedIn and Indeed
  • Default memory is 4 GB to give the browser session headroom

How do you get hour-precise posting times and repost flags from ZipRecruiter?

The job board scraper records date_posted as a calendar date, such as 2026-07-07, with no time of day. ZipRecruiter's job pages carry more than that: an exact posting time and, when an employer bumps an old ad back to the top, evidence of the refresh. Datapika's Indeed and ZipRecruiter scan actor reads those pages and returns posted_at as a UTC timestamp such as 2026-07-07T06:55:00Z, plus a separate reposted_at whenever a listing was refreshed rather than newly created. It also pulls the employer's live ZipRecruiter posting total (593 for Paychex in the sample output) and a profile with industry, headcount, revenue band, headquarters, founding year, and a ZipRecruiter rating. Point it at a list of company names, optionally with websites for domain-level matching, and it reports has_active_postings and posted_last_48h_count per company; keyword mode takes up to 10 terms instead. Each company or job record costs $0.0005, and the hiring signals API use case page walks through the setup. Timestamp filtering replaces the board's own date facets, so a 48-hour window is true to the hour.

  • posted_at: exact UTC posting time read from the job page, not the search listing
  • reposted_at: present only when the employer bumped an existing ad, so fresh lists stay clean
  • Live ZipRecruiter total per employer (active_jobs_total_by_site), read from its employer page rather than a page-limited sample
  • Profile fields: zr_rating, zr_headquarters, zr_year_founded, zr_total_jobs alongside Indeed company_* keys
  • Company matching by website domain first, then normalized name with a 0.82 default threshold
  • recencyMode last48h or latest10, with hoursOld from 1 to 720 and up to 200 jobs per company per board

How do you filter ZipRecruiter results by recency, remote, and job type?

The input form is the same for every board, and all of it applies to ZipRecruiter. searchTerm takes one query, or searchTerms takes up to 5 that run in sequence and merge, with each row tagged by the term that surfaced it. location accepts a city, state, or country, and distance sets the search radius in miles with a default of 50. maxResults caps rows per board per term between 1 and 100, defaulting to 20, and offset skips ahead for pagination. isRemote restricts to remote roles, jobType picks one of fulltime, parttime, contract, internship, or temporary, hoursOld limits to postings inside a window, and easyApply keeps only listings that apply on the board itself. Because ZipRecruiter batches its intake, pair hoursOld of 48 with a scheduled daily run rather than chasing a 24-hour window. Advanced inputs cover descriptionFormat, enforceAnnualSalary, a custom userAgent, a caCert for enterprise proxies, and proxyConfiguration. If you want the same query answered by Indeed, LinkedIn, and Glassdoor in one deduplicated dataset, add them to sites; the full board list is at /scrape.

  • searchTerms: up to 5 queries per run, each row carries matched_search_term
  • location plus distance in miles (default 50) for metro-area targeting
  • maxResults 1 to 100 per board per term, default 20; offset for paging
  • isRemote, jobType, hoursOld, and easyApply all work on ZipRecruiter
  • enforceAnnualSalary normalizes hourly and monthly pay to yearly
  • userAgent, caCert, and proxyConfiguration for enterprise network setups

How do you run the ZipRecruiter scraper from code or an AI agent?

Three entry points share one actor and one dataset. In the Apify Console you fill the form, press Start, and download the Output tab as JSON, CSV, Excel, XML, or RSS. From code, POST the input to https://api.apify.com/v2/acts/openclawai~job-board-scraper/runs with your token, or call run-sync-get-dataset-items to get the rows back in one response; the Python client is pip install apify-client followed by client.actor("openclawai/job-board-scraper").call(run_input=...), and the Node client mirrors it. For agents, add https://mcp.apify.com/?tools=fetch-actor-details,openclawai/job-board-scraper to Claude, ChatGPT, or Cursor and the assistant can ask for remote nursing jobs in Toronto posted this week and reason over typed rows instead of HTML. Zapier, Make, and n8n connect through the Apify app, and a Console schedule with a webhook turns one query into a daily feed. The actor holds a 5.0 rating from 3 reviews, and 381 distinct users ran it in the 30 days to August 29, 2026.

  • Console: form in, dataset out as JSON, CSV, Excel, XML, or RSS
  • REST: POST to /v2/acts/openclawai~job-board-scraper/runs, or run-sync-get-dataset-items for a single call
  • Python and Node clients: apify-client with actor("openclawai/job-board-scraper").call(...)
  • MCP: https://mcp.apify.com/?tools=fetch-actor-details,openclawai/job-board-scraper
  • Zapier, Make, n8n, schedules, and webhooks through the Apify platform
  • 2,471 total users, 381 active in the last 30 days, 27,458 runs as of August 29, 2026
ZipRecruiter field coverage in the job board scraper output schema (derived from the actor README)
FieldPopulated on ZipRecruiter?Notes
id, title, company, locationYesBoard-issued id; site is set to zip_recruiter on every row
job_urlYesListing URL on ZipRecruiter
job_url_directWhen availableEmployer application page when the listing exposes one; absent when the job applies on ZipRecruiter itself
date_postedYesCalendar date only; hour-precise posted_at requires the Indeed and ZipRecruiter scan actor
salary_min, salary_max, salary_currency, salary_intervalWhen listedsalary_source shows direct_data or description; enforceAnnualSalary normalizes to yearly
job_type, is_remoteWhen listedjob_type is one of fulltime, parttime, contract, internship, temporary
descriptionYesMarkdown by default, HTML with descriptionFormat; emails extracted into a separate array
company_industry, company_num_employees, company_revenue, company_rating, company_reviews_count, company_logo, company_descriptionVariesCompany enrichment depends on what the board exposes for that employer; not guaranteed per row
job_level, company_countryNoLinkedIn-only and Indeed-only fields respectively
skills, experience_range, vacancy_count, work_from_home_typeNoNaukri-only fields
posted_at, reposted_atNot in this actorHour-precise UTC timestamp and repost flag come from the Indeed and ZipRecruiter scan actor
search_term, matched_search_term, scraped_atYesProvenance fields on every row regardless of board

How to do it

  1. 1.Open https://apify.com/openclawai/job-board-scraper, enter a searchTerm such as registered nurse, set location to a US or Canadian city or state, and set sites to ["zip_recruiter"].
  2. 2.Choose maxResults between 1 and 100, add hoursOld of 48 or more (ZipRecruiter ingests in daily batches), and toggle isRemote, jobType, or easyApply as needed; leave proxyConfiguration on residential.
  3. 3.Start the run. ZipRecruiter rows stream into the dataset after the browser warm-up, usually within 1 to 3 minutes; export JSON, CSV, or Excel from the Output tab or read the dataset through the API or MCP.
  4. 4.If you need exact posting times, repost flags, or a company's live ZipRecruiter job total, run https://apify.com/openclawai/indeed-ziprecruiter-scraper in companies mode with your target employer list.
Run a search

Questions, answered

Does ZipRecruiter have an official API?

Not for searching jobs. ZipRecruiter's ZipSearch API, which let publishers pull listings, was deprecated on March 31, 2025, and as of July 2026 the publisher program has not reopened (Job Boardly). What remains is the Partner Platform Jobs API, which lets registered ATS partners create, update, retrieve, and close their own listings; access requires partner registration and an API key sent with every request (ZipRecruiter Partner Platform Documentation). Reading the public search pages is the only self-serve route to other employers' postings.

Why do ZipRecruiter rows appear later than Indeed rows in the same run?

ZipRecruiter is fetched through a real browser session that has to pass an anti-bot warm-up before the first search, while Indeed and LinkedIn are read directly. In practice Indeed rows land 5 to 20 seconds after start and ZipRecruiter rows follow within 1 to 3 minutes. Results are pushed per board as each one finishes, so nothing waits on ZipRecruiter; you can read the early rows while its browser session is still working.

Can I scrape ZipRecruiter jobs outside the United States and Canada?

No. ZipRecruiter's job search covers the US and Canada only, and the actor has no country parameter for it. For other markets, run the same query on Indeed or Glassdoor with countryIndeed set to uk, australia, germany, india, or another supported code, on LinkedIn which is global, on Naukri for India, or on Bayt for the Middle East. Cross-board duplicates collapse into one row tagged with each source, so mixing boards costs nothing extra in cleanup.

How fresh are the ZipRecruiter postings the scraper returns?

ZipRecruiter ingests new listings in daily batches, so the newest job on the board is typically 12 to 48 hours old at scrape time. A 24-hour hoursOld window against ZipRecruiter alone will look sparse and is mostly driven by Indeed in a mixed run. Use 48 hours as the floor, schedule the actor daily, and if you need the true posting hour rather than the date, use the scan actor's posted_at field.

Does the scraper detect reposted ZipRecruiter jobs?

The job board scraper returns date_posted as shown on the listing and deduplicates identical postings across boards, but it does not flag bumps. The Indeed and ZipRecruiter scan actor does: it keeps the original posting date and emits a separate reposted_at timestamp when an employer refreshed an existing ad, so a list filtered to the last 48 hours contains only genuinely new roles. That distinction matters most for hiring-velocity scoring and recruiter outreach.

Do I need my own proxies to scrape ZipRecruiter?

No. The actor's proxyConfiguration defaults to Apify's residential proxy group, which is the right setting for ZipRecruiter because the board rate-limits datacenter IPs and the browser-based fetch has to clear an anti-bot warm-up first. Proxy traffic is billed as Apify platform usage rather than through the per-row price. Enterprise networks that route through their own gateway can supply a caCert and a custom userAgent instead.

Keep reading