# B2b Ats Open Jobs Tech Stack Scraper (`arezdev/b2b-ats-open-jobs-tech-stack-scraper`) Actor

Find open jobs, published salaries and hiring signals across Greenhouse, Lever, Ashby, Workable and SmartRecruiters. Start with company domains or ATS boards, filter roles and export structured job data for recruiting and B2B prospecting.

- **URL**: https://apify.com/arezdev/b2b-ats-open-jobs-tech-stack-scraper.md
- **Developed by:** [Arezdev](https://apify.com/arezdev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 job posting extracteds

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

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Turn company career pages into open jobs, salary ranges, and technology buying signals in one structured dataset.**

Monitor Greenhouse, Lever, Ashby, Workable, and SmartRecruiters using public HTTP APIs. Start with company domains or known ATS boards, filter for departments or remote roles, and export results directly into your prospecting and automation workflows.

The Actor scans full job descriptions for more than 120 enterprise technology signatures, including Rust, Go, TypeScript, Python, Kubernetes, Terraform, AWS, GCP, Azure, Snowflake, Databricks, Kafka, Postgres, React, Next.js, Salesforce, HubSpot, NetSuite, Workday, and SOC2. A mention is a hiring signal, not proof of a product purchase or deployment.

Best for recruiting research, hiring signals and account segmentation. Each successful row is an open job; technology mentions describe hiring requirements, not confirmed software purchases. Use a direct ATS board URL for the most predictable first run.

### Ready-to-run example

The Input form includes public example values. A fresh `apify run` uses the input schema defaults. To explicitly select the small sample, including when input was saved by an earlier local run, use:

```sh
apify run --input-file .actor/input.example.json
```

You can also paste [.actor/input.example.json](.actor/input.example.json) into the JSON input editor in Apify Console. Replace the example sources with your own targets and increase the limits when you are ready.

### How to use

1. Add company domains, direct ATS board URLs, or provider identifiers to **Company domains or ATS boards**.
2. Optionally select a department substring, remote roles only, and the maximum number of matching jobs per company.
3. Start the run. Review the **Open jobs** table, then export JSON, CSV, Excel, or retrieve items through the dataset API.

| Example target | Behavior |
| --- | --- |
| `https://boards.greenhouse.io/stripe` | Read the Stripe Greenhouse board |
| `https://jobs.ashbyhq.com/linear` | Read the Linear Ashby board |
| `lever:palantir` | Read a known Lever board |
| `workable:huggingface` | Read a known Workable board |
| `smartrecruiters:BoschGroup` | Read a known SmartRecruiters board |
| `https://linear.app` | Discover the company's ATS from its website |

Corporate discovery checks `/careers`, `/jobs`, and the supplied page. It recognizes links, embedded boards, widget attributes, and embedded ATS URLs. If no link is found, it probes the supported providers using the domain's company name. Explicit board URLs are best for companies whose ATS slugs differ from their domains.

Use [examples/input.json](examples/input.json) for a two-row quick run:

```json
{
  "targets": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.ashbyhq.com/linear"
  ],
  "maxJobsPerCompany": 1,
  "maxConcurrency": 20,
  "timeoutSecs": 15
}
```

The Console prefill includes two boards with three jobs per company, for up to six rows. The schema default is also three jobs per company. Increase `maxJobsPerCompany` for larger runs. Public API latency and availability determine run time; a particular company's availability or a sub-five-second run cannot be guaranteed.

### What data can you extract?

Every row has the same flat structure. Missing public fields are empty strings; `detectedTechStack` is an array and `isRemote` is a boolean.

| Field | Meaning |
| --- | --- |
| `companySlug` | ATS board slug, or public target identifier on discovery failure |
| `atsPlatform` | `greenhouse`, `lever`, `ashby`, `workable`, or `smartrecruiters` |
| `status` | `SUCCESS` or `ERROR` |
| `jobId` | Provider identifier, with a stable derived fallback when omitted |
| `jobTitle` | Published position title |
| `department` | Public department or team labels |
| `location` | Published location |
| `isRemote` | Whether the job is explicitly remote |
| `workplaceType` | `Remote`, `Hybrid`, `OnSite`, or empty when unknown |
| `employmentType` | Provider's employment label |
| `publishedAt` | Public publication date in UTC ISO 8601, when supplied |
| `salaryRangeText` | Structured compensation or a salary range from the description |
| `detectedTechStack` | Distinct technology mentions from the full description |
| `descriptionSnippet` | Plain-text description prefix, up to 1500 characters |
| `applyUrl` | Public application or job URL |
| `scrapedAt` | UTC extraction timestamp |
| `errorMessage` | Safe diagnostic text and code for errors |

The Actor preserves disclosed currency and compensation periods and does not estimate missing salaries. Lever exposes a creation timestamp, which is used for `publishedAt`. Explicit hybrid and on-site labels take precedence over remote location wording. Descriptions are not used to infer remote eligibility.

### Input settings

| Setting | Default | Accepted values |
| --- | --- | --- |
| `targets` | Required | 1 to 10000 public domains, board URLs, or provider identifiers |
| `departmentFilter` | Empty | Case-insensitive literal department substring, up to 200 characters |
| `onlyRemote` | `false` | Include explicitly remote jobs; exclude hybrid and unknown workplaces |
| `maxJobsPerCompany` | `3` | Integer from 1 to 1000; Console prefill is 3 |
| `maxConcurrency` | `20` | Integer from 1 to 50, shared by every HTTP request |
| `timeoutSecs` | `15` | Integer from 3 to 60 per request attempt |
| `proxyConfiguration` | Direct first | Optional Apify proxy editor configuration |

Filters run before the matching-job limit, and pagination continues through non-matching jobs. Duplicate targets and company boards are consolidated within a run. Error rows do not use the successful-job allowance, but both row types count toward the free-plan cap.

Omit proxy settings for direct requests with datacenter fallback after HTTP 403, 429, or 503. The Actor tries SHADER, then the account's default datacenter proxy. A configured final tier is used only with explicit `useApifyProxy: true`. Custom `proxyUrls` force those proxies; `useApifyProxy: false` forces direct connections. Automatic Apify tiers require platform proxy configuration. Local runs use direct requests unless custom proxies are supplied.

### Output example

This illustrative row shows the output shape:

```json
{
  "companySlug": "example",
  "atsPlatform": "ashby",
  "status": "SUCCESS",
  "jobId": "engineering-platform-2026",
  "jobTitle": "Senior Platform Engineer",
  "department": "Engineering",
  "location": "Remote - United States",
  "isRemote": true,
  "workplaceType": "Remote",
  "employmentType": "FullTime",
  "publishedAt": "2026-10-01T12:00:00.000Z",
  "salaryRangeText": "$150,000 - $200,000 per year",
  "detectedTechStack": ["AWS", "Kubernetes", "Python", "Terraform"],
  "descriptionSnippet": "Build our platform with Python, Kubernetes, Terraform and AWS. Salary: $150,000 - $200,000 per year.",
  "applyUrl": "https://jobs.ashbyhq.com/example/engineering-platform-2026/apply",
  "scrapedAt": "2026-10-19T12:00:00.000Z",
  "errorMessage": ""
}
```

Failed targets produce `ERROR` rows with a public identifier and a diagnostic such as `HTTP_404`, `ETIMEDOUT`, or `ATS_NOT_FOUND`. Valid boards with no matching jobs produce zero job rows. Totals and the stopping reason are saved as `RUN-SUMMARY` in the default key-value store.

### Use the results in Clay, n8n, Make, or your API

In Clay, import the dataset or CSV, group by `companySlug`, and use technology mentions and department hiring as enrichment signals. Select `status = SUCCESS` before building prospect segments.

In n8n or Make, use the Apify integration to run a saved task, wait for completion, and retrieve dataset items. Compare `companySlug`, `atsPlatform`, and `jobId` with your previous run to identify new or removed postings. Schedule recurring monitoring in Apify Console.

For API workflows, obtain the completed run's `defaultDatasetId` and call the [dataset items endpoint](https://docs.apify.com/api/v2/dataset-items-get), selecting JSON or CSV with `format`. Authenticate through the integration's connection or authorization header. Keep credentials out of target URLs and exported data.

### Pricing and limits

The custom pay-per-event name is **`job-scraped`**. Only saved `SUCCESS` rows are submitted with this event. **`ERROR` rows cost $0 in custom event fees.** Platform compute, storage, transfer, and any proxy charges can still apply.

The price is configured in Apify Store settings. For example, at **$0.001 per successful job**, 1000 successful jobs cost **$1.00** in event fees; an additional 100 error rows add **$0.00** in custom event fees. This calculation is illustrative; check the Store's displayed price before running.

On the platform, non-paying users can save **25 total rows per run**, including errors. Paid users and local runs do not have that cap. The writer reserves the remaining custom-event budget across buffered results. Reaching either limit stops new work and exits successfully. Results flush at 100 rows, every five seconds, and during shutdown.

The container defaults to 256 MB and a one-hour timeout. Decompressed responses are capped at 2 MiB for JSON, 4 MiB for HTML, and 15 MiB for XML. Small Greenhouse runs use lean listings and selected details; oversized full feeds fall back to that approach. Lever uses adaptive pagination, Workable uses cursor tokens, and SmartRecruiters uses offsets and detail requests. A board is limited to 50000 scanned postings.

### FAQ and troubleshooting

**Why is salary or publication date empty?** The provider may not publish it. Only disclosed salary values or recognizable ranges are extracted. Updated timestamps are not substituted for publication dates.

**Why did a domain return `ATS_NOT_FOUND`?** The site may require JavaScript, use another ATS, or use an unrelated slug. Supply a direct supported board URL. Discovery does not run a browser.

**Can this extract private jobs?** It reads public boards and descriptions. It does not log in, submit applications, or access recruiting records.

**Why are fewer jobs returned?** Filters, the free-plan cap, the event budget, or current openings can reduce the count. Failed details appear as uncharged errors. Empty boards do not produce synthetic successful records.

**What happens on provider errors?** Timeouts, TLS failures, blocked responses, and malformed payloads become error records so other companies can finish. Invalid input fails validation. An unavailable platform or output-storage service stops the run safely with a status message; output delivery cannot be guaranteed during a storage outage.

**How are requests protected?** Every destination and redirect is checked against public DNS addresses. Direct requests retain the original hostname while pinning validated IPs. Proxy tunnels connect to validated public IPs with the origin's TLS server name. Certificate verification remains enabled. Unsafe links, excessive nesting, prototype-pollution keys, and spreadsheet formulas are handled before writes.

**Which APIs are used?** The adapters use the [Greenhouse Job Board API](https://developers.greenhouse.io/job-board.html), [Lever Postings API](https://github.com/lever/postings-api), Ashby's hosted GraphQL API with its [documented public posting API](https://developers.ashbyhq.com/docs/public-job-posting-api) for enrichment and fallback, Workable's public board endpoints, and the [SmartRecruiters Posting API](https://developers.smartrecruiters.com/docs/endpoints). Workable and SmartRecruiters detail calls supply descriptions absent from list responses.

### Local development

Use Node.js 22 or newer:

```sh
npm ci --ignore-scripts
npm run build
npm test
npm run smoke
npm run smoke -- --all
```

Unit tests make no remote requests. Smoke tests make live read-only requests, requesting one job per board. `--all` adds Lever, Workable, and SmartRecruiters checks. Test and smoke subprocesses enable `UV_THREADPOOL_SIZE=64`, production mode, and strict TLS verification.

To run the complete Actor locally, save the example as `storage/key_value_stores/default/INPUT.json`, then run `npm start`. With Apify CLI, use `apify run --input-file examples/input.json`. See [DEPLOYMENT.md](DEPLOYMENT.md) for container and Store setup.

Repository initialized by Arezgit.

# Actor input Schema

## `targets` (type: `array`):

Direct board URLs, provider:slug identifiers (greenhouse, lever, ashby, workable, smartrecruiters), or public company domains. Up to 10000 targets.

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

Optional case-insensitive literal substring matched against the department, for example Engineering.

## `onlyRemote` (type: `boolean`):

Include jobs explicitly marked remote; hybrid jobs are excluded.

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

Limit applied after department and remote filters. Use 1 for the fastest two-target sample.

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

Global request limit shared by discovery and all provider adapters. Lower it if sites return rate limits.

## `timeoutSecs` (type: `integer`):

Total timeout for each HTTP attempt, including connection establishment and response streaming.

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

Omit for direct-first requests with automatic datacenter fallback. Explicitly enable Apify Proxy for a final configured tier, set useApifyProxy false for direct only, or supply custom proxyUrls to force your proxies.

## Actor input object example

```json
{
  "targets": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.ashbyhq.com/linear"
  ],
  "onlyRemote": false,
  "maxJobsPerCompany": 3,
  "maxConcurrency": 20,
  "timeoutSecs": 15
}
```

# Actor output Schema

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

Default dataset items. Review each record status before using extracted data downstream.

# 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 = {
    "targets": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.ashbyhq.com/linear"
    ],
    "maxJobsPerCompany": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("arezdev/b2b-ats-open-jobs-tech-stack-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 = {
    "targets": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.ashbyhq.com/linear",
    ],
    "maxJobsPerCompany": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("arezdev/b2b-ats-open-jobs-tech-stack-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 '{
  "targets": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.ashbyhq.com/linear"
  ],
  "maxJobsPerCompany": 3
}' |
apify call arezdev/b2b-ats-open-jobs-tech-stack-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arezdev/b2b-ats-open-jobs-tech-stack-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/YXAw0zsCusFHjUQv0/builds/ruDxZpXEdegDPoKc5/openapi.json
