# Wellfound Jobs Scraper — Startup Roles, Pay & Companies (`thenetaji/wellfound-jobs-scraper`) Actor

Search startup jobs by role and location and get one row per posting: the full description, the pay Wellfound shows, the experience asked for, the remote policy, and the hiring company with its size, website tags and hiring badges. Several roles and cities in one run.

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

## Pricing

from $1.28 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 Jobs Scraper

Export startup jobs from Wellfound by role and city. Each posting becomes one row carrying the
complete job description, the pay Wellfound displays, the experience the company asks for, the
remote policy and the hiring company — its name, slug, pitch, headcount band and hiring badges —
so a row is readable and sortable without a second lookup anywhere.

Several roles and several cities go into one run. Three roles against two cities is six searches
and one dataset, and a posting that turns up in more than one of them is saved once.

### What a row looks like

```json
{
  "job_id": "4592877",
  "title": "AI Engineer",
  "slug": "ai-engineer",
  "url": "https://wellfound.com/jobs/4592877-ai-engineer",
  "company_name": "Mosaic",
  "company_slug": "mosaicapp",
  "company_url": "https://wellfound.com/company/mosaicapp",
  "company_pitch": "AI-Powered Workforce Management Software",
  "company_size": "SIZE_51_200",
  "company_badges": ["Actively Hiring", "Top 10% of responders", "B2B", "Growth Stage"],
  "compensation": "$220k – $300k • 0.01% – 0.02%",
  "job_type": "full-time",
  "remote": false,
  "remote_policy": "ONSITE",
  "locations": ["San Francisco"],
  "remote_locations": [],
  "primary_role": "Software Engineer",
  "years_experience_min": 5,
  "years_experience_max": null,
  "posted_at": "2026-08-15T03:08:03.000Z",
  "ats_source": "AtsIntegration::Greenhouse::Listing",
  "description": "## The role\n\nMosaic is the first AI resource manager…",
  "search_role": "software-engineer",
  "search_location": null
}
```

`description` is the whole posting, not a snippet — that is the unusual part of this source and
the reason a search row here is worth more than a search row on most job boards. `compensation` is
the string Wellfound renders, en-dash and equity clause included; the numeric version is described
under **Pay as numbers** below.

`ats_source` names the applicant-tracking system the company runs. Across 80 postings under
`software-engineer` on 16 August 2026, 65 stated one and the rest were null, which means the
company posted directly rather than syncing from a system. It is carried under Wellfound's own
field name and value so it can be matched against their data without translating anything.

### Accepted input

```json
{
  "roles": ["software-engineer", "designer"],
  "locations": ["san-francisco", "new-york"],
  "maxItems": 100
}
```

`roles` and `locations` take the slugs that appear in a wellfound.com URL — `software-engineer`,
`san-francisco` — and a pasted role or location URL is reduced to its slug. At least one of the
two is required; Wellfound publishes no unfiltered job-search page, so there is nothing to read
without one. Given both, every role is searched in every location.

`remote` restricts a run to remote postings and works on a role-only search. `maxItems` applies
per search, so two roles at 100 is up to 200 rows.

#### Pay as numbers

`enrichJobDetails` reads each job's own posting page and adds `salary_min`, `salary_max`,
`salary_currency` and `salary_period` — the range as numbers with the period they are quoted over
— plus `employment_type`, `direct_apply`, `job_location_type`, `applicant_locations`, the
company's own website, its industries, the benefits it states and the posting body as published
HTML. It is one extra request per job and is billed per job enriched, so it is off by default.

### Questions

**Why did a run return fewer rows than `maxItems`?**
Because Wellfound's own result set ran out first. A search walks pages until the site stops
producing new postings, and past the end of a result set it answers by serving the first page
again rather than an empty one. That is detected and the walk stops, so a short result means the
site has nothing further for that query — retrying returns the same rows. The ceiling on any one
search is 500. Reaching further means another role or another city, not a larger number.

**A role came back with nothing at all. Is it broken?**
Almost always the slug. Wellfound serves pages only for the roles and locations in its own
vocabulary, and a slug outside it has no page, which from outside looks the same as a page that
refused to load. The run reports the search that found nothing and carries on with the rest, so
one typo in a list of twelve does not cost the other eleven. Check the slug by opening
`wellfound.com/role/<slug>` in a browser.

