Company Jobs Scraper
Pricing
from $0.60 / 1,000 job founds
Company Jobs Scraper
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.
Pricing
from $0.60 / 1,000 job founds
Rating
0.0
(0)
Developer
丂卩ㄖㄖҜㄚ
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 hours ago
Last modified
Categories
Share
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 |
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 |
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?
- Click Try for free and sign in to Apify, which takes about a minute.
- Paste your company domains into Company domains, one per line.
stripe.comis fine, so ishttps://www.stripe.com/. - Optionally narrow it down with Location contains, Department or team contains or Posted within the last N days.
- Turn off Include the full job description if you only want titles, locations and links, which makes the dataset a lot smaller.
- 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 |
{"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
{"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:
{"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 | The public board API | stripe, 578 roles in one response | Richest coverage. Department, offices and the full advert |
| Lever | The public postings API | spotify, 103 postings | Publishes a structured salary range on boards that use the feature |
| Ashby | The public posting API | openai, 746 roles in one response | Publishes structured compensation, including equity and bonus flags |
| Workable | The public widget account API | zego, and a 404 on an unknown board | Department, function and the full advert |
| Recruitee | The careers site API | channable, 13 roles | Publishes a structured salary with a period, such as per month |
| Teamtailor | 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 | The public XML board | holidu, 67 positions | Offices, department and the advert split into named sections |
| BambooHR | The public careers list | gitkraken, 5 roles | The posting date and advert need one extra call per job |
| SmartRecruiters | 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.
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.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. CheckcompanyNameagainst the domain if it matters to you.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.
maxJobsPerCompanyis 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
statussaying 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.
- 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:
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:
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.
❓ 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 and Indeed Jobs Scraper 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 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 | Contact addresses a company publishes, plus its email naming convention |
| Website Contact Scraper | Emails, phones, socials and addresses from company websites, one record per domain |
| LinkedIn Jobs Scraper | Job listings from LinkedIn search, with no login or cookie needed |
| Indeed Jobs Scraper | Job listings from Indeed search, with no account needed |
| Reed Jobs Scraper | UK vacancies from Reed, searched by keyword, location and salary |
| Totaljobs Jobs Scraper | UK vacancies from Totaljobs, searched by keyword and location |