# Job Feed & Tracker: Greenhouse, Lever, Ashby & 6 more ATS (`bottyprojects/job-board-tracker`) Actor

Search fresh jobs across about 9,700 company job boards on 9 ATS platforms, or track your own company list for new and removed jobs. Every result links to the original posting. Pay per result.

- **URL**: https://apify.com/bottyprojects/job-board-tracker.md
- **Developed by:** [BottyProjects Data Tools](https://apify.com/bottyprojects) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 job records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Job Feed & Tracker: Greenhouse, Lever, Ashby & 6 more ATS

Search fresh jobs on 9 ATS job-board sources, or track any company list.

**Find fresh jobs straight from company career boards, in one run, with no setup.** Type a role ("data engineer") and a place ("United States", "Remote"). The Actor scans thousands of public company job boards on Greenhouse, Lever, Ashby, Workable, Recruitee, Personio, Teamtailor, Breezy HR and Pinpoint, and returns one clean record per matching job with a link to the original posting.

Already have a list of companies? Switch to **Track** mode. It remembers the last run and marks every job **new**, **still open** or **removed**, so a daily run delivers only the changes.

- ✅ **Search mode:** keywords + location + "posted in the last N days", across a bundled list of about 9,660 live company boards, plus any boards you add
- ✅ **Track mode:** change tracking for your own company list, with only-changes output (unchanged from v1)
- ✅ **9 sources searched by default:** Greenhouse, Lever (US + EU), Ashby, Workable, Recruitee, Personio, Teamtailor, Breezy HR and Pinpoint. Rippling (and BambooHR, if you turn it on) is fetched only for boards you enter yourself.
- ✅ **One schema for every source:** title, company, department, location, country, remote flag, workplace and employment type, posted date, salary when published, link, short snippet
- ✅ **No personal data by default:** recruiter fields and avatars dropped, emails and phone numbers redacted
- ✅ **Polite by design:** public logged-off endpoints only, robots.txt honored, per-host rate limits, stops on blocks
- ✅ **Fast and cheap:** you pay only for matching jobs delivered; one bad board never fails the run

### Who it is for

- **Recruiters and agencies:** fresh roles to pitch candidates into, by title and region, daily.
- **Sales teams:** hiring is buying intent. Find companies opening "Head of RevOps" or "Data Engineer" roles this week.
- **Investors and competitor watchers:** track hiring at portfolio or rival companies and see openings, removals and freezes.
- **Job-board and newsletter builders:** a clean, deduplicated feed of new postings with links back to the source.
- **Job seekers:** see new roles the day they go up, straight from the employer's own board.

### Quick start (Search mode)

The form is prefilled. Press **Start** and you get up to 50 software and data engineer jobs in the United States, posted in the last 14 days, at most 10 per company.

```json
{
  "mode": "search",
  "searchKeywords": ["software engineer", "data engineer"],
  "searchLocations": ["United States"],
  "postedWithinDays": 14,
  "maxResults": 50
}
```

More examples:

| Goal | Input |
|---|---|
| Remote product designers, last 7 days | `{"mode":"search","searchKeywords":["product designer"],"remoteOnly":true,"postedWithinDays":7}` |
| Account executives in the UK or Germany | `{"mode":"search","searchKeywords":["account executive"],"searchLocations":["UK","Germany"]}` |
| Only boards I choose, no bundled list | `{"mode":"search","searchKeywords":["nurse"],"companies":["teamtailor:acme","https://acme.breezy.hr"],"useSeedList":false}` |

How search works:

1. Boards you enter in **Companies** are scanned first.
2. Then boards your earlier runs learned about (see "Board registry" below).
3. Then the bundled seed list, largest boards first, mixed across sources.
4. It stops at **Max results**, **Max boards to scan** or **Max run time** (default 240 s), whichever comes first. `RUN_SUMMARY` shows how many boards were scanned and why it stopped, and suggests what to change if nothing matched.

Search options:

- **Keywords:** whole words or phrases in the job title. Any one keyword is enough. `excludeTitleKeywords` removes titles.
- **Locations:** any one match is enough. Countries and common aliases work ("United States", "US", "UK", "Germany", "Europe", "Remote"). US state codes ("CA", "NY") match the "City, ST" part of a location.
- **Max results per company:** default 10, so one big employer can't fill the whole result set (boards you enter yourself are not capped).
- **Posted within (days):** jobs with no published date (for example Pinpoint and some Workable boards) are left out unless you turn on **Include undated jobs**.
- **Sources to search:** limit the scan to some of the 9 searchable sources.

### Track mode (change tracking)

Put one company per line in **Companies** and set **Mode** to **Track**. Any of these work:

| Format | Example |
|---|---|
| Board URL | `https://job-boards.greenhouse.io/airbnb`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/ramp`, `https://apply.workable.com/huggingface/` |
| More board URLs | `https://<slug>.recruitee.com`, `https://<slug>.jobs.personio.de`, `https://<slug>.teamtailor.com`, `https://<slug>.breezy.hr`, `https://<slug>.pinpointhq.com`, `https://ats.rippling.com/<slug>/jobs` |
| Shorthand | `greenhouse:stripe`, `lever:spotify`, `lever-eu:<slug>`, `ashby:notion`, `workable:<slug>`, `recruitee:<slug>`, `personio:<slug>`, `teamtailor:<slug>`, `breezy:<slug>`, `pinpoint:<slug>`, `rippling:<slug>` |
| A company careers page | `https://www.example.com/careers`: scanned for an embedded board (if robots.txt allows) |
| Custom name | add ` \| Name`, e.g. `lever:spotify \| Spotify` |

```json
{
  "mode": "track",
  "companies": ["greenhouse:airbnb", "https://jobs.lever.co/palantir", "ashby:notion | Notion"],
  "trackerId": "my-prospects",
  "onlyChanges": true
}
```

**Existing users:** if your input has no `mode`, the Actor runs in Track mode exactly as before. Same output fields, same events, same prices.

Tracking options: **Tracker ID** (the name of this watchlist's memory), **Output all jobs on a company's first run**, **Title must / must not contain**, **Location contains**, **Remote only**, **Max jobs per company**.

### How change tracking works

The Actor keeps a small memory in a named key-value store, `job-board-tracker-state`, in **your** account. It holds one record per tracker ID and company. Each run compares the live board with that memory:

1. A job that wasn't there last time is **new**.
2. A job that's still listed is **open**. If its title, location, department or type changed, `changedFields` lists what changed.
3. A remembered job that's no longer listed is **removed** (output once, then forgotten).

The memory is only updated after a company was fetched successfully, so a temporary outage never shows up as "everything removed". If your max cost per run cuts off a company's output, that company's memory isn't updated, so you get those changes on the next run. Use a different **Tracker ID** for each list you track.

**Tip:** schedule a daily run with **Only output changes** on. Add a webhook or the Slack/email integration to get the changes delivered.

### Output

One dataset item per job. Search-mode example (shape of a real record):

```json
{
  "recordType": "job",
  "mode": "search",
  "changeStatus": null,
  "company": "Ramp",
  "ats": "ashby",
  "companySlug": "ramp",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Data Engineer",
  "department": "Engineering",
  "location": "New York, NY (HQ) | Remote (US)",
  "country": null,
  "remote": true,
  "workplaceType": "hybrid",
  "employmentType": "full_time",
  "postedAt": "2026-10-27T17:12:35.753Z",
  "postedAtSource": "ats",
  "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "salary": null,
  "salaryText": null,
  "descriptionSnippet": "ABOUT THE ROLE …",
  "matchedKeywords": ["data engineer"],
  "scrapedAt": "2026-10-31T19:52:42.508Z"
}
```

| Field | Meaning |
|---|---|
| `changeStatus` | `new` (first time seen), `open` (seen before, still listed) or `removed` (gone since last run) |
| `changedFields` | for `open` jobs: which of title, location, department, employmentType, workplaceType changed |
| `isBaseline` | `true` on the first run for that company (everything is "new") |
| `remote` | `true` if the ATS marks it remote or a location says remote |
| `workplaceType` | `remote`, `hybrid`, `onsite` or `null` if the ATS doesn't say |
| `employmentType` | `full_time`, `part_time`, `contract`, `internship`, `temporary`, `other` or `null` |
| `postedAt` / `updatedAt` | ISO dates as given by the ATS (`null` if the ATS doesn't publish it) |
| `firstSeenAt` / `lastSeenAt` / `removedAt` | when this Actor first saw, last saw, and noticed removal of the job |

| `mode` | `search` for search results; absent on Track-mode records (unchanged v1 format) |
| `matchedKeywords` | which of your keywords matched the title |
| `country` | ISO country code when the source publishes one |

Error rows (wrong slug, removed board, blocked, opt-out) are never charged. The dataset has views for **Search results**, **Overview**, **Changes**, **Full details** and **Errors**. `RUN_SUMMARY` in the run's key-value store has the counts.

### Board registry (shared within your account, no personal data)

Search runs keep a small list in a named key-value store, `job-feed-board-registry`, in **your** Apify account. It holds only board identifiers (like `greenhouse:acme`), a status (`ok`, `empty`, `dead`, `blocked`, `error`) and a date. Your later runs use it to skip dead boards for 30 days, blocked boards for 90 days and empty boards for 5 days, and to include boards you entered before. No job data and no personal data are stored there. Delete the store at any time to reset it.

### Sources and limits

| Source | Used in search | How it is read |
|---|---|---|
| Greenhouse | yes | public Job Board API |
| Lever (US and EU) | yes | public Postings API, 1 request/second (its robots.txt crawl delay) |
| Ashby | yes | public Job Posting API |
| Workable | yes | public jobs widget feed |
| Recruitee | yes | public Careers Site API (Recruitee plans to require a token from 10 Feb 2027) |
| Personio | yes | public XML job feed |
| Teamtailor | yes | public `jobs.json` feed on each career site |
| Breezy HR | yes | public JSON feed on each career portal |
| Pinpoint | yes | public `postings.json` endpoint (no posted dates) |
| Rippling | no, boards you enter only | public board feed, about 1 request/second |
| BambooHR | no, off by default (`enableBambooHR`), boards you enter only | public careers list, about 1 request/second |
| SmartRecruiters | Not supported | Its API policy covers a customer's own career site only, and its robots.txt disallows other crawlers. |
| Workday, iCIMS, Taleo, LinkedIn, Indeed | not supported | — |

Limits:

- The bundled seed list was built once from public web-crawl index files and checked against each board's public endpoint. It is a sample of company boards, not every company. Add your own boards in **Companies** to cover a specific employer.
- Each board is fetched live in your run, so results are as fresh as the employer's board. A search run scans up to **Max boards to scan** (default 1,500) within **Max run time** (default 240 s).
- On HTTP 401 or 403, a robots.txt disallow, or a bot-challenge page, the Actor stops for that board and reports an error row. On HTTP 429 it stops at once for Rippling and BambooHR, and after one polite retry for other sources. It never retries through proxies or other workarounds.
- Requests to each host are sent one at a time, with a fixed pause between them (0.3–1.5 s, depending on the source, or longer if the site's robots.txt asks for it). No more than 10 boards are processed in parallel.
- We suggest keeping output datasets for no more than 90 days. `RUN_SUMMARY` (counts, board identifiers and error text, no personal data) sits in the run's default key-value store, which Apify deletes automatically after your account's data-retention period. Keep it no longer than 90 days either (Billing → Limits → data retention). The named stores `job-board-tracker-state` and `job-feed-board-registry` are kept until you delete them, so delete them when you stop tracking.

### Privacy

By default the output contains no personal data: recruiter and hiring-manager fields and avatars are dropped, and emails and phone numbers in descriptions are replaced with `[email removed]` / `[phone removed]`. If you turn on `includePersonalData`, you must also tick `personalDataLawfulBasisConfirmed`. Then only names shown on the public posting are kept, nothing is enriched, and no personal data is cached between runs or shared between users.

### Pricing

Pay per event. You only pay for what's delivered:

| Event | Price |
|---|---|
| Job record delivered (both modes) | $0.003 ($3 per 1,000) |
| Company checked (Track mode only: one company's board fetched and compared) | $0.002 |

Search mode charges **only** for matching jobs delivered, never for the boards it scans. The Apify platform also charges its standard tiny Actor-start fee. Error rows and skipped unchanged jobs are free.

Examples:

- **Search:** 50 results = **$0.15**. 500 results = **$1.50**. **Max results** caps the cost of a run.
- **Track, full snapshot:** 8 companies with 1,642 jobs cost about **$4.94**.
- **Track, daily changes:** the same 8 companies with only changes on cost about **$0.03 a day**. 100 companies with about 30 changes a day cost about **$0.29 a day**.

Set **Max cost per run** to cap any run. The Actor stops cleanly when the cap is reached.

### Legal & fair use

**Legal & fair-use notice.** This Actor collects publicly available job postings from public, logged-off career pages and job-board endpoints of applicant tracking systems (ATS). It is not affiliated with, endorsed by, or sponsored by Workday, BambooHR, Rippling, SmartRecruiters, Greenhouse, Lever or any other ATS vendor or employer; product names are used only to describe compatible sources. The Actor never logs in, never bypasses bot protection, honors robots.txt, rate-limits itself, and stops on blocks (HTTP 401/403/429 or challenges). Each result links to the original posting, which remains the authoritative source.

You are responsible for making sure your use of this Actor and its output complies with the terms of the sites you target and with applicable law, including the GDPR, UK GDPR and CCPA/CPRA. By default, the Actor does not output personal data. If you enable `includePersonalData`, you act as the data controller and must have a lawful basis. Don't use the output for unsolicited marketing, profiling, or building contact databases.

**Opt-out / removal:** Employers, ATS vendors or individuals can ask for a company, tenant or person to be excluded by opening an issue on this Actor's Apify page. We act on requests within 30 days, and within 24 hours for site-owner access objections.

This notice is not legal advice.

### FAQ

**Can I use the output to train AI models?** No. Some job-board hosts publish a content signal that they don't want their content used for AI training (`ai-train=no`). This Actor is built for job search, monitoring, recruiting and research, not for training datasets.

**Why did my search return few results?** Narrow keywords plus a short date window can match few jobs among the boards scanned. `RUN_SUMMARY` → `search.whyNoResults` says what to loosen: broader keywords, more locations, a longer `postedWithinDays`, `includeUndated`, or a higher **Max boards to scan**.

**How do I add a company that isn't found?** Paste its board link or careers page into **Companies**. It is scanned first, and later runs in your account remember it.

**How fast is it?** For common searches, fast: in our test, the prefilled search ("software engineer" / "data engineer", United States, last 14 days) returned 50 results in about 30 seconds after checking about 52,000 open jobs on 114 boards. Rare or very narrow searches can run until the time limit (default 240 s) and return fewer results; the run summary then tells you which limit stopped it. Most of the time is polite waiting between requests to each job-board host.

**Something broke, or you want a board removed?** Open an issue on the Actor's Issues tab with the board link. `RUN_SUMMARY` shows the HTTP status and error per board.

# Actor input Schema

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

Search = one live query across a bundled list of public company job boards (plus any boards you list below). You pay only per matching job delivered. Track = monitor the companies you list run after run and report new / removed / changed jobs. API calls without this field run in Track mode, exactly as before.

## `searchKeywords` (type: `array`):

Search mode: a job matches if its title contains any of these words or phrases as whole words (case-insensitive), e.g. "data engineer", "product designer", "account executive". Use "Title must NOT contain" below to exclude words such as "senior" or "intern".

## `searchLocations` (type: `array`):

Search mode: a job matches if its location mentions any of these. Country names understand common cities and codes ("United States" also matches "Austin, TX"; "Germany" matches "Berlin"). "Remote" matches remote jobs. Leave empty for anywhere.

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

Search mode: only jobs whose board shows a posting date in the last N days (0 = any date). Boards that publish no date (Pinpoint, some Personio and Breezy jobs) are left out unless you turn on "Include jobs without a posting date".

## `maxResults` (type: `integer`):

Search mode: stop after this many matching jobs. You are charged per job delivered, so this also caps the cost of a run (e.g. 100 results = $0.30).

## `maxResultsPerCompany` (type: `integer`):

Search mode: at most this many results from any one company found through the bundled list, so one big employer can't fill your whole result set. Boards you enter in Companies are not capped. 0 = no limit.

## `companies` (type: `array`):

Company career-board URLs or ATS slugs to track. You confirm these pages are publicly accessible without login. Companies or tenants that block automated access or ask to be excluded will be skipped. One per line: a board URL (e.g. https://jobs.ashbyhq.com/ramp, https://job-boards.greenhouse.io/airbnb, https://jobs.lever.co/palantir, https://apply.workable.com/huggingface, https://acme.recruitee.com, https://acme.jobs.personio.de, https://acme.teamtailor.com, https://acme.breezy.hr, https://acme.pinpointhq.com, https://ats.rippling.com/acme/jobs), the shorthand ats:slug (greenhouse:stripe, lever:spotify, ashby:notion, workable:huggingface, recruitee:x, personio:x, teamtailor:x, breezy:x, pinpoint:x, rippling:x), or a company careers page that embeds one of those boards. Add " | Name" to set the company name. Required in tracking mode; optional in search mode, where these boards are searched first.

## `includeUndated` (type: `boolean`):

Search mode: also return jobs from boards that do not publish a posting date (postedAt will be empty).

## `maxBoards` (type: `integer`):

Search mode: how many job boards from the bundled list to check (largest boards first). Boards are not charged; this only limits run time.

## `maxRunSeconds` (type: `integer`):

Search mode: stop scanning after this many seconds and return what was found.

## `searchAts` (type: `array`):

Search mode: which platforms from the bundled list to search (empty = all). BambooHR and Rippling boards are never in the bundled list (SmartRecruiters is not supported); Rippling and BambooHR boards can still be added under Companies.

## `useSeedList` (type: `boolean`):

Search mode: turn off to search only the boards you list under Companies (and boards your earlier runs added).

## `onlyChanges` (type: `boolean`):

Output only jobs that are new, removed, or whose title / location / department / type changed since the last run with the same tracker ID. Unchanged open jobs are skipped (and not charged).

## `emitBaseline` (type: `boolean`):

The first time a company is tracked every job is 'new'. Turn this off to silently record the baseline in 'Only output changes' mode, so you are only told about real changes from the next run on.

## `trackerId` (type: `string`):

Name of this watchlist's memory. Runs with the same tracker ID are compared with each other. Use different IDs for different lists (e.g. 'prospects', 'competitors'). Letters, digits, - and \_ only.

## `maxJobsPerCompany` (type: `integer`):

Cap the records output per company (0 = no cap). Change tracking still covers all jobs.

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

Only output jobs whose title contains at least one of these words (case-insensitive). Leave empty for all jobs. In search mode this is used only if "Search: job title keywords" is empty.

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

Skip jobs whose title contains any of these words (both modes).

## `locationKeywords` (type: `array`):

Only output jobs whose location contains at least one of these words, e.g. 'Remote', 'New York', 'Germany'. In search mode this is used only if "Search: locations" is empty.

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

Only output jobs flagged as fully remote (both modes).

## `includePersonalData` (type: `boolean`):

**Include personal data (off by default).** When off, recruiter or hiring-manager names, avatars, emails and phone numbers are removed, and emails and phone numbers inside job descriptions are replaced with a placeholder (`[email removed]`, `[phone removed]`). Turn this on only if you have a lawful basis to process this data (for example, a documented legitimate-interest assessment under GDPR Art. 6(1)(f)) and you can meet transparency (GDPR Art. 14), minimisation, retention and data-subject request obligations. When on, you are the data controller for that data. Don't use it for unsolicited marketing or to build contact lists.

## `personalDataLawfulBasisConfirmed` (type: `boolean`):

Required when "Include personal data" is on. When on, only names and business contact details shown on the public posting are kept; nothing is enriched or combined across postings.

## `descriptionSnippetLength` (type: `integer`):

Characters of job description to include (personal data removed unless "Include personal data" is on). 0 = no snippet. In search mode Greenhouse jobs have no snippet (their list endpoint has no descriptions), which keeps searches fast.

## `scanCareersPages` (type: `boolean`):

If an entry is a company's own careers page (not a board URL), fetch that one page (robots.txt respected) to find the embedded job board.

## `outputErrors` (type: `boolean`):

Add one row per company that failed (with the reason). Error rows are never charged.

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

How many companies to fetch at once. Requests to the same ATS host are always spaced out politely. Tracking mode only.

## `enableBambooHR` (type: `boolean`):

Off by default. When on, BambooHR careers pages you list under Companies are read from their public, logged-off careers list at about 1 request per second; the run stops for that board on HTTP 401/403/429 or a challenge. Never searched from the bundled list.

## Actor input object example

```json
{
  "mode": "search",
  "searchKeywords": [
    "data engineer",
    "analytics engineer"
  ],
  "searchLocations": [
    "United States",
    "Remote"
  ],
  "postedWithinDays": 14,
  "maxResults": 50,
  "maxResultsPerCompany": 10,
  "companies": [
    "greenhouse:airbnb",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/ramp",
    "workable:huggingface"
  ],
  "includeUndated": false,
  "maxBoards": 1500,
  "maxRunSeconds": 240,
  "useSeedList": true,
  "onlyChanges": false,
  "emitBaseline": true,
  "trackerId": "default",
  "maxJobsPerCompany": 0,
  "remoteOnly": false,
  "includePersonalData": false,
  "personalDataLawfulBasisConfirmed": false,
  "descriptionSnippetLength": 300,
  "scanCareersPages": true,
  "outputErrors": true,
  "maxConcurrency": 4,
  "enableBambooHR": false
}
```

# Actor output Schema

## `searchResults` (type: `string`):

No description

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

No description

## `changes` (type: `string`):

No description

## `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 = {
    "mode": "search",
    "searchKeywords": [
        "software engineer",
        "data engineer"
    ],
    "searchLocations": [
        "United States"
    ],
    "postedWithinDays": 14,
    "maxResults": 50,
    "companies": [
        "greenhouse:airbnb",
        "https://jobs.lever.co/palantir",
        "https://jobs.ashbyhq.com/ramp",
        "workable:huggingface"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("bottyprojects/job-board-tracker").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",
    "searchKeywords": [
        "software engineer",
        "data engineer",
    ],
    "searchLocations": ["United States"],
    "postedWithinDays": 14,
    "maxResults": 50,
    "companies": [
        "greenhouse:airbnb",
        "https://jobs.lever.co/palantir",
        "https://jobs.ashbyhq.com/ramp",
        "workable:huggingface",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("bottyprojects/job-board-tracker").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",
  "searchKeywords": [
    "software engineer",
    "data engineer"
  ],
  "searchLocations": [
    "United States"
  ],
  "postedWithinDays": 14,
  "maxResults": 50,
  "companies": [
    "greenhouse:airbnb",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/ramp",
    "workable:huggingface"
  ]
}' |
apify call bottyprojects/job-board-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bottyprojects/job-board-tracker"
        }
    }
}
```

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/gZx1MzDA7YKkIBMeJ/builds/l4X21vaIECAMzlaye/openapi.json
