# Career Site Jobs API: Job Postings from 19,000 Company Sites (`hiver/career-site-job-search`) Actor

Career site jobs API: search job listings on 19,000 company career sites (Workday, Greenhouse, Lever, Ashby, Oracle, iCIMS +8) by keyword, country, date and salary floor. Bench 10-09: 19 of 19 checks vs 11 of 19 for the most-used career site jobs API.

- **URL**: https://apify.com/hiver/career-site-job-search.md
- **Developed by:** [hiver](https://apify.com/hiver) (community)
- **Categories:** Jobs
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 1,000 jobs

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

## Career Site Jobs API: Job Postings from 19,000 Company Sites

**A career site jobs API (a job postings and job listings API) that reads employers' own career sites: search open jobs by keyword, location and posting date across 19,029 company job boards on 14 job systems (Workday, Greenhouse, Lever, Ashby, Oracle, iCIMS, SmartRecruiters, Personio and more; about 0.8 to 1.0 million open jobs). Every match is re-read from the employer's own board, so closed jobs are dropped, and a pay range stated in the posting text lands in `salaryMin` / `salaryMax`.**

![One keyword search over company career sites: real output rows from a board test run on 2026-10-12](https://raw.githubusercontent.com/hiver-data/career-site-job-alerts/main/media/career-site-jobs/readme.gif)

[Watch the 1-minute walkthrough (MP4)](https://github.com/hiver-data/career-site-job-alerts/blob/main/media/career-site-jobs/walkthrough.mp4)

- **Coverage:** 19,029 boards (about 15,000 companies) on Greenhouse (5,150), Ashby (3,270), Workday (1,609 sites of 1,075 companies), BambooHR (1,490), Personio (1,361), Breezy HR (1,224), Lever (906), Teamtailor (860), Rippling (831), iCIMS (779), SmartRecruiters (732), Oracle Recruiting Cloud (553), Recruitee (192) and Pinpoint (72). Workable (1,729 more boards) is opt-in. Details and gaps below.
- **Freshness:** your search reads a shared index of the boards' open jobs. A separate maintenance run re-reads the index stalest parts first, and every run reports the age of the oldest part it used (public index manifest, 2026-10-15 11:33 UTC: 236 of its 311 parts were under 72 hours old and 52 under 30 hours; the oldest was 153 hours old). Parts of the index more than 30 hours old, or that the last refresh could not fully read, are searched live as well, for up to 5 seconds: parts the refresh read only partly first, then the oldest (set `liveTopUpSeconds` to change it; 0 switches it off); the search is always answered from the index first, and boards the live read does not reach keep their indexed answer, whose age the run reports (up to 4 minutes of live reading only when no part of the index is usable). Then **every match is re-read from the employer's own job board**, so closed jobs are dropped and descriptions, dates and apply links are current. Each run reports the index age, and how many boards it read live, in its status line and in `RUN_SUMMARY`.
- **Speed and price:** on the latest board bench (2026-10-09 20:52 UTC, build 0.6.7) a search took 18.4 to 22.6 s on Apify (20.1 s on average). The Actor charges a flat $0.003 per search plus $0.004 per job.
- **Salary floor:** set `minSalary` (annual, e.g. `150000`) to keep only jobs whose posted pay range reaches it. Jobs below it, paid in another currency, or with no pay stated (unless you turn on `includeJobsWithoutSalary`) are left out and cost nothing. Board test run 2026-10-10 12:19 UTC (build 0.7.4; `Software Engineer`, posted in the last 30 days, `minSalary` 150000, `maxJobs` 20): 20 jobs in 8.6 s, charged as 1 search plus 20 jobs; the 11 matches left out on the way (1 paying below the floor, 9 with no pay stated, 1 paid in another currency) were not charged. The most-used career-site search Actor (1,565 monthly users) can keep jobs that state a salary but has no minimum-pay setting (its input schema, checked 2026-10-10). A job passes when the top of its range reaches the floor, so an $80,000 to $210,000 range passes a $150,000 floor.
- **Country filter from the employer's own record:** `locations: ["United States"]` (or `US`, `USA`, `United Kingdom`, `UK`, `Germany`, `DE`) matches the job's country as the job system records it: its country field or the job's office list where it has one (Lever, Greenhouse, Workday, SmartRecruiters, Ashby and others), otherwise the country named in its place text. Every row carries `country` (ISO-2) and `countries`. This matters because many postings never write the country. Of Palantir's 235 Lever postings with country `US`, none says "United States" or "USA" in its location (they say "New York, NY", "Washington, D.C."). Of the 476 Anthropic jobs Greenhouse lists under a US office, 467 don't either (public APIs, 2026-10-11). **Our Lab's replay against 11 public boards' own APIs (8 Greenhouse, 3 Lever), 2026-10-11:** the boards themselves place 2,644 jobs in the US and 396 in the UK. A `United States` search over the index keeps 2,639 of the US jobs (99.8%) and a `United Kingdom` search keeps 385 of the UK jobs (97.2%). 99.8% and 100% of what each keeps really is in that country. The text matching used before 2026-10-11 kept 15% and 43%. Codes match only as whole words, so `US` never matches "Austin" or "business". `CA` alone means Canada.

### Measured against the two most-used career-site search Actors

Our board's Lab bench gave this Actor and two career-site job search Actors on the Apify Store, the most-used one (1,565 monthly users) and a newer one (113 monthly users), the same four searches. It then checked each answer against the employer's own job board: job found, company, location, posted date, department, and the lowest salary stated in the posting. Bench run 2026-10-09 20:52 UTC, build 0.6.7:

| Search | This Actor | Most-used (1,565 monthly users) | Newer (113 monthly users) |
|---|---|---|---|
| Greenhouse job at a large employer, posted 2026-10-07 | 5 of 5 checks | 4 of 5 | 4 of 5 |
| Ashby job at a small startup, pay range from the posting text | 5 of 5 | 4 of 5 | 5 of 5 |
| Workday job at a large employer, pay range from the posting text | **4 of 4** | 0 of 4 (job not returned) | 4 of 4 |
| Personio job at a small company, posted 2026-10-07 | **5 of 5** | 3 of 5 | 3 of 5 |
| **All checks matched** | **19 of 19 (100%)** | 11 of 19 (58%) | 16 of 19 (84%) |
| Fields filled | 91% | 53% | **92%** |
| Searches that returned the job | 4 of 4 | 3 of 4 | 4 of 4 |
| Run time per search (average) | 20.1 s | 1.9 s | 2.1 s |
| Price in these runs, per 1,000 jobs | $7.05 | $12.03 | **$3.29** |

**Known gap 2026-10-10, fixed 2026-10-11:** on 2026-10-10 10:49 UTC (board test run, same code as the bench) the Workday search in the table returned 0 jobs. The index part that holds that employer's board (one of 3 parts with 60 large Workday employers, among them NVIDIA, Target and Salesforce) had held 0 jobs since 2026-10-08, and the 5-second live read went to the oldest parts first, so it never reached that board. Since then the live read goes first to parts the last refresh could read only partly, and the index is refreshed on a rotation, stalest parts first (2026-10-11 09:08 UTC: none of the 311 parts held 0 jobs). Our re-run of the same search on 2026-10-11 09:08 UTC, with the code of the current Store build, returned the job again (1 job in 25.1 s; that board was read live, as its index part still holds only 13 of its 20 boards). A board the index lacks can still be missed when the 5-second live read does not reach it; the status line says how old the index is and how many boards were read live, and `extraBoards` (e.g. `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`) always reads a board live.

Both alternatives answer faster (they serve from their own databases) and the newer one filled slightly more fields and cost less in these runs. Our price per 1,000 jobs is high here because each search returned one job, so the $0.003 search fee counts in full; at 1,000 jobs a run costs $4.003. Four searches are a small sample; later builds have not been benched again yet.

### Use it as a job postings API

Get your Apify API token (free account; Apify Console > Settings > API & Integrations), then:

**Python** (`pip install "apify-client>=3.2"`)

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("hiver/career-site-job-search").call(run_input={
    "keywords": ["data engineer"],
    "locations": ["United States"],
    "postedWithinDays": 7,
    "maxJobs": 50,
    "includeDescription": False,
})
for job in client.dataset(run.default_dataset_id).iterate_items():
    print(job["postedAt"], job["company"], job["title"], job["country"], job["salaryMin"], job["url"])
```

**HTTP** (one call, waits for the run and returns the jobs as JSON)

```bash
curl -X POST "https://api.apify.com/v2/acts/hiver~career-site-job-search/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["data engineer"], "postedWithinDays": 7, "maxJobs": 20, "includeDescription": false}'
```

Only jobs whose pay range reaches $150,000 a year: add `"minSalary": 150000` to either input (`"salaryCurrency": "EUR"` for euros; `"includeJobsWithoutSalary": true` keeps jobs that state no pay, marked `salaryKnown: false`). Add `&format=csv` to that URL for CSV. The same input works from JavaScript (`apify-client`), Make, Zapier, n8n and Apify schedules. For a daily alert, see **Alerts** below or the free GitHub Actions template [hiver-data/career-site-job-alerts](https://github.com/hiver-data/career-site-job-alerts) (daily search, de-duplicated `jobs.csv`, optional Slack or Discord post).

Unofficial, not affiliated with any job-system vendor or employer. It reads only the public job-board data that companies publish for anyone to see. No login, no API key, no applicant data.

### What you get

- One row per job, the same fields from every source: `source`, `company`, `companySlug`, `boardUrl`, `jobId`, `title`, `url`, `applyUrl`, `department`, `team`, `employmentType`, `experienceLevel`, `workplaceType`, `isRemote`, `location`, `locations`, `city`, `region`, `country` (ISO-2, `null` when no place names one), `countries` (all of a multi-office job's), `postedAt`, `updatedAt`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`, `salaryKnown`, `descriptionText`, `descriptionHtml`.
- **Salary floor:** set `minSalary` (annual) to keep only jobs whose posted pay range reaches it (top of the range, or the single figure). Hourly, weekly and monthly pay is converted to a year for the comparison only (hour x 2080, month x 12); the output keeps the posted numbers. `salaryCurrency` (default USD) limits the comparison to that currency, with no exchange rates. Jobs with no pay stated are dropped unless `includeJobsWithoutSalary` is on (then `salaryKnown` is false). Dropped jobs are never delivered or charged; the run status says how many were dropped. Because most postings state no pay, expect a small share of matches to pass.
- Fields a source does not publish are `null` (for example most boards publish no structured salary; where the posting text states a pay range it is read into the salary fields).
- A `RUN_SUMMARY` record in the run's key-value store: boards scanned per source, boards that failed, rate-limit hits, run time.

### The board directory

The Actor ships with a directory of company job boards. It was **built on 2026-10-08** from the open Common Crawl web index (crawls CC-MAIN-2026-25, -30, -34 and -39) plus the MIT-licensed company lists of the open-source project Feashliaa/job-board-aggregator. Every candidate was then checked against its public API or public sitemap, and only boards that answered with at least one open job on that day were kept.

| Source | Boards in the directory | Open jobs on those boards when built | Searched by default |
|---|---|---|---|
| Greenhouse | 5,150 | 184,281 | yes |
| Ashby | 3,270 | 67,906 | yes |
| Workday (career sites of 1,075 companies) | 1,609 | 396,272 | yes |
| Breezy HR | 1,224 | 26,215 | yes |
| BambooHR | 1,490 | 11,768 | yes |
| Personio (EU) | 1,361 | 13,987 | yes |
| Teamtailor (EU) | 860 | 14,516 | yes |
| Rippling | 831 | 13,104 | yes |
| Lever | 906 | 36,652 | yes |
| iCIMS | 779 | 99,879 | yes (not with a posting-date filter) |
| SmartRecruiters | 732 | 232,613 | yes |
| Oracle Recruiting Cloud (445 companies' hosts) | 553 | 255,024 | yes |
| Recruitee | 192 | 3,564 | yes |
| Pinpoint | 72 | 3,252 | yes |
| Workable | 1,729 | 88,664 | **no**, add `workable` to `sources` (see Limits) |

That is **19,029 boards searched by default and 20,758 including Workable.** Oracle: a company can run several career sites, so 553 sites are listed on 445 distinct Oracle hosts (sites of one host with the same job count are counted once). Workday: 1,194 company hosts were found in Common Crawl, 1,075 answered with an open job, and a company can run several career sites.

What this means for you:

- It is **not every company**. It is the boards that show up in open web data and answered when checked. SAP SuccessFactors and Taleo are **not covered** (SuccessFactors career sites have no public search API and no directory could be built from open data; only 11 of them turned up). Workday, Oracle or iCIMS companies that do not appear in Common Crawl are missing too. Use `extraBoards` to add a board by its address (Workday, Oracle, iCIMS, Personio, Teamtailor, BambooHR, Breezy, Pinpoint, Rippling and the other addresses are recognised).
- Boards that have closed since 2026-10-08 are skipped silently; boards opened since then are missing until the directory is rebuilt.
- Job counts change daily: the "open jobs when built" column is a snapshot, not a promise.
- iCIMS and BambooHR list pages carry no description; the Actor reads each matching posting page for it. **iCIMS publishes no real posting date** (only a rolling placeholder), so `postedAt` is empty for iCIMS jobs and they are left out when you set `postedWithinDays`; the sitemap's last-modified time is in `updatedAt`.
- Oracle and Workday search by keyword on their servers; the other sources return their whole board and the Actor filters it.

### How to use

1. Enter keywords, e.g. `data engineer`, `product designer`. A job matches when its title contains a keyword at the start of a word (so `ai` finds "AI Engineer" but not "Email Manager"). Phrases work.
2. Optionally add a location (`Berlin`, `United Kingdom`), `remoteOnly`, `postedWithinDays`, words to exclude from the title, and which job systems to search.
3. Set `maxJobs` (you are charged per job delivered, so this is also your spending cap).
4. Run. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Alerts:** turn on `onlyNew`, choose a `monitorId` such as `backend-berlin`, and schedule the Actor daily. The first run delivers every current match and remembers it; later runs deliver and charge only jobs not delivered before under that monitor ID. Prefer GitHub? The free template [hiver-data/career-site-job-alerts](https://github.com/hiver-data/career-site-job-alerts) runs the search daily from GitHub Actions and keeps a de-duplicated `jobs.csv`.

### Input

| Field | What it does |
|---|---|
| `keywords` | Jobs whose title contains any of these. Add `searchDescriptions` to match the full text too. |
| `titleTerms`, `excludeTerms` | Stricter title requirement, and title words to drop. |
| `locations` | A country name or code (`United States`, `US`, `UK`, `Germany`) matches the job's `country` (ISO-2, from the board's own country field or office list; whole words only). `CA` alone means Canada. Any other term (`Berlin`, `Austin`, `Remote`) matches whole words in the location text. |
| `remoteOnly` | Jobs the company marks as remote, or whose location says remote. |
| `postedWithinDays` | Published in the last N days (jobs with no publish date are dropped when this is set). |
| `minSalary`, `salaryCurrency`, `includeJobsWithoutSalary` | Salary floor: keep jobs whose posted pay range reaches this annual amount in this currency (default USD); hourly and monthly pay is annualised for the comparison. Jobs with no pay stated are dropped unless the third option is on. Dropped jobs are not charged. |
| `sources` | Which job systems to search (Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Workday, Oracle, iCIMS, Personio, Teamtailor, BambooHR, Breezy, Pinpoint, Rippling, Workable). Empty means all except Workable. |
| `maxJobs`, `maxJobsPerBoard` | Total cap, and cap per company. |
| `maxBoardsPerSource`, `maxMinutes` | Scan only the first N boards of each system, or stop after N minutes, and deliver what was found. |
| `extraBoards` | Board addresses not in the directory, e.g. `https://jobs.lever.co/spotify`. Scanned first. |
| `onlyNew`, `monitorId` | New-since-last-run alerts. |
| `includeDescription` | Full descriptions (default on; same price either way). |

Example input (also the default prefill):

```json
{
  "keywords": ["data engineer", "machine learning engineer"],
  "postedWithinDays": 7,
  "maxJobs": 50
}
```

### Speed and what a run costs

**How a search works.** A separate maintenance Actor (`career-jobs-index-refresh`, run on the publisher account) re-reads the 19,029 boards in turn, stalest parts of the index first, and writes the open jobs (title, company, location, date, links) to a public index; each search run reports the age of the oldest part it used. A search then (1) scans the titles of all ~1 million indexed jobs for your keywords, location and date, (2) takes the newest matches up to `maxJobs`, and (3) re-reads each match from the employer's own job board, which drops jobs closed since the refresh and adds the description, salary and current date. If a part of the index is older than 30 hours or missing, those boards are also read live (capped at `liveTopUpSeconds`, 5 s by default: parts the last refresh read only partly first, then the oldest); if the whole index is unreachable the Actor falls back to a live sweep capped at 4 minutes (set `maxMinutes` to change it) and says so in the status line.

**Local measurements (2026-10-08, 4-core machine, index read from disk, so no download time):**

- 50 jobs for `data engineer` / `machine learning engineer`, last 7 days, with descriptions: 4.7 s.
- 1,000 jobs for `engineer`, no descriptions: 27 s. 525 jobs for the two data keywords with `maxJobs` 1000: 23 s.
- A search with no match: 1.4 s. Peak memory 300 to 450 MB.
- Full index refresh of all 19,029 boards: 771 s, 1.04 million jobs from 18,873 boards that answered (the rest were closed or empty that day).
- For comparison, the old live sweep of the same boards took 358 s locally and an estimated 15 to 16 minutes on Apify.

**Cloud measurements on Apify (1 GB, board bench 2026-10-09 20:52 UTC, build 0.6.7):** four searches took 18.4 to 22.6 s each (20.1 s on average) and used $0.00006 to $0.0017 of compute. A typical status line: "1 jobs in 17.0s: 19029 boards from the daily index (oldest part 31.8 h old, 0 closed jobs dropped), 401/489 boards swept live - live top-up ended at its 5.0s limit". An older build (0.5.1, bench 2026-10-08 09:37 UTC) read stale boards live for up to 60 s and took about 69 s per search; the 5-second live top-up replaced that.

- **Index size:** about 25 MB of title files plus 100 MB of job records; a search downloads the title files (about 25 MB, compressed) and only the record chunks that hold your matches.
- **Live mode** (`useIndex` off, `searchDescriptions` on, or `maxBoardsPerSource` set) reads the boards directly as before; it is slower (minutes) and meant for description search and for boards you add with `extraBoards`.

### Coverage limits

- **Workable** answers HTTP 429 after a few dozen requests from one address, so a polite sweep of its 1,729 boards is not possible at this speed. It is off by default; add `workable` to `sources` to try it (two requests at a time, it gives up when the host keeps refusing and says so).
- **SAP SuccessFactors and Taleo** are not covered (see the directory section).
- Each source returns only what the company publishes: most boards have no salary, and some (Rippling, BambooHR, iCIMS list pages) carry less structured data than others.

### Pricing

Pay per event, no subscription. You pay $0.003 per search and for jobs delivered to the dataset; a company that is not found or has no open jobs costs nothing. `maxJobs` caps what a run can charge, and you can also set a maximum cost per run in Apify.

| Apify plan | Per job | Per 1,000 jobs |
|---|---|---|
| Free / Bronze | $0.004 | $4.00 |
| Silver | $0.0036 | $3.60 |
| Gold | $0.0032 | $3.20 |

Plus a flat **$0.003 per search** (Silver $0.0027, Gold $0.0024), charged once per run whatever the number of matches. Apify's own run-start event is $0.00005.

**Worked example:** A run that returns 1,000 jobs costs $4.003 on Free/Bronze ($3.20 on Gold); a search with no match costs $0.003.

### Limits (honest list)

- Workday: the search is done by Workday's own job search, which ranks by relevance and may return jobs that match only part of your phrase; results are then checked against the title here, like for every other system. Workday list rows show "Posted N days ago" only, so the date is day-precise, and "Posted 30+ days ago" jobs have no date until the posting page is read (done automatically when a location, remote filter or a window over 29 days is set). Workday returns at most 2,000 results per search. Workday `workplaceType`/`isRemote` come from the posting page and are only filled when a location, remote filter or descriptions are requested.  Salary comes from the pay range stated in the posting text.
- Country filter: a job whose job system gives only a city (`London`, `Munich`, `Vancouver, BC`) and no country has `country: null`, and a country term does not match it. Add the city as a second term (`["United Kingdom", "London"]`). In an index search, Greenhouse jobs whose location text names no country are checked against the posting's office list when the search re-reads them, up to the 400 newest per search. A very broad country search (no keyword) can miss older jobs past that cap. The replay above covers Greenhouse and Lever boards only.
- Titles are matched as text, not by meaning: "software developer" will not find "programmer".
- SmartRecruiters boards are filtered on SmartRecruiters' side by keyword, which can match on more than the title; results are then checked against the title again here. SmartRecruiters descriptions need one extra request per matching job.
- `searchDescriptions` is slower (Greenhouse boards are downloaded in full) and is not available for SmartRecruiters.
- Posting dates are what the employer's job system reports. Some systems report "last updated" rather than "first published".
- Workable limits request rates strictly. In the 2026-10-07 full-sweep test Workable answered 429 from the test machine and was skipped entirely (the Actor stops asking after repeated refusals, it does not try to get around the limit). A run can come back with Workable partly or entirely skipped; the status message and `RUN_SUMMARY` say when. Workable boards in the directory: 1,729.
- Salary is only present where the company publishes it in its job post: structured pay when the board has it, otherwise a range stated in the posting text (several ranges, e.g. per level or location: the lowest minimum and highest maximum of those in the first range's currency and period). Text without a clear range gives `null`, never a guess. This works with `includeDescription` off too.

### FAQ

**Does it need a login or API key?** No. It reads public data only.

**Can I call it as a job postings API?** Yes. Each call is one Actor run that returns the jobs as JSON, CSV or Excel: use Apify's HTTP endpoint `run-sync-get-dataset-items` or its Python and JavaScript clients (examples under "Use it as a job postings API"). You need your own Apify token; you pay per search and per job delivered.

**Coming from JobSpy (python-jobspy)?** JobSpy searches job boards such as LinkedIn and Indeed; this Actor searches employers' own career sites. The free script [hiver-data/career-sites-for-jobspy](https://github.com/hiver-data/career-sites-for-jobspy) calls it from Python and returns a pandas DataFrame with the same 34 columns, in the same order, as `jobspy.scrape_jobs()` (checked against python-jobspy 1.3.0), so you can `pd.concat` both results.

**Can I filter jobs by salary?** Yes. `minSalary` keeps only jobs whose posted pay range reaches that annual amount (top of the range, or the single figure), in the currency you set; jobs without stated pay are dropped or kept and flagged (`includeJobsWithoutSalary`), and dropped jobs are not charged. Pay comes from the employer's own posting: structured pay where the job system has it, otherwise a range stated in the posting text. Most postings state no pay, so expect a smaller result set.

**Can I get only jobs in one country?** Yes. Put the country in `locations` (`United States`, `US`, `United Kingdom`, `UK`, `Canada`, `Germany`). It matches the country the employer's job system records for the job (country field or office list, else the country its place text names), and each row's `country` field tells you which. In the 2026-10-11 replay of 11 public Greenhouse and Lever boards, that kept 99.8% of the US jobs and 97.2% of the UK jobs. See the first section for details and the limits list for the gaps.

**What are the rate limits?** The Actor reads at a polite pace, retries when a source slows it down and stops asking a source that keeps refusing; it never tries to get around a limit. The run log says what was skipped.

**How many jobs can I get per run?** As many as the sources list, up to your `maxJobs` cap. Jobs closed by the employer disappear from the source and from your next run.

**Is it affiliated with the job systems or companies?** No. Unofficial, not affiliated with or endorsed by any job-board vendor or employer; it reads only public job data and no applicant or employee data.

**Something is broken or a field is missing?** Open an issue on the Actor page.

# Actor input Schema

## `keywords` (type: `array`):

Jobs whose title (or full text, if you turn on description search) contains at least one of these words or phrases, matched at the start of a word, not case sensitive. Example: "data engineer", "python".

## `titleTerms` (type: `array`):

Optional stricter filter: the job title itself must contain one of these. Use it together with keywords when keywords search descriptions.

## `excludeTerms` (type: `array`):

Drop jobs whose title contains any of these words, e.g. "intern", "manager".

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

Keep jobs in these places. A country name or code ("United States", "US", "USA", "United Kingdom", "UK", "Germany", "DE") matches the job's country, read from the job board's own country field or office list (whole words only: "US" never matches inside another word). "CA" alone means Canada (California is "California" or "San Francisco, CA"). Any other term ("Berlin", "Austin", "Remote") matches whole words in the job's location text. Leave empty for anywhere.

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

Keep only jobs the company marks as remote (or whose location says remote).

## `postedWithinDays` (type: `integer`):

Keep only jobs published in the last N days. Jobs without a published date are dropped when this is set. Leave empty for all open jobs.

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

Keep only jobs whose posted pay range reaches this annual amount (top of the range, or the single figure). Hourly, weekly and monthly pay is converted to a year (hour x 2080, month x 12) for the comparison only; the output keeps the posted numbers. Jobs with no pay stated are dropped unless you turn on the option below. Dropped jobs are never delivered or charged.

## `salaryCurrency` (type: `string`):

Currency of the minimum pay, e.g. USD, EUR, GBP. Only jobs paying in this currency are compared; no exchange rates are applied.

## `includeJobsWithoutSalary` (type: `boolean`):

With a minimum pay set, also keep jobs that state no pay (marked salaryKnown: false).

## `jobTypes` (type: `array`):

Keep only jobs of these types (jobType field), applied before maxJobs. Read from the board's employment type, the title ('Intern', 'Co-op') or an explicit statement in the posting text ('Employment type: Full-time', 'this is a contract role'). Leave empty for all jobs.

## `includeUnknownJobType` (type: `boolean`):

With job types set, also keep jobs whose type is not stated (jobType: null). Off by default: they are left out.

## `sources` (type: `array`):

Which applicant-tracking systems to search. The default is every system except Workable (14 systems, about 19,000 boards).

## `searchDescriptions` (type: `boolean`):

Match keywords in the full description, not just the title. Slower (Greenhouse boards are downloaded in full). Not available for SmartRecruiters, which only exposes titles in its list.

## `includeDescription` (type: `boolean`):

Adds descriptionText and descriptionHtml to each job. Same price either way; turn off for a smaller dataset.

## `maxJobs` (type: `integer`):

Hard cap on jobs delivered (and charged) in this run. The search stops as soon as it is reached.

## `maxJobsPerBoard` (type: `integer`):

Stop after this many matching jobs from one company board, so one big employer does not fill the result. Leave empty for no limit.

## `maxBoardsPerSource` (type: `integer`):

Scan only the first N boards of each system in the directory (for quick tests). Leave empty to scan every board.

## `extraBoards` (type: `array`):

Company job-board addresses not in the bundled directory, e.g. https://jobs.lever.co/spotify. They are scanned first.

## `onlyNew` (type: `boolean`):

For scheduled alerts. Delivers (and charges) only jobs not delivered before under the same monitor ID. The first run delivers every current match and remembers it.

## `monitorId` (type: `string`):

A name you choose for this search, e.g. "backend-berlin". Runs with the same monitor ID share their memory of delivered jobs (stored in your account). Required with onlyNew.

## `maxMinutes` (type: `integer`):

Optional time limit for any live sweeping. Searches answered from the index take about a minute and ignore it; leave empty.

## `useIndex` (type: `boolean`):

Answer from the index of all boards, refreshed on a rotation twice a day, stalest parts first; each run reports the age of the oldest part it used, and stale parts are also read live (about a minute), then re-check each match on the employer's own board. Turn off to read every board live (slow: 15+ minutes for all systems, so combine with maxMinutes or fewer systems).

## `liveTopUpSeconds` (type: `integer`):

After the index answer, seconds spent reading live the boards the index lacks or holds only partly (most outdated first). 0 = index and the live re-check of matches only. Ignored when maxMinutes is set.

## Actor input object example

```json
{
  "keywords": [
    "data engineer",
    "machine learning engineer"
  ],
  "remoteOnly": false,
  "postedWithinDays": 7,
  "salaryCurrency": "USD",
  "includeJobsWithoutSalary": false,
  "includeUnknownJobType": false,
  "sources": [
    "greenhouse",
    "lever",
    "ashby",
    "smartrecruiters",
    "recruitee",
    "workday",
    "oracle",
    "icims",
    "personio",
    "teamtailor",
    "bamboohr",
    "breezy",
    "pinpoint",
    "rippling"
  ],
  "searchDescriptions": false,
  "includeDescription": true,
  "maxJobs": 50,
  "onlyNew": false,
  "useIndex": true,
  "liveTopUpSeconds": 5
}
```

# Actor output Schema

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

No description

## `RUN_SUMMARY` (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 = {
    "keywords": [
        "data engineer",
        "machine learning engineer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("hiver/career-site-job-search").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 = { "keywords": [
        "data engineer",
        "machine learning engineer",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("hiver/career-site-job-search").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 '{
  "keywords": [
    "data engineer",
    "machine learning engineer"
  ]
}' |
apify call hiver/career-site-job-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hiver/career-site-job-search"
        }
    }
}
```

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/WMJyK4F4E1IPhD0IA/builds/RG1Fph8P0SWbd5P2Q/openapi.json
