# Company Job Postings & Hiring Signals Scraper (`eiv/company-jobs-scraper`) Actor

Pull every open role from any company's job board via official ATS APIs —
Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio,
Breezy. Enriched with seniority, function, salary, remote flag and the tech
named in each posting. From $2 / 1,000 companies.

- **URL**: https://apify.com/eiv/company-jobs-scraper.md
- **Developed by:** [Eimantas V](https://apify.com/eiv) (community)
- **Categories:** Lead generation, Jobs, Other
- **Stats:** 4 total users, 3 monthly users, 43.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 job scrapes

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/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 Job Postings & Hiring Signals Scraper

Give it a list of company domains. Get back **every open role**, enriched with seniority, function, location, salary and the technologies named in each posting.

It reads companies' **official applicant-tracking system APIs** — Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Personio and Breezy — not LinkedIn or Indeed. Those endpoints exist so companies can embed their own vacancies on their own sites: they return clean JSON, they are public and unauthenticated, and they are not defended by bot management. That makes this fast, cheap and stable in a way that job-board scraping is not.

```
Input:   gitlab.com
Output:  190 open roles · 31 posted in the last 30 days · 52 remote
         Tooling in postings: Salesforce, Ruby, Kubernetes, PostgreSQL
         Hiring in United States, United Kingdom, India · velocity 90/100
```

***

### What you get

**Per job posting**

| Field | Detail |
|---|---|
| Identity | Stable `uid` (`<ats>:<token>:<jobId>`), company name and domain, ATS and board token |
| Role | Title, **normalised title** (seniority and location noise stripped, so the same role groups across companies), department, team |
| Classification | **Seniority** (intern → executive) and **function** (engineering, data, sales, marketing, product, design, security, finance, people, …) |
| Location | Location string, all secondary locations, parsed country and city, `isRemote`, `isHybrid` |
| Compensation | Parsed `min` / `max` / `currency` / `period`, including salaries stated only in the body text |
| Timing | `postedAt`, `updatedAt`, `daysOpen` |
| **Tech keywords** | Technologies and vendors named in the posting — see below |
| Links | Direct apply URL, description snippet, optional full text |

**Per company** (optional rollup record)

Open roles by function and seniority, top locations, top tooling, remote count, roles posted in the last 30 days, newest and oldest posting, and a **0–100 hiring velocity score**.

***

### Why the tech keywords matter

Every posting is scanned for ~90 technologies and vendors. That turns a job board into a **buying-intent feed**:

- A company hiring for **Snowflake + dbt + Airflow** is standing up a data warehouse *now*.
- A company hiring **Salesforce administrators** is expanding its CRM.
- A company hiring **Kubernetes + Terraform** engineers is re-platforming.

Real output from a single run:

```
gitlab.com   Salesforce(18), Ruby(8), Kubernetes(7), PostgreSQL(6), Vue.js(4)
ramp.com     Python(16), NetSuite(12), Salesforce(10), React(9), AWS(8)
figma.com    Python, Salesforce, Snowflake, NetSuite, dbt
```

Keywords that appear in nearly every posting on a large board are dropped as boilerplate — a company naming its own product in all 60 ads is not a signal.

***

### Who this is for

- **Outbound sales** — "companies that posted a Data Engineer role in the last 30 days" is a far better trigger than firmographics. Filter on `postedWithinDays`, `jobFunction` and `techKeywords`.
- **Recruiters and staffing** — live, structured vacancy data straight from the source, with direct apply links.
- **Competitive and market intelligence** — track where competitors are investing headcount, by function and country, week over week.
- **Investors** — headcount growth by function is one of the earliest public signals of a company's direction.
- **Data teams** — a fixed, documented schema with a stable primary key, so consecutive runs diff cleanly.

***

### Input

```json
{
  "companies": ["gitlab.com", "figma.com", "linear.app"],
  "titleKeywords": ["engineer", "data"],
  "postedWithinDays": 30,
  "maxJobsPerCompany": 200
}
```

Domains, careers URLs and direct board URLs all work. If you already know the board, skip discovery entirely:

```json
{ "boards": [{ "ats": "greenhouse", "token": "gitlab" }] }
```

**Chains off the Website Tech Stack & Lead Enrichment Scraper.** Point `sourceDatasetId` at its output and the `careersPageUrl` field is picked up automatically — enrich a lead list, then find who on it is hiring.

#### Key options

| Option | Default | Notes |
|---|---|---|
| `titleKeywords` / `locationKeywords` | — | Applied **before billing**, so you only pay for postings you keep |
| `postedWithinDays` | all | Undated postings are excluded, since they can't be shown to be recent |
| `remoteOnly` | `false` | |
| `maxJobsPerCompany` | `200` | Large employers can exceed 4,000 roles — this is your billing guard |
| `includeDescriptions` | `true` | Needed for `techKeywords` and body-text salaries; ~10× the bandwidth |
| `includeFullDescriptionText` | `false` | Off keeps the dataset small; a 300-char snippet is always included |
| `includeCompanySummary` | `true` | Rollup rows are **not** billed as jobs |
| `guessBoardToken` | `true` | Tries the domain name on the three biggest ATSs, verified against the live API |

***

### Supported systems

| ATS | Coverage |
|---|---|
| **Greenhouse** | Full board in one request, `meta.total` for board size |
| **Lever** | Full board, descriptions always included |
| **Ashby** | Full board, compensation included where published |
| **SmartRecruiters** | Paginated — handles boards of thousands of roles |
| **Workable** | Full board |
| **Recruitee**, **Personio** (XML), **Breezy HR** | Full board |

A company on an unsupported ATS, or with a bespoke careers page, returns `NO_BOARD_FOUND` — **and is not charged.**

***

### Output

One record per posting, `recordType: "job"`:

```json
{
  "uid": "greenhouse:gitlab:8503792002",
  "companyName": "GitLab", "companyDomain": "gitlab.com",
  "ats": "greenhouse", "atsToken": "gitlab",
  "title": "Senior Backend Engineer, Remote", "normalisedTitle": "Backend Engineer",
  "department": "Engineering",
  "location": "Remote, Italy", "country": "Italy", "isRemote": true,
  "seniority": "senior", "jobFunction": "engineering",
  "salary": { "min": 211400, "max": 290600, "currency": "USD", "period": "year" },
  "postedAt": "2026-05-16T00:00:00.000Z", "daysOpen": 86,
  "techKeywords": ["Ruby", "Ruby on Rails", "Kubernetes", "PostgreSQL"],
  "applyUrl": "https://job-boards.greenhouse.io/gitlab/jobs/8503792002",
  "recordType": "job"
}
```

Plus one `recordType: "company-summary"` per company. The dataset ships with three ready-made views — **Job postings**, **Tech & salary signals** and **Company hiring signals** — so you can export just the columns you need.

Set `flattenOutput: true` for CSV.

***

### Performance and honest limits

Measured on six real companies, descriptions on, 60-role cap:

| | |
|---|---|
| Wall clock | **3.2 s** for 6 companies (271 jobs) |
| Requests per company | 4.2 |
| CPU per company | ~290 ms |
| Boards resolved | 5 of 6 |

**Limits worth knowing:**

- **Coverage is ATS coverage.** Companies on Workday, Taleo, SAP SuccessFactors, Teamtailor or a hand-built careers page are not supported and return `NO_BOARD_FOUND`. Expect misses on large enterprises and on companies with no public board.
- **`postedAt` is whatever the ATS reports.** Some providers keep the original creation date across republication, so a role can show a large `daysOpen` while being actively advertised.
- **Descriptions drive bandwidth.** Greenhouse goes from 343 KB to 4.1 MB on a 561-role board when descriptions are on. Turn them off for a fast titles-only pass.
- **Salary is only reported when a currency is attached** to the numbers. A bare "120000 - 160000" is left null rather than guessed, because a wrong salary is worse than a missing one.

***

### Pricing

Pay per event. Companies with no discoverable board are **never charged**, and filters run before billing.

| Event | Price | When |
|---|---|---|
| Actor start | $0.005 | Once per run |
| Company resolved | $0.004 | Per company whose board is found and read, including companies with no open roles |
| Job scraped | $0.0008 | Per posting returned, after your filters |

**Worked example:** 500 companies averaging 15 kept roles = $2.00 + $6.00 = **$8.00**, under two cents per company. That is roughly **$0.80 per 1,000 job records** — well under what job-data APIs charge.

Filters run before billing, so a tight query costs less. The per-company fee still applies, because an entire board is downloaded whether you keep every posting or three of them.

***

### Tips

- **Schedule it weekly and diff on `uid`.** New UIDs are new roles; disappeared UIDs are filled or pulled. That diff is the actual sales trigger.
- **Filter hard.** `titleKeywords: ["data engineer"]` plus `postedWithinDays: 14` turns 50,000 postings into a short, timely call list — and cuts the bill proportionally.
- **Segment on `hiringVelocityScore`.** High score plus a small team means a company scaling fast.
- **Use `techKeywords` as intent.** Cross-reference against what you sell.

# Actor input Schema

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

Company domains or careers URLs, one per line. A direct job-board URL (boards.greenhouse.io/acme, jobs.lever.co/acme, jobs.ashbyhq.com/acme …) is used straight away and skips discovery.

## `boards` (type: `array`):

Skip discovery entirely by naming the ATS and board token: \[{"ats":"greenhouse","token":"gitlab"}]. The cheapest and most reliable input if you already know them. Valid ats values: greenhouse, lever, ashby, smartrecruiters, workable, recruitee, personio, breezy.

## `startUrls` (type: `array`):

Alternative to Companies, for feeding output from another Actor straight into this one.

## `sourceDatasetId` (type: `string`):

Read companies from an existing dataset. Works out of the box with the Website Tech Stack & Lead Enrichment Scraper — its careersPageUrl field is picked up automatically.

## `sourceDatasetField` (type: `string`):

Which field on the source dataset holds the domain or careers URL.

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

Keep only roles whose title contains any of these (case-insensitive). Leave empty for every role.

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

Keep only roles whose location, city or country contains any of these. E.g. Germany, Remote, London.

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

Keep only postings flagged as remote.

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

Keep only roles published in the last N days — the strongest freshness signal for outbound timing. Postings with no publication date are excluded, since they cannot be shown to be recent. Leave at 0 for all roles.

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

Hard cap per company. Large employers can have thousands of open roles (Bosch: 4,760), so this protects you from an unexpected bill. Records flag truncated:true when a board exceeded the cap.

## `includeDescriptions` (type: `boolean`):

Required for the techKeywords field and for salaries stated only in the body text. Costs roughly 10x the bandwidth — Greenhouse grows from 343 KB to 4.1 MB on a 561-role board. Turn off for a fast, cheap titles-and-locations pass.

## `includeFullDescriptionText` (type: `boolean`):

Write the whole description (up to 8,000 characters) into each record. Off by default because it multiplies dataset size; a 300-character snippet and the extracted keywords are always included.

## `includeCompanySummary` (type: `boolean`):

Add one rollup record per company (recordType: company-summary) with counts by function and seniority, top locations, top tooling, and a 0-100 hiring velocity score. Not billed as a job.

## `guessBoardToken` (type: `boolean`):

When no board is linked from the site, try the domain name as a token on Greenhouse, Lever and Ashby. Adds up to three cheap requests and is verified against the live API before being accepted.

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

Companies processed in parallel. Job-board responses can be several MB, so raise memory alongside this.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout. Some boards are slow to serialise thousands of postings.

## `maxRetries` (type: `integer`):

Retries for timeouts, connection resets and 5xx responses. ATS APIs are reliable, so 1 is usually enough.

## `flattenOutput` (type: `boolean`):

Emit flat dot-notation columns with arrays joined by ' | '. Use for spreadsheets; leave off for warehouses.

## `proxyConfiguration` (type: `object`):

Optional and usually unnecessary — ATS APIs are public and are not defended by bot management.

## Actor input object example

```json
{
  "companies": [
    "gitlab.com",
    "figma.com",
    "linear.app"
  ],
  "sourceDatasetField": "domain",
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 200,
  "includeDescriptions": true,
  "includeFullDescriptionText": false,
  "includeCompanySummary": true,
  "guessBoardToken": true,
  "maxConcurrency": 20,
  "requestTimeoutSecs": 30,
  "maxRetries": 1,
  "flattenOutput": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One dataset item per job posting. Company rollups are included as records with recordType 'company-summary'.

# 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 = {
    "companies": [
        "gitlab.com",
        "figma.com",
        "linear.app"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eiv/company-jobs-scraper").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 = { "companies": [
        "gitlab.com",
        "figma.com",
        "linear.app",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("eiv/company-jobs-scraper").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 '{
  "companies": [
    "gitlab.com",
    "figma.com",
    "linear.app"
  ]
}' |
apify call eiv/company-jobs-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/Rxfe8av85aTc1BZ9x/builds/hEK2KKqssyMJo62Gf/openapi.json