**Why is `remote` rejected together with a location?**
Wellfound publishes a remote variant of its role pages and none of its location pages. Sending
both would return the location's ordinary results — a different question, answered as though it
were the one asked — so the run stops and says so instead. When a place matters, search the
`remote-us` location slug.

**Why is `compensation` text rather than a number?**
Because the same field carries salary ranges, equity ranges and free text: `$220k – $300k • 0.01%
– 0.02%` is one value. Parsing it into a number would be wrong more often than useful. The
posting page states the pay as structured values, which is what `enrichJobDetails` adds, and it is
null on a posting that states no range rather than zero.

**Do the same postings appear twice when several searches overlap?**
No. A role search and the role-in-a-city search under it genuinely overlap, and rows are matched
on the job id across the whole run, so a posting reached twice is saved and charged once.
`search_role` and `search_location` record which search each row came from, which is how a mixed
dataset is split back apart.

**Can the companies be exported instead of the jobs?**
Each row already carries the hiring company, so grouping the dataset by `company_slug` gives the
companies behind a role. The company columns on a job row are the summary Wellfound attaches to a
posting — for websites, industry tags, office locations and open-role counts, the directory is the
better source. See below.

**Is `years_experience_max` usually missing?**
Yes, and that is the posting rather than the export: most companies state a floor and no ceiling,
so `years_experience_min` is set and `years_experience_max` is null. Filtering on the maximum
therefore drops most postings; filter on the minimum.

### Related Actors

[Wellfound Job Posting Scraper](https://apify.com/thenetaji/wellfound-job-posting-scraper) reads
job links directly, and is the one to use when the links already exist somewhere else.

[Wellfound Startups Scraper](https://apify.com/thenetaji/wellfound-startups-scraper) works the
other way round — companies first, with websites, industries, offices and open-role counts — and
[Wellfound Company Scraper](https://apify.com/thenetaji/wellfound-company-scraper) takes a
`company_slug` from any row here and returns that company's funding, team and perks.

# Actor input Schema

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

Role slugs to search, one per line, exactly as they appear in a wellfound.com/role/<role> URL: `software-engineer`, `designer`, `data-scientist`, `product-manager`. A full role URL may be pasted instead and the slug is taken from it.

Roles come from Wellfound's own vocabulary; a slug it publishes no page for cannot be searched, and there is no way to list the vocabulary from here. A role that returns nothing is reported in the run log and the remaining searches continue.

At least one role or one location is required. Given both, every role is searched in every location — three roles and two locations is six searches.

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

Location slugs to search, one per line, exactly as they appear in a wellfound.com/location/<location> URL: `san-francisco`, `new-york`, `london`, `remote-us`. A full location URL may be pasted instead.

Used alone it searches every role in that location; combined with roles it searches each pair. At least one role or one location is required.

## `remote` (type: `boolean`):

Restrict the search to remote postings.

Available on a role-only search and nowhere else. Wellfound publishes a remote variant of its role pages and none of its location pages, so a run that sets this together with a location is refused rather than answered with results that quietly ignore it. Search the `remote-us` location slug instead when a location is what matters.

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

How many job rows to save from each search. Three roles at 100 is up to 300 rows.

The upper bound is 500 and it is Wellfound's, not a preference: a result set is walked until it starts repeating itself, which on a narrow query happens well before 500. Another role or another location reaches further than a larger number here does.

## `enrichJobDetails` (type: `boolean`):

Read each job's own posting page and add the structured pay range with the period it is quoted over, the employment type, whether the application is completed on Wellfound or handed to the company's system, the company's own website, the industries it lists, its stated benefits and the posting body as published HTML.

One extra request per job, billed per job enriched and only when the posting page returns something. Leave it off when the search row's own pay string and description are enough.

## Actor input object example

```json
{
  "roles": [
    "software-engineer"
  ],
  "locations": [
    "san-francisco"
  ],
  "remote": false,
  "maxItems": 40,
  "enrichJobDetails": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped by this run

# 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"
    ],
    "maxItems": 40
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/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"],
    "maxItems": 40,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/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"
  ],
  "maxItems": 40
}' |
apify call thenetaji/wellfound-jobs-scraper --silent --output-dataset

```

## MCP server setup

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