ZipRecruiter API

Search ZipRecruiter job listings across the UK, Ireland and Germany

The ZipRecruiter API returns live US job listings as clean JSON.

no credit card1,000 free credits · instant API key · live in 10 seconds
Missing a ZipRecruiter endpoint, or need a source we don't have yet?Contact us real people · same-day reply.
Z
/ziprecruiter/v1

3 active endpoints. Every call is 1 credit.

  • POST/ziprecruiter/v1/search
  • POST/ziprecruiter/v1/job_detail
  • POST/ziprecruiter/v1/jobs_by_company

What ZipRecruiter endpoints does ReefAPI ship?

3 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.

3 endpoints

search

1 cr

Search live ZipRecruiter job listings by keyword and location, with pagination and an optiona…

required
query
optional
location, country, page, max_results, remote

job_detail

1 cr

Full ZipRecruiter job posting by job ID (or URL).

required
job_id
optional
url, country

jobs_by_company

1 cr

List a specific employer's open jobs on ZipRecruiter.

required
company
optional
location, country, page, max_results

Every parameter, every allowed value →

ZipRecruiter API

2 of 3 endpoints, ready to run

View docs ↗

Keyword search on one of ZipRecruiter's country editions. Twenty listings a page, with a parsed salary object where the advertiser published one.

1 credit1 required · 5 optional
POST/ziprecruiter/v1/search
idle
// Press "Try it" and this pane shows exactly what the
// live site returned this second — including an empty
// result, if that is the truth. No key, no account.

How the ZipRecruiter API works

ZipRecruiter is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.

01
Authenticate
x-api-key header

No OAuth app, no request signing, no per-site account. One key covers all 185 engines.

02
Call
POST /ziprecruiter/v1/…

Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.

03
Pay
1 credit per call

Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.

04
Read
{ ok, data, meta, error }

One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.

Page until relevance runs out, because the row count never will

This board never tells you how many results exist and never runs out of pages, so the stop condition has to be yours. The practical one is a relevance test on the rows themselves.

01search
POST/ziprecruiter/v1/search
{"query": "software engineer", "location": "London", "country": "gb", "page": 1}

Twenty rows, each with title, company, location, snippet, apply link and a parsed salary object where one was advertised.

02search
POST/ziprecruiter/v1/search
{..., "page": <n>}  — with your own relevance check on each page

Pages 10, 60, 200 and 500 each returned 20 unique rows with zero overlap. What changed was the content: by page 500 the query "software engineer" was returning an electrical technician and a contracts manager.

As much depth as you are willing to pay for — and a clear rule for when to stop, since the source will not give you one.

request
curl -X POST https://api.reefapi.com/ziprecruiter/v1/search \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"query":"software engineer","location":"London","country":"gb","max_results":20}'
response envelope
{
  "ok": true,
  "data": { … },
  "meta": {
    "api": "ziprecruiter",
    "endpoint": "search",
    "mode": "live",
    "latency_ms": …,
    "record_count": …
  },
  "error": null
}

Which editions answer, and why the two actions disagree about dates and links

This engine reads ZipRecruiter's European editions. The search action returns what the country site prints on the page, localized strings included, while job_detail reads the posting's own structured data. That is why the same job comes back with different date, link and location formats depending on which action you called. Measured on 'software engineer' in London and 'nurse' in the UK (50 rows), job_detail for 584464834, 583195628, 583691259 and 580259992, and an ie, de, fr and us sweep.

