# Vee Jobs - Greenhouse, Lever, Ashby, Workable Jobs Scraper (`veelabs/vee-jobs`) Actor

Every open job from Greenhouse, Lever, Ashby and Workable career pages in one clean schema. Paste a URL or a company slug. Filter by title, location, remote and date. Emails and phones are redacted by default. $1.80 per 1,000 jobs.

- **URL**: https://apify.com/veelabs/vee-jobs.md
- **Developed by:** [Hoxei R\&D](https://apify.com/veelabs) (community)
- **Categories:** Jobs, Lead generation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## Vee Jobs

**Every open job from Greenhouse, Lever, Ashby and Workable career pages, in one consistent dataset.**

Paste a careers URL or just a company name. Vee Jobs works out which platform the company uses, pulls all open roles, and returns them in the same fields no matter where they came from.

### Who it is for

- Job boards and aggregators that need a steady feed of direct-from-employer listings
- Recruiters and sourcers tracking which companies are hiring for which roles
- Sales and market-intelligence teams using hiring as a buying signal ("who is hiring data engineers in Berlin")
- Researchers studying the labour market

### What you get: every open job as one clean table (CSV, JSON, Excel)

One row per job, with the same fields for all four platforms:

```json
{
  "platform": "ashby",
  "companySlug": "openai",
  "jobId": "…",
  "title": "Research Engineer",
  "company": "openai",
  "location": "San Francisco",
  "department": "Research",
  "employmentType": "FullTime",
  "remote": false,
  "workplaceType": "onsite",
  "url": "https://jobs.ashbyhq.com/openai/…",
  "postedAt": "2026-09-30T…",
  "updatedAt": null,
  "compensation": "$257K – $335K • Offers Equity",
  "descriptionHtml": "…",
  "descriptionText": "…",
  "scrapedAt": "2026-10-14T17:13:57.104Z"
}
```

`compensation` is readable text (for example `USD 70,000-110,000 per year`) filled when the company publishes it on the job board. `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryInterval` hold the same range as numbers when the platform provides them (Lever, Ashby, and Greenhouse boards with pay transparency). `remote` is true only for fully remote jobs, `workplaceType` is `remote`, `hybrid` or `onsite` when the platform says so, and `allLocations` lists every location of multi-location postings. `applyUrl` is the direct apply link. `employmentType` and `department` are filled when the platform provides them, otherwise `null`.

A `SUMMARY` record in the key-value store lists, for every company you entered, the platform found, the number of open jobs, how many matched your filters, and the reason for any company that was skipped.

### How it works

Vee Jobs calls the public job-board APIs that Greenhouse, Lever, Ashby and Workable provide for embedding listings on career sites. It does not open a browser, use proxies or parse HTML pages. Runs need little compute, and the output does not break when a company redesigns its careers page.

### How it is different

- **One schema for four platforms.** Fields are normalised, so you do not write four parsers.
- **Enter a name, not a URL.** A bare slug such as `notion` is tried against each platform in turn (up to four requests per slug).
- **Email and phone redaction on by default.** Email addresses (including `%40` and `&#64;` forms) and phone numbers in job descriptions, titles, locations and other text fields (and in job links) are replaced with `[email removed]` and `[phone removed]`. Turn it off with `redactContacts`.
- **Clear reporting.** A company that is not found is listed in `SUMMARY` and skipped, and the run continues. Access-denied responses (401, 403, 451) are reported as errors and not mislabelled "not found".
- **Hard limits on spend.** `maxJobs` caps each company, `maxTotalJobs` caps the whole run, and the run stops at `maxRuntimeSecs`. Charging stops if you set a maximum cost on the run.
- **Fixed, trusted hosts only.** Requests go only to the four platforms' API hosts. Company input is reduced to a validated board slug, so nothing else can be requested.

### If something looks wrong

- **"No job board found"**: the slug is misspelled or the company uses another system. Paste the full careers page URL, or give `{ "slug": "acme", "platform": "lever" }`. A bare slug that exists on several platforms returns the first one that has open jobs (Greenhouse, Lever, Ashby, Workable order).
- **0 jobs for a company that exists**: the board has no open jobs right now, or your filters (keywords, locations, remoteOnly, postedWithinDays) matched nothing. The reason is written to `SUMMARY`.
- The same board listed twice (even as a slug and as a URL) is returned once and not billed twice.
- **A posting with broken data** (for example a null entry) is skipped and counted in `SUMMARY` as `skippedPostings`. The other jobs of that board are still returned.

### Input examples

Three companies, all open roles:

```json
{
  "companies": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.ashbyhq.com/openai",
    "https://jobs.lever.co/spotify"
  ]
}
```

Remote data roles posted in the last 14 days, without descriptions:

```json
{
  "companies": ["notion", "stripe", "outreach"],
  "keywords": ["data", "analytics"],
  "remoteOnly": true,
  "postedWithinDays": 14,
  "includeDescription": false
}
```

Berlin roles only:

```json
{
  "companies": ["https://apply.workable.com/huggingface/", "spotify"],
  "locations": ["Berlin"]
}
```

| Field | What it does |
|---|---|
| `companies` | Career page URLs or bare slugs (max 500) |
| `keywords` | Keep jobs whose title contains any of these |
| `locations` | Keep jobs whose location contains any of these |
| `remoteOnly` | Only remote jobs |
| `postedWithinDays` | Only jobs posted in the last N days (0 = any time) |
| `includeDescription` | Include HTML and plain-text descriptions (default on) |
| `redactContacts` | Redact emails and phone numbers (default on) |
| `sanitizeHtml` | Keep only basic formatting tags (paragraphs, lists, headings, bold, italic, links, tables) in descriptions. Script, style, iframe, form and image tags and all attributes are removed, and only `http(s)` links are kept (default on) |
| `maxJobs` | Cap per company (default 5,000) |
| `maxTotalJobs` | Cap for the whole run (default 100,000) |
| `maxRuntimeSecs` | Stop after this many seconds (default 3,600) |
| `contact` | Optional URL or email added to the User-Agent |

### What it costs

Pay per event. You pay only for results, and the price is the same on every Apify plan.

| Event | Price |
|---|---|
| Job returned | $1.80 per 1,000 jobs ($0.0018 each) |
| Run start | $0.005 per run |

Platform usage (compute) is included in these prices, so there is no separate bill for it. The pricing table at the top of the Actor page is Apify's standard layout and lists every plan (Free, Starter, Scale, Business). Our price is the same in every column, so you never pay more or less because of your plan.

Examples, each for one run:

| You get | Results | Run start | Total |
|---|---|---|---|
| 100 jobs | $0.18 | $0.185 |
| 1,000 jobs | $1.80 | $1.805 |
| 10,000 jobs | $18.00 | $18.005 |

Filters and caps (limits such as the per-run result caps and your input filters) only reduce the number of results you pay for. Set a maximum cost on the run in Apify if you want a hard spending limit.

### FAQ

**Which platforms are supported?** Greenhouse, Lever, Ashby and Workable.

**Can I give a company name instead of a URL?** Yes, give the board slug (the part after the platform's domain). The platform is detected automatically.

**Does it return salary?** When the company publishes it on the job board. Many do not, and the field is `null` then.

**Why is a company missing from my results?** Check `SUMMARY`. It states whether the board was not found, access was denied, or no jobs matched your filters.

**Do the filters change what I pay?** Yes, in your favour. You are charged only for jobs that pass your filters and are returned.

**Can I render `descriptionHtml` on my site?** Run it through your own HTML sanitizer first. By default (`sanitizeHtml` on) Vee Jobs keeps only basic formatting tags, removes script, style, iframe, form and image tags and every attribute, and keeps only `http(s)` links (with `rel="nofollow noopener"`), but the text is still third-party content. Turn `sanitizeHtml` off if you need the raw HTML exactly as published. `descriptionText` is plain text.

**How fresh is the data?** Each run reads the live job board, so you get what the company shows at that moment.

**Can I schedule it?** Yes, use Apify schedules and compare runs by `jobId` to find new roles.

### Limits

- `companies`: up to 500. `keywords` and `locations`: up to 200 each.
- `maxJobs` per company up to 100,000. `maxTotalJobs` up to 1,000,000.
- Each API response is capped at 50 MB. Requests are spaced 300 ms apart, and 429 and 5xx errors are retried with backoff, honouring `Retry-After`.
- With sanitising on (the default), each description is cut at 200,000 characters, even when redaction is off. With redaction on, each description is limited to 50,000 characters. `descriptionTruncated: true` marks longer ones.
- Redaction catches standard email and phone formats. It does not detect names or spelled-out forms such as "jane at example dot com". Long digit strings of 8 to 11 digits (for example a salary written as `80000000`) are redacted as phone numbers.
- If a description contains an unterminated HTML comment (`<!--` without `-->`), the rest of that description is dropped and `descriptionTruncated` is `true`.
- `url` and `applyUrl` only ever contain `http(s)` links of up to 2,048 characters. Anything else (`javascript:`, `data:`, `mailto:`) becomes `null`. `salaryCurrency` is a three-letter code or `null`.
- **Spreadsheets:** text fields that a spreadsheet would read as a formula (starting with `=` or `@`, or with `+` / `-` that is not followed by a digit) get a leading apostrophe (for example `'=SUM(A1)`), so opening the CSV or Excel export cannot run them. Normal values such as `+65 6123 4567` or `-5% off` are untouched.

### Responsible use, legal and data protection

This Actor retrieves publicly available data only. It is not affiliated with, endorsed by or sponsored by Greenhouse, Lever, Ashby, Workable or any employer whose jobs you retrieve. All trademarks belong to their owners and are used only to describe compatibility.

Vee provides tools only; you are fully responsible for how you use them and the results. The tool is provided as is, without warranty, and Vee gives no legal advice. You are fully responsible for your use of the data: comply with the terms of service, robots.txt, copyright and database rights of the sites you target, and with all applicable law, including data protection laws (GDPR, UK GDPR and similar). The output includes personal data whenever a job post names a person, for example a recruiter or hiring manager. Have a lawful basis before you store or reuse any personal data, minimise it and delete it when no longer needed, and do not use it for unsolicited contact. Do not republish copyrighted text or images without permission. Vee does not collect or keep your inputs or results; they stay in your Apify account. Vee reserves the right to change or withdraw the Actor if a site owner objects. To raise a concern, open an issue on this Actor's Issues tab on Apify.

### Support

Questions, a missing field or a feature request? Open an issue on this Actor's Issues tab on Apify and we will reply there.

# Actor input Schema

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

Career page URLs (boards.greenhouse.io/x, jobs.lever.co/x, jobs.ashbyhq.com/x, apply.workable.com/x) or bare board slugs (platform auto-detected). The same board listed twice is only returned once.

## `keywords` (type: `array`):

Keep jobs whose title contains any of these.

## `locations` (type: `array`):

Keep jobs whose location (or any of their locations) contains any of these (e.g. Berlin, Remote).

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

Only fully remote jobs (hybrid roles are excluded).

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

0 = any time.

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

Include HTML and plain-text descriptions.

## `redactContacts` (type: `boolean`):

Replace email addresses and phone numbers found in job descriptions, titles, locations and other text fields with placeholders (recommended, protects personal data).

## `sanitizeHtml` (type: `boolean`):

Keep only basic formatting tags (paragraphs, lists, headings, bold, italic, links, tables) in descriptionHtml, remove script, style, iframe, form and image tags and all attributes, and keep only http(s) links. Tag-like markup is removed from descriptionText. With this on, descriptions are cut at 200,000 characters even when redaction is off. Turn off to receive the raw text exactly as published.

## `maxJobs` (type: `integer`):

Cap applied to EACH company board separately.

## `maxTotalJobs` (type: `integer`):

Overall cap across all companies in one run.

## `maxRuntimeSecs` (type: `integer`):

Stop gracefully after this many seconds.

## `contact` (type: `string`):

Optional. Added to the User-Agent so job-board operators can reach you.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/discord",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/linear"
  ],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": true,
  "redactContacts": true,
  "sanitizeHtml": true,
  "maxJobs": 5000,
  "maxTotalJobs": 100000,
  "maxRuntimeSecs": 3600
}
```

# Actor output Schema

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

Open jobs from Greenhouse, Lever, Ashby and Workable career pages in the default dataset, one row per job, ready to export as CSV, JSON or Excel.

## `summary` (type: `string`):

SUMMARY record with run statistics and any inputs that were skipped.

# 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": [
        "https://boards.greenhouse.io/discord",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/linear"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("veelabs/vee-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 = { "companies": [
        "https://boards.greenhouse.io/discord",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/linear",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("veelabs/vee-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 '{
  "companies": [
    "https://boards.greenhouse.io/discord",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/linear"
  ]
}' |
apify call veelabs/vee-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,veelabs/vee-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/e5KtfwUhbqwSRTHtR/builds/a0eFhbTn27XONdF0k/openapi.json
