# Wellfound Scraper - Startup Jobs, Salary, Equity (`s-r/wellfound-scraper`) Actor

Scrape startup job listings from Wellfound, formerly AngelList Talent. Returns title, company, salary range parsed into numbers, equity band, remote flag, locations and posting date. Salary and equity are published openly on Wellfound, which most job boards do not do.

- **URL**: https://apify.com/s-r/wellfound-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:**
- **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?

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

## Wellfound Scraper

Startup jobs from Wellfound, formerly AngelList Talent, **with the salary and
the equity**. That is the reason this one exists: most job boards let companies
hide compensation, and Wellfound does not. Every listing publishes a pay range,
and many publish an equity band alongside it.

So a run here gives you something you cannot get from a general job board: a
straight read on what startups are actually paying, by role, right now.

### What each job gives you

- `title`, `url`, and `role`, Wellfound's own category for the position
- `company`, `company_url`, `company_slug`, `company_size` and
  `company_tagline`, the one-line description of what the startup does
- `compensation` exactly as published, plus `salary_min` and `salary_max`
  parsed into plain numbers you can sort and average
- `equity`, as a band such as `0.1%-0.5%`, or the string `none` when the
  listing explicitly says no equity
- `remote`, plus `locations` and `remote_locations`, which are different lists:
  the second is the set of places the company will accept a remote worker from,
  and it is often much wider than the first
- `posted_at` in ISO 8601 and `posted_timestamp` as a Unix time

### Three things this gets right that are easy to get wrong

**`$60k` is sixty thousand.** Strip the suffix and you get 60, which is a
number, looks like a salary, and is wrong by three orders of magnitude. Both
`k` and `m` are applied, and a range is split into its two bounds.

**The company is a pointer, not an object.** Wellfound is a Next.js app and its
embedded state is an Apollo normalised cache: a job's company is stored as a
reference like `{"__ref": "Startup:12345"}`, with the actual company sitting
elsewhere in a flat lookup table. A parser that reads the job object alone
returns fifty jobs with fifty empty company columns. Those references are
resolved before any row is emitted, and a reference that points at nothing
still produces a row rather than an exception.

**The dash in `0.1% – 0.5%` is not a hyphen.** It is an en dash, and a range
pattern written with a plain `-` matches only the first half, quietly turning a
band into a flat figure. Both characters are accepted.

Each of those three would produce output that looks completely fine. That is
what makes them worth stating.

### Input

Give it a list of targets:

- `jobs` for the main board
- a role slug such as `software-engineer`, `product-manager` or
  `data-scientist` for a role page
- any Wellfound URL, pasted as-is

Plus, optionally, how many pages to walk per target, an upper bound on rows,
how many pages to fetch at once, retries, and a country to request from.

### Run summary

Jobs returned, how many carried a salary, how many carried equity, how many are
remote, the number of unique companies, and the **median advertised minimum
salary** across the run. That median is the fastest read on whether a role
category pays what you thought it did.

Company names are counted separately in `withCompanyName`, which exists as a
health check: if that number ever drops well below the job count, the reference
resolution described above has stopped working and the run should not be
trusted. A silent column of blanks is exactly the failure this actor is built
to avoid, so it is measured rather than assumed.

### Scale

Each page costs one fetch and holds roughly fifty listings. A single target at
one page returned 50 jobs in about two seconds in testing, 48 of them with a
salary and 44 distinct companies.

Rows are deduplicated on the job id across every target and page in a run, so
overlapping searches do not inflate the count.

### What people use this for

**Compensation benchmarking.** This is the main one. Run a role page on a
schedule and you build a real series of advertised startup pay for that role.
Because `salary_min` and `salary_max` are numbers, medians and percentiles are
a one-line calculation rather than a parsing project.

**Equity norms by stage.** `equity` combined with `company_size` shows how
bands move as companies grow. Very few sources publish this at all, and almost
none publish it next to the salary it comes with.

**Sourcing and lead lists.** `company`, `company_url`, `company_size` and
`company_tagline` together make a usable list of startups that are actively
hiring, which is a strong buying signal if you sell to startups.

**Remote-policy mapping.** `remote_locations` is the interesting column: it is
the set of countries a company will actually employ from, which is a much
sharper filter than a yes-or-no remote flag and is rarely published anywhere
else in structured form.

### Notes

Salaries are annual figures in US dollars unless the listing itself says
otherwise, and they are what the company advertised rather than what anyone was
paid. Equity bands are similarly an offer range, not a grant.

Listings that have been filled or withdrawn simply stop appearing. Because
`job_id` is stable, running the same target on a schedule and keeping the rows
lets you see how long postings stay open, which is a decent proxy for how hard
a role is to fill.

Nothing here needs a login, a cookie or an API key.

# Actor input Schema

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

Which startup jobs to collect. Use 'jobs' for the main board, a role such as 'software-engineer' or 'product-manager' for a role page, or paste any Wellfound URL directly.

## `pages` (type: `integer`):

How many pages to walk for each target. Each page holds roughly 50 listings and costs one fetch.

## `jobs_per_target` (type: `integer`):

Upper bound on rows returned for each target.

## `max_targets` (type: `integer`):

How many targets to process in one run.

## `concurrency` (type: `integer`):

How many pages to fetch at the same time.

## `retries` (type: `integer`):

Retries with backoff before a page is reported as an error.

## `country` (type: `string`):

Optional two-letter country code to request from, for example us. Leave empty to request from wherever the run happens to sit.

## Actor input object example

```json
{
  "targets": [
    "jobs",
    "software-engineer"
  ],
  "pages": 1,
  "jobs_per_target": 200,
  "max_targets": 30,
  "concurrency": 4,
  "retries": 3,
  "country": "us"
}
```

# Actor output Schema

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

One row per job listing.

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

Counts, unique companies and the median advertised salary.

## `errors` (type: `string`):

Targets that could not be read, with a code and a redacted message.

# 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": [
        "jobs",
        "software-engineer"
    ],
    "pages": 1,
    "jobs_per_target": 200,
    "max_targets": 30,
    "concurrency": 4,
    "retries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/wellfound-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": [
        "jobs",
        "software-engineer",
    ],
    "pages": 1,
    "jobs_per_target": 200,
    "max_targets": 30,
    "concurrency": 4,
    "retries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/wellfound-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": [
    "jobs",
    "software-engineer"
  ],
  "pages": 1,
  "jobs_per_target": 200,
  "max_targets": 30,
  "concurrency": 4,
  "retries": 3
}' |
apify call s-r/wellfound-scraper --silent --output-dataset

```

## MCP server setup

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