# Wellfound Startup Jobs Scraper (role, location, remote, salary) (`datahamster/wellfound-startup-jobs`) Actor

Wellfound (AngelList Talent) startup jobs as flat rows: title, company, size, funding stage, badges, pay range, equity, remote flag, locations, posted date and full description. Search by role, by city or both, filter remote-only, and get monitor-mode alerts on new startup jobs.

- **URL**: https://apify.com/datahamster/wellfound-startup-jobs.md
- **Developed by:** [Viktor Dubnytskiy](https://apify.com/datahamster) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 result items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Startup Jobs Scraper (role, location, remote, salary)

Scrape startup job listings from **Wellfound** (ex AngelList Talent) public landing pages. Give it a role,
a city, or both, and get one flat row per job with the hiring startup's public profile inlined: company name,
size, funding stage, badges, one-line pitch, plus the posting's pay range, equity, remote flag, locations and
the full job description.

No login, no cookies, no account. Only pages Wellfound serves to logged-out visitors are read.

### What you get

One row per job listing:

`id` / `jobId`, `url`, `title`, `roleTitle`, `companyName`, `companySlug`, `companyUrl`, `companySize`,
`companyStage`, `companyTags`, `companyPitch`, `companyLogoUrl`, `location`, `locations`, `remote`,
`remoteType`, `remoteLocations`, `jobType`, `compensation`, `salary`, `salaryMin`, `salaryMax`, `currency`,
`equity`, `yearsExperienceMin`, `yearsExperienceMax`, `postedAt`, `description`, `atsSource`, `query`,
`page`, `rank`, `scrapedAt`.

`id` is the Wellfound job listing id, so the same posting never appears twice in one dataset even when it is
found through two different landings, and monitor mode can track it across runs.

### Example output

| Field | Row from `/role/software-engineer` | Row from `/location/berlin` |
|---|---|---|
| `id` | `4639821` | `3165360` |
| `title` | `Software Engineer` | `Software Architect (L3/L4/L5)` |
| `companyName` | `Checkr` | `openigloo` |
| `companySize` | `501-1000` | `11-50` |
| `companyStage` | `Scale Stage` | `Early Stage` |
| `companyTags` | `["Actively Hiring", "B2B", "Top Investors", "YC Funded", "Valuation $1B+", ...]` | `["Actively Hiring", "Top 5% of responders", "B2C", "B2B"]` |
| `location` | `Denver, San Francisco` | `Berlin, Hyderabad` |
| `remote` / `remoteType` | `false` / — | `false` / `ONSITE` |
| `compensation` | `$150k – $176k` | `$50k – $80k • 0.01% – 0.1%` |
| `salaryMin` / `salaryMax` / `currency` | `150000` / `176000` / `USD` | `50000` / `80000` / `USD` |
| `equity` | — | `0.01% – 0.1%` |
| `postedAt` | `2026-08-27T20:06:24Z` | `2026-09-03T18:50:20Z` |
| `url` | `https://wellfound.com/jobs/4639821-software-engineer` | `https://wellfound.com/jobs/3165360-ai-software-engineer` |

### Input

| Field | Meaning | Default |
|---|---|---|
| `roles` | Role slugs or plain words, one per line: `software-engineer`, `Data Scientist`, `product-designer` | empty |
| `locations` | Cities or regions, one per line: `san-francisco`, `Berlin`, `new-york` | empty |
| `landingUrls` | Ready-made Wellfound landing URLs (`/role/…`, `/location/…`, `/role/l/…/…`) | empty |
| `remoteOnly` | Keep only postings Wellfound marks as remote | `false` |
| `maxJobsPerQuery` | Stop each role/location query after this many jobs | `100` |
| `strictQueryMatch` | Drop a landing Wellfound resolved to something other than what you asked for | `true` |
| `maxItems` | Stop the whole run after this many rows | `100` |
| `mode` | `scrape` or `monitor` (only new/changed since the last run) | `scrape` |
| `monitorKey`, `webhookUrl`, `telegramBotToken`, `telegramChatId` | Monitor-mode state key and alert targets | empty |

Give `roles` alone and the actor walks `/role/<role>`. Give `locations` alone and it walks `/location/<city>`.
Give both and it walks the combined `/role/l/<role>/<city>` landing for every pair, up to 20 queries per run.
Free text is slugified for you, so `Data Scientist` and `data-scientist` are the same query.

### Job alerts (monitor mode)

Set `mode: monitor` and save the run as a task on a schedule. Each run compares against the previous one and
returns **only** new or changed postings, keyed by the job listing id, with the change decided on
`title | companyName | compensation | location`. Point `webhookUrl` or the Telegram fields at your own channel
and every new startup job matching your roles arrives there. You pay one monitor-check event per run plus one
change event per posting that actually moved.

### Pricing

| Event | Price |
|---|---|
| result | $0.001 per job ($1 per 1,000) |
| monitor-check | $0.005 per monitor run |
| change | $0.001 per new/changed job |

You are charged only for rows actually pushed. Postings dropped by `remoteOnly` are filtered before the
dataset and are never charged. **Proxy traffic is billed by Apify on top of these events**: every request goes
through a residential exit and a landing page is roughly 0.6 MB, so budget for it in your plan. If Cloudflare
puts a security check on the residential exit, the actor steps up to Apify's Web Unblocker for the rest of the
run (Web Unblocker is priced per request, so a run that escalates costs noticeably more); if the check survives
that too and nothing has been collected, the run stops and reports `blocked` instead of spending further.

### Limits

- Wellfound paginates its landing pages by **startup**, 20 startups per page, and shows up to 3 open jobs per
  startup — so a page yields roughly 30-45 jobs. The actor stops by itself at the last page Wellfound
  publishes: asking for a page past the end silently serves page 1 again, and the walk detects that instead of
  re-charging the same rows. A hard ceiling of 25 pages per query applies whatever `maxJobsPerQuery` says.
- A role or city Wellfound does not know answers with HTTP 404 and the run ends with no rows and no charge —
  there is no filler-results page, so you never pay for unrelated jobs. One case is not a 404: an unknown
  **role** paired with a **real city** (`/role/l/veterinarian/berlin`) silently degrades to that city's whole
  landing. With `strictQueryMatch` on (the default) the actor notices — the page echoes back which role and
  city Wellfound actually resolved — and drops the query instead of charging you for jobs that have nothing to
  do with the role. Set it to `false` if you would rather have the city's jobs than nothing.
- The landing pages show each startup's **highlighted** open jobs, not necessarily every job it has open.
- Company profile pages (`wellfound.com/company/<slug>`) sit behind a Cloudflare challenge and are **not**
  opened. `companyUrl`, `companySize`, `companyStage`, `companyTags` and `companyPitch` come from the landing
  page itself; there is no headcount, funding total or investor list beyond the badges Wellfound prints.
- `compensation` is the string the employer published; `salaryMin` / `salaryMax` / `currency` are parsed from
  it and are `null` when the posting publishes no pay. Equity-only strings (`No equity`, `0.5% – 1.0%`) land
  in `equity` with the salary fields empty.
- Proxy tiers are walked cheapest-first: residential, then Web Unblocker only if residential is challenged.
  The run reports `blocked` rather than retrying forever when both are refused.
- Only three routes are ever requested — `/role/<role>`, `/location/<city>` and `/role/l/<role>/<city>`,
  with `?page=N`. Anything else pasted into `landingUrls` (a company profile, a job page, Wellfound's own
  `/search`, a candidate profile) is rejected before a request is made. Wellfound's search route is
  disallowed by its robots.txt and is not used.
- **No person-level data.** No candidate profiles, no recruiter or founder names, no emails, no phone numbers,
  no avatars. Rows describe a job posting and the hiring company, nothing else. Some employers type a contact
  address into the posting text itself; `description` and `companyPitch` are scrubbed of e-mail addresses and
  dialable phone numbers before the row is pushed, and a posting that carries neither is returned untouched.

### FAQ

**Can I combine several roles and cities?** Yes. Roles and locations form a cross product of landing pages,
capped at 20 queries per run; each row carries the landing URL it came from in `query`.

**Why do two landings return the same job?** They share startups. The job listing id is the dataset key, so
duplicates collapse into one row automatically.

**What happens when nothing is found?** No rows are pushed and no result events are charged. The `RUN_SUMMARY`
record in the run's key-value store carries `emptyReason`, which separates a genuinely empty landing
(`no_matches`) from a refused request (`blocked`) or moved markup (`source_changed`).

### Changelog

- 0.1: initial release — role, location and role+location landings, pagination, remote filter, parsed pay and
  equity, startup profile fields, monitor mode.

***

If this actor saved you time, a short review on its Store page genuinely helps other people find it. Found a
bug or need a field that is missing? Open a ticket on the **Issues** tab.

# Actor input Schema

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

Stop after this many results (you are charged only for pushed items)

## `mode` (type: `string`):

scrape = full results; monitor = only new/changed items since the previous run of this task

## `monitorKey` (type: `string`):

Optional state key when not running as a saved task

## `webhookUrl` (type: `string`):

POST a change summary here in monitor mode

## `telegramBotToken` (type: `string`):

Optional: bot token for monitor-mode change summaries

## `telegramChatId` (type: `string`):

Optional: chat id that receives monitor-mode summaries

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

Roles to search, one per line, e.g. "software-engineer", "Data Scientist", "product-designer". Plain words are slugified for you. Each role becomes a Wellfound role landing page; combined with Locations it becomes a role + city landing page.

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

Cities or regions, one per line, e.g. "san-francisco", "Berlin", "new-york", "remote". Used alone they give a city landing page; combined with Roles they give one landing page per role + city pair (max 20 queries per run).

## `landingUrls` (type: `array`):

Ready-made Wellfound landing URLs, e.g. "https://wellfound.com/role/l/software-engineer/san-francisco". Only /role/..., /location/... and /role/l/.../... pages are accepted; Wellfound's own search URLs are disallowed by its robots.txt and are rejected.

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

true = keep only postings Wellfound marks as remote. Filtered postings are dropped before the dataset, so you are not charged for them. Example: false.

## `maxJobsPerQuery` (type: `integer`):

How many jobs to collect per role/location landing page before moving to the next one. A landing page yields roughly 30-45 jobs, so 100 is about three pages. Range 1-5000.

## `strictQueryMatch` (type: `boolean`):

true = drop a landing page Wellfound resolved to something other than the role and city you asked for (an unknown role paired with a real city silently falls back to that city's whole landing). Keeps you from being charged for unrelated jobs. Example: true.

## Actor input object example

```json
{
  "maxItems": 100,
  "mode": "scrape",
  "roles": [
    "software-engineer"
  ],
  "remoteOnly": false,
  "maxJobsPerQuery": 100,
  "strictQueryMatch": true
}
```

# Actor output Schema

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

All pushed rows (dataset, JSON)

## `resultsTable` (type: `string`):

Dataset in the Console viewer

## `runSummary` (type: `string`):

RUN\_SUMMARY record

# 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"
    ],
    "maxJobsPerQuery": 100
};

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

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

```

## MCP server setup

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