# Wellfound (AngelList) Jobs Scraper & Job Alerts — Salary/Equity (`lukehunter/wellfound-jobs-scraper`) Actor

Get the newest Wellfound (AngelList) startup jobs for a role or location: title, company, salary range, equity, remote policy, size and stage. Turn on job alerts and run on a schedule to get only new listings each time, billed once each.

- **URL**: https://apify.com/lukehunter/wellfound-jobs-scraper.md
- **Developed by:** [Luke Hunter](https://apify.com/lukehunter) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 jobs

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

## Wellfound (AngelList) Jobs Scraper & Job Alerts — Salary, Equity & Company Data

**For recruiters, job boards and VC/startup researchers who need structured startup job data — salary, equity, company size and one-line pitch — without clicking through Wellfound (formerly AngelList Talent) one listing at a time.** Search by role and/or location and get one row per job from Wellfound's **first results page** (the newest listings, typically about 20 per role or location), with its startup already attached. Turn on **job alerts** mode (`onlyNewSinceLastRun`) and run it on a schedule to get only newly posted jobs each time. Best used as a daily or weekly **new startup jobs** check across several roles or cities, not as a full archive of every Wellfound job.

Pay-per-result: **$0.002 per job — 1,000 jobs = $2.**

Built for people who want a **Wellfound scraper** / **AngelList jobs scraper** that returns clean, structured data — not a screen-scraping browser session.

### Quick start (under a minute)

1. Open the **Input** tab (no Wellfound account needed).
2. Use this prefill, or add your own roles/locations:

```json
{
  "roles": ["software-engineer"],
  "maxItems": 20
}
```

3. Click **Start**. Open the **Jobs** dataset view and export as CSV, Excel or JSON.

### What you get

One flat row per job listing, with:

- **The job**: title, URL, employment type, remote policy, years of experience requested, when it went live.
- **The pay**: salary range and currency, equity range — parsed from Wellfound's own compensation string (see Limitations).
- **The startup**: name, size band, funding stage badge (e.g. "Early Stage", "Growth Stage"), and its own one-line pitch.

### Use cases

- **Recruiters and sourcers** building a live view of who's hiring for a role, with salary/equity visible before opening a single listing.
- **Job boards and aggregators** pulling structured startup listings instead of maintaining their own Wellfound parser.
- **VC and startup researchers** tracking which companies in a space are actively hiring, at what stage, and at what comp.

### Job alerts: get only new jobs

Turn on `onlyNewSinceLastRun` and put this Actor on a **schedule** to get a genuine **Wellfound job alert**: every run after the first delivers (and charges for) only jobs it hasn't delivered before for that exact search. It's the same wedge as [Zillow Price Drop Monitor](https://apify.com/lukehunter/zillow-price-drop-monitor)'s change-event model, applied to job search.

1. Set your roles/locations/filters as usual, plus:

```json
{
  "roles": ["software-engineer"],
  "locations": ["san-francisco"],
  "onlyNewSinceLastRun": true,
  "maxItems": 100
}
```

2. Click **Schedule** on the run page (or create one under **Schedules** in the Apify Console) — daily works well for an active role.
3. **The first scheduled run is a baseline**: it delivers every job currently listed (it's all new to you) and remembers it — you're charged normally for that run. Every run after that only delivers jobs it hasn't seen before for this exact search.
4. Add a **Webhook** under the schedule's **Integrations** for `ACTOR.RUN.SUCCEEDED`, pointing at:
   - **Slack**: Apify's own Slack integration (or a Zapier/Make webhook step) posting each run's new dataset items to a channel.
   - **Email**: a Zapier/Make "on webhook, send email" step, or Apify's own email integration, summarising that run's new jobs.
   - **Google Sheets**: the Apify-to-Google-Sheets integration, appending each run's rows to a sheet you watch.

The run's status message tells you what happened: `"First run: 20 job(s) delivered and remembered; next runs return only new ones."` on the baseline, then `"3 new job(s) since last run (17 already seen)."` on later runs. A run that finds nothing new still **succeeds** with an empty dataset — that's the point of the mode, not a failure.

Each distinct combination of `roles`/`locations`/`remoteOnly`/`minSalary`/`keywords` is tracked as its own watch, so you can run several alert schedules (e.g. one per role) without them mixing up each other's history. `stateStoreName` only needs changing if you want to reset a watch's memory or explicitly isolate it.

### Input

```json
{
  "roles": ["software engineer"],
  "locations": ["san francisco"],
  "remoteOnly": false,
  "minSalary": 0,
  "keywords": [],
  "maxItems": 50,
  "onlyNewSinceLastRun": false,
  "stateStoreName": "wellfound-jobs-scraper-seen-jobs"
}
```

| Field | Type | Default | Description |
|---|---|---:|---|
| `roles` | string\[] | — | Job roles, free text ("software engineer") or a slug ("software-engineer"). At least one role or location is required. Max 10. |
| `locations` | string\[] | — | Locations, free text ("san francisco") or a slug. Max 10. |
| `remoteOnly` | boolean | `false` | For roles, uses Wellfound's dedicated remote-jobs page instead of the general role page. For locations, keeps only jobs Wellfound itself marks as remote. |
| `minSalary` | integer | `0` | Drop jobs whose parsed salary range doesn't reach this (USD/year). A job with no parseable salary is dropped when this is set — see Limitations. |
| `keywords` | string\[] | `[]` | Keep only jobs whose title or description contains at least one of these (case-insensitive). |
| `maxItems` | integer | `50` | 1–2000. Hard cap on jobs delivered across the whole run. You're charged per delivered job, so this is your cost cap. |
| `onlyNewSinceLastRun` | boolean | `false` | Job alerts mode — see above. Delivers and charges only jobs not delivered by a previous run of the same search. |
| `stateStoreName` | string | `wellfound-jobs-scraper-seen-jobs` | Name of the store that remembers what a job-alerts search has already delivered. Change it only to isolate or reset a watch. |

At least one role or location is required. Give both to search more broadly in one run — results are de-duplicated by job ID.

### Example output (one row per job)

A representative delivered row:

```json
{
  "jobId": "1234567",
  "title": "Senior Software Engineer",
  "url": "https://wellfound.com/jobs/1234567-senior-software-engineer",
  "companyName": "Example Labs",
  "companySlug": "example-labs",
  "companyStage": "Early Stage",
  "locations": ["San Francisco", "Remote"],
  "remote": true,
  "remotePolicy": "REMOTE",
  "jobType": "full-time",
  "salaryMin": 150000,
  "salaryMax": 200000,
  "salaryCurrency": "USD",
  "equityMin": 0.05,
  "equityMax": 0.15,
  "role": "software-engineer",
  "sourceUrl": "https://wellfound.com/role/software-engineer",
  "scrapedAt": "2026-09-28T00:00:00.000Z"
}
```

| Field | Description |
|---|---|
| `jobId` | Wellfound's stable job listing ID. |
| `title`, `url` | Job title, and a constructed link to the listing. |
| `companyName`, `companySlug`, `companyUrl` | The startup, and a constructed link to its company page. |
| `companySize` | Wellfound's size band code, e.g. `SIZE_11_50`. |
| `companyStage` | Wellfound's own stage label, e.g. "Early Stage", "Growth Stage" — `null` when Wellfound doesn't show one. |
| `highConcept` | The startup's own one-line pitch. |
| `locations` | Location name(s) shown on the listing. |
| `remote`, `remotePolicy` | Wellfound's remote flag, and category (`REMOTE`/`ONSITE`/`ONSITE_OR_REMOTE`). |
| `jobType` | Employment type, e.g. `full-time`. |
| `salaryMin`, `salaryMax`, `salaryCurrency` | **Derived** from Wellfound's compensation string. |
| `equityMin`, `equityMax` | **Derived** equity percentage range. `0`/`0` for "No equity"; `null`/`null` when equity isn't mentioned. |
| `compensationRaw` | Wellfound's own compensation string, unparsed — check the derived fields against it. |
| `yearsExperienceMin`, `yearsExperienceMax` | Years of experience requested, as published. |
| `postedAt`, `liveStartAt` | `postedAt` is **derived** (ISO 8601) from `liveStartAt`, Wellfound's own raw Unix-seconds timestamp. |
| `role`, `sourceUrl` | Which requested role this job came from (if any), and the exact URL fetched. |
| `scrapedAt` | ISO 8601 UTC timestamp of the run. |

Every field is `null` when Wellfound doesn't publish it. Nothing is guessed.

### Limitations — please read

- **One page per role/location.** Every captured page we checked shows exactly one page of results, and none links to a page 2. This Actor does not guess at pagination it can't verify works — see `ENGINEERING-NOTES.md` for the full reasoning. Each role/location search returns Wellfound's own first page (roughly 20–55 jobs in what we captured), not the full total Wellfound reports (`totalJobCount` can run into the thousands for a popular role). Narrow with more specific roles/locations, or run more of them, to cover more ground.
- **Compensation is free text.** Wellfound doesn't expose salary/equity as separate numeric fields — they're parsed from a string like `"$120k – $200k • 0.01% – 0.15%"`. When that string doesn't match the expected shape (e.g. "Competitive", or blank), the salary/equity fields are `null` rather than guessed. `compensationRaw` is always included so you can check.
- **`minSalary` excludes unknowns.** A job with no parseable salary can't be confirmed to meet your threshold, so it's dropped when `minSalary` is set — it is not assumed to pass.
- **No company pages, ever.** This Actor never fetches `/company/<slug>` pages (they returned an anti-bot challenge/403 for us) — `companyUrl` is constructed for your convenience, not scraped from a page we visited.
- **No personal data.** Nothing about recruiters or individual employees is collected — only company and job fields.
- **Polite by design.** One request per role/location, at least 3 seconds apart, one honest User-Agent, no proxies, no login, no CAPTCHA-solving. If Wellfound blocks a request, that target is reported as failed (and not charged) rather than retried aggressively.

### Pricing

Pay-per-event: **$0.002 per delivered job** — you're only charged for jobs that actually land in your dataset. 1,000 jobs = $2. A run that returns nothing is never charged.

### FAQ

**Does this need a Wellfound account?** No — it reads Wellfound's own public role and location pages.

**Can I get every job for a role?** Not yet — see Limitations above on pagination. You'll get Wellfound's first page of results per role/location you search.

**Why is `salaryMin` sometimes null even though the job has a salary?** Either Wellfound didn't publish a compensation string for that listing, or it used a format outside the range shape this Actor parses (rare — check `compensationRaw`).

**How do I get a Wellfound job alert instead of the same jobs every time?** Set `onlyNewSinceLastRun: true` and put the Actor on a schedule — see "Job alerts" above. The first scheduled run delivers everything (and remembers it); later runs only deliver what's new.

### Related Actors

Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.

- **[Remote Jobs Aggregator](https://apify.com/lukehunter/remote-jobs-aggregator)**: remote job listings from RemoteOK, Himalayas, Jobicy and We Work Remotely in one deduplicated feed.
- **[Google Play App Scraper](https://apify.com/lukehunter/google-play-scraper)**: ratings, installs, developer contact info and pricing for any Google Play app.
- **[Apple App Store Reviews Scraper](https://apify.com/lukehunter/app-store-reviews-scraper)**: Apple App Store reviews for any iOS app, across countries, with rating, version and date.
- **[Spotify Scraper](https://apify.com/lukehunter/spotify-scraper)**: play counts, monthly listeners and playlist track lists for any public Spotify artist, playlist, album or track.
- **[Walmart Category Scraper](https://apify.com/lukehunter/walmart-category-scraper)**: product names, prices, was-prices and ratings from Walmart category pages.
- **[Shopify Store Products Scraper](https://apify.com/lukehunter/shopify-store-products-scraper)**: full product catalogues from any Shopify store, with prices, sale prices, variants and stock.
- **[Vinted Scraper](https://apify.com/lukehunter/vinted-scraper)**: Vinted search results with prices, brands, sizes and favourites, across any Vinted country.
- **[AliExpress Search Scraper](https://apify.com/lukehunter/aliexpress-scraper)**: AliExpress search results with prices, discounts, ratings and sold counts, by keyword.

# Actor input Schema

## `roles` (type: `array`):

Job roles to search, e.g. "software engineer" or a slug like "software-engineer". Free text is lower-cased and slugified automatically. Each role fetches ONE page (https://wellfound.com/role/<slug>, or the remote-only variant if "Remote only" is checked) — see Limitations in the README. At least one role or location is required. Maximum 10.

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

Locations to search, e.g. "san francisco" or a slug like "san-francisco". Free text is lower-cased and slugified automatically. Each location fetches ONE page (https://wellfound.com/location/<slug>). At least one role or location is required. Maximum 10.

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

For roles, fetch Wellfound's dedicated remote-jobs page (wellfound.com/role/r/<slug>) instead of the general role page. For locations (which have no remote-specific page), fetches the normal location page and keeps only jobs Wellfound itself marks as remote.

## `minSalary` (type: `integer`):

Drop jobs whose parsed salary range does not reach this amount. A job with no parseable salary is dropped when this filter is set, because it cannot be confirmed to meet it. Leave at 0 to keep every job regardless of salary.

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

Keep only jobs whose title or description contains at least one of these words or phrases (case-insensitive). Leave empty to keep every job.

## `maxItems` (type: `integer`):

Hard cap on the number of job listings delivered across all roles and locations, 1-2000, default 50. You are charged per delivered job, so this is your cost cap.

## `onlyNewSinceLastRun` (type: `boolean`):

Turn this on and run the Actor on a schedule to get only jobs you haven't seen before: jobs already delivered on a previous run for the same roles/locations/filters are skipped and never charged again. The first run for a given search delivers everything (it's all new) and remembers it; later runs only deliver what's new since then. Off by default so a one-off run always gets the full current results.

## `stateStoreName` (type: `string`):

Only used when "Job alerts" is on. Name of the key-value store that remembers which jobs this search has already delivered, so it persists across scheduled runs. Leave as the default unless you're running several different alert searches that must not share history — give each its own name in that case. Letters, numbers and hyphens only.

## Actor input object example

```json
{
  "roles": [
    "software-engineer"
  ],
  "locations": [],
  "remoteOnly": false,
  "minSalary": 0,
  "keywords": [],
  "maxItems": 20,
  "onlyNewSinceLastRun": false,
  "stateStoreName": "wellfound-jobs-scraper-seen-jobs"
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

# 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 = {
    "roles": [
        "software-engineer"
    ],
    "locations": [],
    "keywords": [],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("lukehunter/wellfound-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 = {
    "roles": ["software-engineer"],
    "locations": [],
    "keywords": [],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("lukehunter/wellfound-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 '{
  "roles": [
    "software-engineer"
  ],
  "locations": [],
  "keywords": [],
  "maxItems": 20
}' |
apify call lukehunter/wellfound-jobs-scraper --silent --output-dataset

```

## MCP server setup

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