FieldMeasured shapeNote
countrygb serves ziprecruiter.co.uk, ie serves ziprecruiter.ie, de serves ziprecruiter.de. All three returned rows.fr is not in the enum but passed through and returned ziprecruiter.fr rows, so other two-letter codes are worth trying. us returned TARGET_BLOCKED, as the parameter description warns.
job_id9-digit numeric string: '584464834'It is the last segment of ziprecruiter.co.uk/jobs/<id>. job_detail takes it as job_id, or accepts the full URL.
posted_date (search)A localized, year-less string in the site's own language: '20 Aug' on gb, '20. Aug' on de, '28 juil.' on frThis is the page's own label. Do not parse it as a date; it carries no year and its format changes with the edition.
posted_date (job_detail)A real ISO date, '2026-08-26', with valid_through '2026-09-25T20:39:27.148Z'valid_through ran 30 days after posted_date on every job we checked. Both can be null on older postings.
salaryObject {min, max, currency, period, raw, source}source was 'text' on every priced row, so the figures are read out of the posting rather than supplied as structured pay. raw keeps the original string, e.g. '£14.25 - £15.15 per hour'.
salary period and coverage'year' and 'hour', lowercaseAbout half the rows had any salary at all: 10 of 20 in one London search, 8 of 30 in another. A posting quoting a single figure gives min set and max null, so '£39,500 per annum' becomes min 39500, max null.
job bodyjob_detail only, and plain text486 to 4,480 characters with no HTML tags in the jobs we read. Search rows give a snippet of about 150 characters instead. requirements is an extracted list that was empty on three of four jobs and held 11 items on the fourth.
apply_urlDifferent per actionSearch rows point at a long tracking redirect. job_detail returns ziprecruiter.co.uk/applicants/external?apply_to=<job_id>. Both reach the employer's application, but only the second is short enough and stable enough to store.
location'Greater London, ENG, GB' in search; a location_structured object in job_detailjob_detail gives {locality, region, postal_code, country} with spelled-out values ('Bristol', 'England', 'United Kingdom'), and postal_code came back as an empty string rather than null.
employment_type / remote'Full Time' plus a remote boolean, job_detail onlySearch rows carry neither. The remote parameter on search is applied by matching each listing's title and location, so it filters the page rather than the site.

sponsored was true on all 50 gb rows we pulled across two different queries, and false on every ie, de and fr row. On the UK edition it does not separate paid placements from organic ones, so do not build a filter on it.

Very deep, no total, and three fields that do not mean what they look like

Measured on 2026-08-28 across three country editions and four page depths on one query. Four of these lines go against us.

No ZipRecruiter account and no login

The country editions serve these listings without a session, so there is no account of yours anywhere in the path and nothing that can be suspended. Failed and blocked calls are not charged.

🔴 There is no total. Not a rough one, not a wrong one — none.

Against us. total_estimate came back null in every single call we made, on every country and every page. Nothing in the response tells you how large the result set is or how far you are through it, so a progress bar or a "we found N jobs" line has nothing to read from.

🔴 And paging does not stop either — but relevance does

Against us, and the most useful thing on this page. We requested page 10, page 60, page 200 and page 500 of the same query: each returned 20 rows, all unique, with zero id overlap between any pair. That looks like limitless depth, and it is not. By page 500 the rows for "software engineer" were an electrical technician, a nuclear risk manager and a contracts manager. The board keeps serving pages long after it has stopped serving matches, so your crawl needs its own relevance test as the exit condition.

🔴 The sponsored flag is true on every row, always

Against us. sponsored came back true on 20 of 20 rows on every page we pulled, including page 500, and the meta echoed sponsored: 20 each time. It does not separate paid placements from organic ones, so do not filter on it and do not surface it to a user as if it meant something.

🔴 The date is a bare day and month, with no year

Against us. posted_date is a string like "17 Aug" — no year, no ISO form, nothing machine-sortable. It was filled on 20 of 20 rows, so the information is there, but you will be inferring the year yourself and you will get it wrong across a new year boundary. This is the weakest freshness signal in the batch.

The United Kingdom, Ireland and Germany answer; the United States did not

gb, ie and de all returned 20 rows in one to two seconds. The us edition returned TARGET_BLOCKED on both attempts, twenty seconds each, and neither was charged. Treat the three European editions as the product.

Salary is parsed properly, when it is there at all

Where the advertiser published pay, it comes back as a real object — min, max, currency, period and the raw string it was parsed from. It was present on 4 to 9 rows out of 20 depending on the page, and the meta reports the count for you as salary_present, so you can see the fill rate of every response without walking the rows.

jobs_by_company is a name filter, and a strict one

The engine runs a search and keeps rows whose employer matches the name. One large retailer returned 4 rows and a large public employer returned 3 — the filter is exact enough that a trading name or an abbreviation will miss. Useful for confirming a specific employer is hiring; not a substitute for a company board.

What comes back, and what does not

The listing and the advertiser: title, company, location, snippet, source site, salary and the apply link. Not candidate profiles, not recruiter contacts, not applications.

What people build with ZipRecruiter

The jobs this data is most often used for.

3

endpoints

1

credit per call

01

Job boards call search to aggregate fresh ZipRecruiter listings with salary data.

02

Compensation tools use the salary range to benchmark pay for a role and location.

03

Sales-intelligence teams use jobs_by_company to detect hiring signals at target accounts.

What ZipRecruiter data costs

The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.

Full pricing →
$0.67–$1.50 / 1,000 credits
  • 1,000 free credits on signup, no card
  • One key, all 185 APIs, one credit pool
  • Failed and blocked calls are never charged
  • Credits roll over and never expire

