# Company Jobs Scraper (`spookyweb/company-jobs`) Actor

Give it company domains and get back the roles those companies are hiring for right now, read straight from their Greenhouse, Lever, Ashby, Workable, Recruitee, Teamtailor, Personio, BambooHR or SmartRecruiters board. Filter by location, department or how recently a job was posted.

- **URL**: https://apify.com/spookyweb/company-jobs.md
- **Developed by:** [丂卩ㄖㄖҜㄚ](https://apify.com/spookyweb) (community)
- **Categories:** Jobs, Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 job founds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

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

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

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

# README

## Company Jobs Scraper

Give it a list of company domains. Get back every role those companies are hiring for right now, read straight from the applicant tracking system each one runs.

### 🔍 What does Company Jobs Scraper do?

You give it **company domains**, one per line. For each one it works out which applicant tracking system that company uses, calls that system's own public board endpoint, and returns the **current openings** as structured rows. The quickest way to try it is to leave the two prefilled domains in place and press Start.

This is a **company research tool, not a job board**. It answers "what is this specific company hiring for today", which is a different question from "find me every marketing job in Berlin". You bring the list of companies, it reads each one's board at source, and the rows are as current as that company's own careers page.

Most companies do not host their own job listings. They use an applicant tracking system, and the common ones publish a JSON endpoint per company board. Company Jobs Scraper knows nine of them and reads whichever one applies.

### 📊 What data can I extract?

One row per job, plus one row for each company that was read and turned out to be hiring nothing, so every domain you put in comes back out. Every field is present on every row, set to `null` where the board in question does not publish it.

| Field | Type | What it is |
|---|---|---|
| `domain` | string | The company domain from your input that this job belongs to |
| `companyName` | string | Company name as the board or the website reports it |
| `status` | string | `open_role` on a vacancy, or one of three outcomes on a company row, see [One row per company, whether or not it is hiring](#one-row-per-company-whether-or-not-it-is-hiring) |
| `jobId` | string | The job identifier used by the applicant tracking system |
| `title` | string | Job title as advertised |
| `department` | string | Department the role sits in |
| `team` | string | Team within the department, where the board separates the two |
| `location` | string | Where the role is based, joined into one line when several places are listed |
| `isRemote` | boolean | True or false where the board flags it, null where it says nothing |
| `employmentType` | string | Contract type as the board words it, such as Full-time or permanent |
| `postedAt` | string | When the job was first published, ISO 8601 |
| `updatedAt` | string | When the job was last changed, ISO 8601, on the boards that report it |
| `applyUrl` | string | Public link to the advert or its application form |
| `descriptionText` | string | Full advert text with the markup removed |
| `descriptionHtml` | string | Full advert as the board publishes it, in HTML |
| `salaryRaw` | string | The salary exactly as published, before any interpretation |
| `salaryMin` | number | Bottom of the range, equal to `salaryMax` when one figure was given |
| `salaryMax` | number | Top of the range, equal to `salaryMin` when one figure was given |
| `salaryCurrency` | string | ISO 4217 code, where it could be determined |
| `ats` | string | Which system the row came from |
| `atsBoardToken` | string | The board identifier that company uses on that system |
| `source` | string | How the row was obtained, see [Where a row comes from](#where-a-row-comes-from) |
| `fetchedAt` | string | When this company was read, ISO 8601 |

### 💡 Why track a company's job openings?

Hiring is the cheapest public signal a company gives off, and it is published before anything else is.

- **Buying signals.** Six Salesforce administrator openings at one company is that company saying what it is about to spend money on, published weeks before any announcement is.
- **A watchlist of boards in one dataset.** Twenty domains in, one table out, on a schedule. A role shows up the day it goes up rather than the week it closes.
- **Department mix over time.** Run the same list weekly and diff it, and a company that stops posting engineering roles and starts posting account executives shows up as data rather than as a hunch.
- **Expansion and location.** Offices, remote flags and the pay figures that were published, as a live read on where headcount is going that does not wait for a filing.
- **Feeding an agent.** The output schema means an AI agent can chain this Actor and act on the rows without a scraping step of its own.

### 🚀 How do I use Company Jobs Scraper?

1. Click **Try for free** and sign in to Apify, which takes about a minute.
2. Paste your company domains into **Company domains**, one per line. `stripe.com` is fine, so is `https://www.stripe.com/`.
3. Optionally narrow it down with **Location contains**, **Department or team contains** or **Posted within the last N days**.
4. Turn off **Include the full job description** if you only want titles, locations and links, which makes the dataset a lot smaller.
5. Press **Start**, then export the results as JSON, CSV, Excel or feed them straight into your CRM through the API.

### ⬇️ Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `domains` | array | required | Company websites, one per line. Every one comes back as at least one row |
| `maxJobsPerCompany` | integer | `100` | Cap per company, applied after the filters |
| `includeDescription` | boolean | `true` | Full advert text and HTML |
| `locationFilter` | string | none | Substring match on location, case insensitive |
| `departmentFilter` | string | none | Substring match on department or team, case insensitive |
| `postedWithinDays` | integer | none | Only jobs first published this recently |

```json
{
  "domains": ["stripe.com", "gitkraken.com", "channable.com"],
  "maxJobsPerCompany": 100,
  "includeDescription": true,
  "locationFilter": "London",
  "postedWithinDays": 30
}
```

### ⬆️ Output

Every row below came out of a real run over `stripe.com`, `gitkraken.com` and `channable.com`, copied unedited apart from the advert text, which is shortened here because the real one runs to several thousand characters.

#### Table view

| Company | Status | Job title | Department | Location | Posted | ATS |
|---|---|---|---|---|---|---|
| Stripe | open\_role | Account Executive, Bridge | 1642 Product Sales - MaaS | SF, NYC, SEA, CHI | 2026-07-22 | greenhouse |
| GitKraken | open\_role | Product Associate/Manager | PRO001 - Product | Scottsdale, Arizona, United States | 2026-06-03 | bamboohr |
| Channable | open\_role | Technical Customer Support DACH | Support | Utrecht, Utrecht, Netherlands | 2026-08-03 | recruitee |

#### JSON

```json
{
  "domain": "stripe.com",
  "companyName": "Stripe",
  "status": "open_role",
  "jobId": "8077887",
  "title": "Account Executive, Bridge",
  "department": "1642 Product Sales - MaaS",
  "team": null,
  "location": "SF, NYC, SEA, CHI",
  "isRemote": null,
  "employmentType": null,
  "postedAt": "2026-07-22T17:15:53.000Z",
  "updatedAt": "2026-08-06T16:10:17.000Z",
  "applyUrl": "https://stripe.com/jobs/search?gh_jid=8077887",
  "descriptionText": "Who we are\n\nAbout Stripe\n\nStripe is a financial infrastructure platform for businesses...",
  "descriptionHtml": "<h2><strong>Who we are </strong></h2>\n<h3><strong>About Stripe</strong></h3>...",
  "salaryRaw": null,
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "ats": "greenhouse",
  "atsBoardToken": "stripe",
  "source": "ats_api_guessed_token",
  "fetchedAt": "2026-08-15T16:58:06.422Z"
}
```

A company with a structured salary looks like this, from the same run. Lever, Ashby and Recruitee publish pay as fields rather than as prose, so the figures are exact rather than read out of the advert:

```json
{
  "domain": "channable.com",
  "companyName": "Channable",
  "status": "open_role",
  "title": "Technical Customer Support DACH - German speaking",
  "department": "Support",
  "location": "Utrecht, Utrecht, Netherlands",
  "isRemote": false,
  "employmentType": "fulltime_permanent",
  "postedAt": "2026-08-03T15:40:43.000Z",
  "salaryRaw": "2850 to 2950 per month",
  "salaryMin": 2850,
  "salaryMax": 2950,
  "salaryCurrency": "EUR",
  "ats": "recruitee",
  "atsBoardToken": "channable",
  "source": "ats_api_guessed_token"
}
```

#### One row per company, whether or not it is hiring

A company that was read and has nothing open still comes back as one row, so the output reconciles against the list you put in rather than leaving you to work out which domains went missing. That row costs nothing: only a vacancy is charged for.

| `status` | What happened | Charged |
|---|---|---|
| `open_role` | A live vacancy. This is what a job row is | Yes, one `job-found` |
| `no_open_roles` | The board or careers page was read and nothing matched | **No** |
| `no_board_found` | No board and no careers page could be found for that domain | **No** |
| `check_failed` | The company could not be reached at all, so nothing was learned | **No** |

On a company row every field belonging to a vacancy is `null`, so filtering the dataset to `status = open_role` gives you the jobs and nothing else. `ats`, `atsBoardToken` and `source` are still filled in where a board was found, which is how you tell "we know their board, it is empty today" from "we could not find their board at all".

#### Which fields you get depends on the board, not on the Actor

How full a row is is decided by the system the company runs, so the honest answer is per system rather than an average. A null here is the board publishing nothing, not the Actor failing to read it.

| System | Department | Team | Location | Remote flag | Contract type | Updated date | Pay as fields |
|---|---|---|---|---|---|---|---|
| Greenhouse | Yes | Second department where there is one | Yes | From the location text only | No | Yes | No, read from the advert |
| Lever | Yes | Yes | Yes | Yes | Yes | No | Yes |
| Ashby | Yes | Yes | Yes | Yes | Yes | No | Yes |
| Workable | Yes | Yes, as function | Yes | Yes | Yes | No | No, read from the advert |
| Recruitee | Yes | No | Yes | Yes | Yes | Yes | Yes |
| Teamtailor | No | No | No | No | No | Yes | No, read from the advert |
| Personio | Yes | Yes | Yes | From the location text only | Yes | No | No, read from the advert |
| BambooHR | Yes | No | Yes | Yes | Yes | No | No, read from the advert |
| SmartRecruiters | Yes | Yes, as function | Yes | Yes | Yes | No | No, read from the advert |

`isRemote` is left `null` rather than `false` when a board says nothing either way, because silence is not a denial. `title`, `postedAt` and `applyUrl` are published by all nine.

For a sense of scale rather than a rule: the 118 row run above, which was Greenhouse, BambooHR and Recruitee, came back 100% filled on `title`, `location`, `department`, `postedAt`, `applyUrl` and `descriptionText`, 96% on `updatedAt`, 22% on `isRemote`, 15% on `employmentType`, 10% on the salary fields and 0% on `team`. Those figures are three companies on three of the nine systems, so read them as an illustration of the table above and not as a population average. A list of Lever and Ashby companies would fill the salary and remote columns far more often.

Note also that `department` is whatever the employer typed into their own board, so it is worth looking at one unfiltered run before relying on a department filter across a whole list. Stripe names its departments after internal cost centres, so `Engineering` matches rows such as `8217 Risk Engineering` there but misses `8556 LPM Core - Eng`, while at GitLab the same filter matches plain `Data Engineering` and `DevOps Engineering`.

### 🧩 Which applicant tracking systems are supported?

Nine, covering the large majority of companies that publish jobs at all. Every endpoint below was read from the live service against a named board, and the board named in each row is the one it was checked against. An endpoint taken off a documentation page is a guess, and a test asserting the guess only makes the bug permanent.

| System | What the Actor reads | Checked against | Notes |
|---|---|---|---|
| [Greenhouse](https://www.greenhouse.io/) | The public board API | `stripe`, 578 roles in one response | Richest coverage. Department, offices and the full advert |
| [Lever](https://www.lever.co/) | The public postings API | `spotify`, 103 postings | Publishes a structured salary range on boards that use the feature |
| [Ashby](https://www.ashbyhq.com/) | The public posting API | `openai`, 746 roles in one response | Publishes structured compensation, including equity and bonus flags |
| [Workable](https://www.workable.com/) | The public widget account API | `zego`, and a 404 on an unknown board | Department, function and the full advert |
| [Recruitee](https://recruitee.com/) | The careers site API | `channable`, 13 roles | Publishes a structured salary with a period, such as per month |
| [Teamtailor](https://www.teamtailor.com/) | The public jobs feed | `vardaga`, 175 roles across two pages | Paged, 100 a page. Title, link, date and advert only, the feed carries no location or department |
| [Personio](https://www.personio.com/) | The public XML board | `holidu`, 67 positions | Offices, department and the advert split into named sections |
| [BambooHR](https://www.bamboohr.com/) | The public careers list | `gitkraken`, 5 roles | The posting date and advert need one extra call per job |
| [SmartRecruiters](https://www.smartrecruiters.com/) | The public postings API | `Visa`, with the total it reports | Paged, 100 a page. The advert needs one extra call per job |

### Where a row comes from

The `source` field says how the board was found, because that is the difference between a fact and a good guess.

1. **`ats_api`.** The Actor read the company's homepage, followed the link to its careers page, and found the board token in that page's links, scripts or iframes. This is the company telling you which system it runs.
2. **`ats_api_guessed_token`.** Nothing on the company's pages named a board, so the Actor tried the obvious token, the bare domain label, against each supported system, and one of them answered with jobs. Usually correct and occasionally not, which is why it is labelled. Check `companyName` against the domain if it matters to you.
3. **`careers_page`.** No supported system was found at all, so the careers page itself was parsed for job links. These rows carry a title and a link and little else, and they are marked so you can tell rather than having to work it out from the nulls.

Two systems answer with an empty list rather than an error when you ask them about a company they have never heard of. SmartRecruiters and BambooHR both do this, so an empty board reached by guessing is never treated as an answer. It has to come from the company's own pages, or return at least one job.

### 🧠 What the filters actually do

- **Location** and **department** are plain case insensitive substring matches, applied to the normalised row rather than passed to the board, so they behave identically across all nine systems.
- A row whose board publishes **no** location is dropped when a location filter is set, and the same for department and for posting date. A filter that cannot be evaluated is a filter that was not satisfied.
- **`maxJobsPerCompany`** is applied after the filters, so a cap of 50 with a location filter gives you the first 50 matching roles rather than 50 roles of which a few match. When the cap does bite, the run log names the company, says how many further matching roles were left behind and gives the real total, so a company with 100 openings and a company with 900 do not look identical in the output.
- **Two boards are paged** rather than read in one call, SmartRecruiters and Teamtailor, both at 100 postings a page, so on those two the cap stops the paging as well and the filters only ever saw what was read. The run log says so when it happens. SmartRecruiters publishes the real total, so the log gives the number left unread. Teamtailor publishes no total at all, so the log can only say that a further page was advertised and not read. Every other system returned its whole list in one response when this was checked, including boards of 578 and 746 roles.
- BambooHR and SmartRecruiters combined with **Posted within the last N days** are the one case where the cap comes before a filter. Those two publish the posting date only on a second per job request, so on those boards the date filter runs after the cap and you can get back fewer rows than the cap allows. Every other system carries the date on the list response and is filtered first.
- Two boards need one extra request per job to fetch the advert. Turning descriptions off skips those requests, which makes a BambooHR or SmartRecruiters company much faster.

### ⚠️ Limits worth knowing

- **A company with no open roles is a successful run.** You get one row for that company with a `status` saying which of the three no-vacancy outcomes it was, it is logged plainly, and the run carries on to the next company. It is not an error, and it does not fail the run.
- Teamtailor's public feed publishes no location or department, so those fields are null for Teamtailor companies. That is the feed, not the Actor.
- Salary is only as good as what was published. Where a board exposes structured pay, the figures come from those fields. Where it does not, the salary is read out of the advert text and left null rather than guessed at when nothing is there.
- Companies that render their careers page entirely in the browser, with no board token anywhere in the HTML, will fall through to the guess and then to zero rows. Give the board's own domain instead if you know it.

### 💰 How much does it cost?

Pay per event, so there is no monthly rental and nothing to cancel. One event is ours and it is tiered below. Apify charges its own **Actor start** event on top, at $0.00005 per gigabyte of memory allocated, and this Actor is capped at 1 GB because it needs no browser, so that is $0.00005 a run whatever you set.

Job returned, per vacancy:

| Free | Bronze | Silver | Gold | Platinum | Diamond |
|---|---|---|---|---|---|
| $0.014 | $0.0119 | $0.0098 | $0.0084 | $0.007 | $0.0056 |

On the Bronze plan, a run over 50 target accounts that turns up 400 open roles costs 400 at $0.0119, which is **$4.76**. The 50 companies themselves cost nothing.

**A vacancy is the only thing this Actor bills for.** A company that was read and is hiring nothing, a company whose board could not be found, and a company that could not be reached at all are all free. They still come back as rows so the output reconciles against the list you put in, and so you can see the coverage you actually got rather than only the hits.

If you set a maximum cost per run, the lowest this Actor accepts is **$0.02**, which covers the Actor start plus the first vacancy at the Free tier. A cap below that would let a run start, do the work and then be unable to bill for the first result it found.

### 🔌 Integrations

Company Jobs Scraper runs on the Apify platform, so it comes with the platform's plumbing rather than needing any of it built.

- **Schedule** it daily or weekly against your account list and diff the results to see what changed.
- **Webhooks** fire on a finished run, so a new job can land in Slack, a CRM or a spreadsheet without anyone watching.
- **Integrations** with Make, Zapier, Airbyte, Google Sheets, Slack, GitHub and others are configured in the Console.
- **Monitoring** and alerting on run status and output size, through [Apify monitoring](https://apify.com/apify/monitoring).
- **MCP** exposes it to an AI agent, which is why the output schema is part of the contract rather than an afterthought.

### 🔗 Using Company Jobs Scraper with the Apify API

Start a run and wait for the results in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/spookyweb~company-jobs/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "domains": ["stripe.com", "gitkraken.com"],
    "maxJobsPerCompany": 50,
    "postedWithinDays": 14
  }'
```

Or with the JavaScript client:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });

const run = await client.actor('spookyweb/company-jobs').call({
  domains: ['stripe.com', 'gitkraken.com'],
  includeDescription: false,
  locationFilter: 'London',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

Full reference for both is in the [Apify API documentation](https://docs.apify.com/api/v2).

### ❓ FAQ

#### How does it know which applicant tracking system a company uses?

It reads the company's homepage, follows the link to the careers page, and looks for a board token in that page's links, scripts and iframes. Almost every system leaves an unmistakable trace, such as a `boards.greenhouse.io` link or a `teamtailor.com` iframe. If nothing is there, it tries the obvious token against each supported system and labels the row so you know it was a guess.

#### What happens if a company has no open roles?

You get one row for that domain with `status` set to `no_open_roles`, a log line saying so, and a successful run. That is not an error. It is a genuine answer to the question you asked, and a company you are tracking will often have nothing open for weeks at a time. The row is there so that a run over 50 domains comes back as 50 companies accounted for rather than as a shorter list you have to diff against your own.

#### Why is there a row with no job on it?

Because that company was read and is hiring nothing right now, or its board could not be found. Filter the dataset to `status = open_role` if you only want vacancies. The company rows exist so the output reconciles against your input list, and they are free: every charge on your bill is a vacancy you can point at in the dataset.

#### Does it need an API key for any of these systems?

No. Every endpoint it reads is the same public board a candidate would see, published by the applicant tracking system so that job aggregators can read it. There is no login, no key and no proxy.

#### Why is the location empty for some jobs?

Because the board did not publish one. Teamtailor's public feed carries no location field at all, and some Greenhouse boards leave it blank. The Actor reports what the source says rather than inventing something plausible.

#### Can I get salary data for every job?

No, and no tool honestly can. Salary is only present where the employer chose to publish it. Where the board exposes pay as structured fields, as Lever, Ashby and Recruitee do, the figures are exact. Otherwise the salary is read out of the advert text, which works when a figure is stated and returns null when it is not.

#### How is this different from a job board scraper?

A job board scraper starts from a query and searches the market. This starts from a company and returns that company's roles, read from the board the employer posts to, so the rows are as current as that company's own careers page and include roles that were never syndicated anywhere. The two answer different questions and are often run together: [LinkedIn Jobs Scraper](https://apify.com/spookyweb/linkedin-jobs) and [Indeed Jobs Scraper](https://apify.com/spookyweb/indeed-jobs) do the search side.

#### Can I run this on a schedule?

Yes. Schedule it in the Apify Console, run it daily or weekly against the same account list, and compare datasets to see the roles that appeared and disappeared. That difference is usually the interesting part.

### ⚖️ Is it legal to scrape company job listings?

Reading a public job board is a well established and ordinary activity, and the endpoints this Actor uses are published by the applicant tracking systems specifically so that job aggregators, search engines and candidates can read them. It collects no personal data: a job advert is a company document about a role, not information about a person.

That said, we are not lawyers and this is not legal advice. Your obligations depend on where you are, what you do with the data afterwards, and the terms of any site involved. Apify has written a longer piece on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) that is worth reading before a large project. If you plan to republish the listings rather than use them internally, check the source's terms first.

### 👍 Your feedback

Found a company whose board is not detected, or an applicant tracking system that should be supported? Open an issue on the **Issues** tab of this Actor and it will be looked at. We build custom scrapers too, so if this is nearly what you need but not quite, say so and it can probably be made to fit.

### 🔎 You might also like

| Actor | What it does |
|---|---|
| [Company Email Finder](https://apify.com/spookyweb/company-email-finder) | Contact addresses a company publishes, plus its email naming convention |
| [Website Contact Scraper](https://apify.com/spookyweb/website-contact-scraper) | Emails, phones, socials and addresses from company websites, one record per domain |
| [LinkedIn Jobs Scraper](https://apify.com/spookyweb/linkedin-jobs) | Job listings from LinkedIn search, with no login or cookie needed |
| [Indeed Jobs Scraper](https://apify.com/spookyweb/indeed-jobs) | Job listings from Indeed search, with no account needed |
| [Reed Jobs Scraper](https://apify.com/spookyweb/uk-jobs-reed) | UK vacancies from Reed, searched by keyword, location and salary |
| [Totaljobs Jobs Scraper](https://apify.com/spookyweb/uk-jobs-totaljobs) | UK vacancies from Totaljobs, searched by keyword and location |

# Actor input Schema

## `domains` (type: `array`):

The company websites to check, one per line, for example stripe.com. Protocol and www are optional. Each domain is looked up separately and charged separately, whether or not it turns out to have any open roles, and every one comes back as at least one row so the output reconciles against this list. A domain that could not be reached at all is not charged for.

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

Stop after this many jobs from any single company, applied after the filters below. Useful when one company on your list has thousands of openings and the rest have a handful.

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

Return the full advert text and HTML for every job. Turn it off for a much smaller dataset when you only need titles, locations and links.

## `locationFilter` (type: `string`):

Keep only jobs whose location contains this text, matched without regard to case. For example London, or Remote. Jobs from a board that publishes no location are dropped when this is set.

## `departmentFilter` (type: `string`):

Keep only jobs whose department or team contains this text, matched without regard to case. For example Engineering, or Sales. Boards vary in how they name departments, so keep the text short.

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

Keep only jobs first published within this many days. Leave it empty for every open role regardless of age. Jobs from a board that publishes no posting date are dropped when this is set.

## Actor input object example

```json
{
  "domains": [
    "stripe.com",
    "gitkraken.com"
  ],
  "maxJobsPerCompany": 100,
  "includeDescription": true
}
```

# Actor output Schema

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

Every open role found, one row per job, with the company domain, the applicant tracking system it came from and a link to apply, plus one row for each company that was read and is hiring nothing. The status field says which is which.

# 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 = {
    "domains": [
        "stripe.com",
        "gitkraken.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("spookyweb/company-jobs").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 = { "domains": [
        "stripe.com",
        "gitkraken.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("spookyweb/company-jobs").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 '{
  "domains": [
    "stripe.com",
    "gitkraken.com"
  ]
}' |
apify call spookyweb/company-jobs --silent --output-dataset

```

## MCP server setup

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

```

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/84tRkqtsWE142GQND/builds/7Sj2RK9iQ5Ppba7Ee/openapi.json
