Company Jobs Scraper avatar

Company Jobs Scraper

Pricing

from $0.60 / 1,000 job founds

Go to Apify Store
Company Jobs Scraper

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 Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 hours ago

Last modified

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.

FieldTypeWhat it is
domainstringThe company domain from your input that this job belongs to
companyNamestringCompany name as the board or the website reports it
statusstringopen_role on a vacancy, or one of three outcomes on a company row, see One row per company, whether or not it is hiring
jobIdstringThe job identifier used by the applicant tracking system
titlestringJob title as advertised
departmentstringDepartment the role sits in
teamstringTeam within the department, where the board separates the two
locationstringWhere the role is based, joined into one line when several places are listed
isRemotebooleanTrue or false where the board flags it, null where it says nothing
employmentTypestringContract type as the board words it, such as Full-time or permanent
postedAtstringWhen the job was first published, ISO 8601
updatedAtstringWhen the job was last changed, ISO 8601, on the boards that report it
applyUrlstringPublic link to the advert or its application form
descriptionTextstringFull advert text with the markup removed
descriptionHtmlstringFull advert as the board publishes it, in HTML
salaryRawstringThe salary exactly as published, before any interpretation
salaryMinnumberBottom of the range, equal to salaryMax when one figure was given
salaryMaxnumberTop of the range, equal to salaryMin when one figure was given
salaryCurrencystringISO 4217 code, where it could be determined
atsstringWhich system the row came from
atsBoardTokenstringThe board identifier that company uses on that system
sourcestringHow the row was obtained, see Where a row comes from
fetchedAtstringWhen 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

FieldTypeDefaultNotes
domainsarrayrequiredCompany websites, one per line. Every one comes back as at least one row
maxJobsPerCompanyinteger100Cap per company, applied after the filters
includeDescriptionbooleantrueFull advert text and HTML
locationFilterstringnoneSubstring match on location, case insensitive
departmentFilterstringnoneSubstring match on department or team, case insensitive
postedWithinDaysintegernoneOnly 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

CompanyStatusJob titleDepartmentLocationPostedATS
Stripeopen_roleAccount Executive, Bridge1642 Product Sales - MaaSSF, NYC, SEA, CHI2026-07-22greenhouse
GitKrakenopen_roleProduct Associate/ManagerPRO001 - ProductScottsdale, Arizona, United States2026-06-03bamboohr
Channableopen_roleTechnical Customer Support DACHSupportUtrecht, Utrecht, Netherlands2026-08-03recruitee

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.

statusWhat happenedCharged
open_roleA live vacancy. This is what a job row isYes, one job-found
no_open_rolesThe board or careers page was read and nothing matchedNo
no_board_foundNo board and no careers page could be found for that domainNo
check_failedThe company could not be reached at all, so nothing was learnedNo

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.

SystemDepartmentTeamLocationRemote flagContract typeUpdated datePay as fields
GreenhouseYesSecond department where there is oneYesFrom the location text onlyNoYesNo, read from the advert
LeverYesYesYesYesYesNoYes
AshbyYesYesYesYesYesNoYes
WorkableYesYes, as functionYesYesYesNoNo, read from the advert
RecruiteeYesNoYesYesYesYesYes
TeamtailorNoNoNoNoNoYesNo, read from the advert
PersonioYesYesYesFrom the location text onlyYesNoNo, read from the advert
BambooHRYesNoYesYesYesNoNo, read from the advert
SmartRecruitersYesYes, as functionYesYesYesNoNo, 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.

SystemWhat the Actor readsChecked againstNotes
GreenhouseThe public board APIstripe, 578 roles in one responseRichest coverage. Department, offices and the full advert
LeverThe public postings APIspotify, 103 postingsPublishes a structured salary range on boards that use the feature
AshbyThe public posting APIopenai, 746 roles in one responsePublishes structured compensation, including equity and bonus flags
WorkableThe public widget account APIzego, and a 404 on an unknown boardDepartment, function and the full advert
RecruiteeThe careers site APIchannable, 13 rolesPublishes a structured salary with a period, such as per month
TeamtailorThe public jobs feedvardaga, 175 roles across two pagesPaged, 100 a page. Title, link, date and advert only, the feed carries no location or department
PersonioThe public XML boardholidu, 67 positionsOffices, department and the advert split into named sections
BambooHRThe public careers listgitkraken, 5 rolesThe posting date and advert need one extra call per job
SmartRecruitersThe public postings APIVisa, with the total it reportsPaged, 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:

FreeBronzeSilverGoldPlatinumDiamond
$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.

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

ActorWhat it does
Company Email FinderContact addresses a company publishes, plus its email naming convention
Website Contact ScraperEmails, phones, socials and addresses from company websites, one record per domain
LinkedIn Jobs ScraperJob listings from LinkedIn search, with no login or cookie needed
Indeed Jobs ScraperJob listings from Indeed search, with no account needed
Reed Jobs ScraperUK vacancies from Reed, searched by keyword, location and salary
Totaljobs Jobs ScraperUK vacancies from Totaljobs, searched by keyword and location