Call it in two lines

Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.

curl
curl -X POST https://api.reefapi.com/ziprecruiter/v1/search \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"query":"software engineer","location":"London","country":"gb","max_results":20}'
python
import requests

r = requests.post(
    "https://api.reefapi.com/ziprecruiter/v1/search",
    headers={"x-api-key": REEF_KEY},
    json={
  "query": "software engineer",
  "location": "London",
  "country": "gb",
  "max_results": 20
},
)
print(r.json()["data"])
FAQ

Have a question? We got answers.

The questions people actually ask before wiring up ZipRecruiter.

Get a free key →
Why is posted_date '20 Aug' with no year?

Search results carry the label ZipRecruiter prints on its own listing page, and that label is localized and year-less. We saw '20 Aug' on the UK edition, '20. Aug' on the German one and '28 juil.' on the French one for comparable rows. If you need a real date, call job_detail on that job_id, which returns posted_date as '2026-08-26' and valid_through as a full ISO timestamp.

Is the salary structured, or scraped out of the text?

It is a structured object, but derived from text. Every priced row carries salary{min, max, currency, period, raw, source}, and source was 'text' on all of them, meaning the engine parsed the figures out of the posting body. raw keeps the original string so you can check the parse yourself. Period values are lowercase 'year' and 'hour'.

Half of my results have salary: null. Is that a failure?

No, that is the market. UK listings often do not quote pay, and we measured 10 of 20 priced in one London search and 8 of 30 in another. A null salary means the posting did not state one in a form the parser recognized, not that the call failed. Note also that a posting quoting a single figure comes back as min with max null, even when the wording is 'up to'.

Is the job description HTML or plain text?

Plain text, and only from job_detail. The four postings we pulled ran from 486 to 4,480 characters with no HTML tags at all, starting with a literal 'Job Description' heading. Search rows do not include the body; they give you a snippet field of roughly 150 characters. There is an extracted requirements array too, though it was empty on three of the four jobs we checked.

Which countries work, and can I use a code that is not in the list?

gb, ie and de are the supported editions and all three returned rows. us is documented as unavailable and returns a clear TARGET_BLOCKED rather than an empty list. Other two-letter codes are passed through as-is, which does sometimes work: fr is not in the enum but returned five ziprecruiter.fr rows, with French date labels.

Why does apply_url change between search and job_detail?

They are taken from different places on the site. Search rows expose the tracked outbound link, a long redirect with an opaque token, while job_detail returns ZipRecruiter's own ziprecruiter.co.uk/applicants/external?apply_to=<job_id> form. If you are storing links, store the job url and the job_detail apply_url; the tracked search links are long and not durable.

Can I trust the sponsored flag?

Not on the UK edition. It came back true on all 50 gb rows we pulled across two unrelated queries, and false on every ie, de and fr row, so on gb it describes the whole page rather than individual paid placements. Treat it as metadata about how the page was served, not as an ad label.

What does jobs_by_company actually match on?

The country sites have no employer profile page, so the action runs a search and keeps rows whose employer name matches what you passed. That match is loose: asking for 'NHS' returned jobs from 'NHS Professionals' and 'NHS Shared Business Services'. It is useful for grouping an employer family, but check the company field on each row if you need one exact legal entity.

What is the ZipRecruiter API?

ZipRecruiter API is a ReefAPI endpoint group for job listings with full details. It returns live JSON through POST requests under /ziprecruiter/v1.

Is the ZipRecruiter API free to try?

Yes. ReefAPI starts with 1,000 free credits, no card required. ZipRecruiter calls use the same shared credit balance as every other ReefAPI engine.

Do I need a ZipRecruiter login or account?

No login to ZipRecruiter is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.

How fresh is the ZipRecruiter data?

The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.

How many credits does the ZipRecruiter API use?

ZipRecruiter actions currently cost 1 credit per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.

Can I call ZipRecruiter from an AI assistant or MCP client?

Yes. Connect ReefAPI once through MCP and your assistant can call ziprecruiter actions with the same key, credit pool and JSON envelope used by normal REST requests.

15 Jobs & Hiring APIs on the same key

One key, one credit pool, one response envelope. If you are pulling ZipRecruiter, you are one call away from the rest of the category — no second contract, no second integration.

Need something this API does not do?

Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.

0/4000

No account needed · we reply from [email protected]

Try it on your own data before you pay anything

The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 184 APIs, and the same envelope everywhere.

Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-08-28.