# Wellfound Startup Jobs Scraper (`nanomicon/wellfound-startup-jobs-scraper`) Actor

Find and monitor startup roles for recruiters, analysts, and job seekers. Returns structured compensation, remote-work, company, location, and listing details. Includes compact output for agent workflows.

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

## Pricing

from $2.50 / 1,000 dataset items

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 Startup Jobs Scraper

Scrape Wellfound (formerly AngelList) startup job listings — structured
salaries, equity, remote policy, and company data. Built for recruiters,
analysts, job-seekers, and AI agents.

**Why this one over the alternatives:**

- **Fails loudly.** Zero results or a site change = explicit error naming what
  broke — never a silent empty run burning your quota (the #1 complaint about
  other Wellfound scrapers).
- **Fresh and maintained.** Daily builds; when the site changes we ship fixes
  within a day.
- **Cheap.** Low platform usage we pass on to you.
- **Incremental by default.** Repeat runs emit only NEW jobs — build job alerts
  without deduplicating anything yourself.
- **Webhook notifications.** Optional secret `webhookUrl` receives one JSON
  notification after each run writes newly emitted jobs.
- **Stale-tolerant cache mode.** $0.0005/result snapshots for agents that want
  bulk data fast and accept some staleness.

### What you get

| Field | Example |
|---|---|
| `title` | Senior Backend Engineer |
| `company` | Virta Health |
| `comp` | $114k – $148k |
| `salaryMin` / `salaryMax` / `salaryCurrency` | 114000 / 148000 / USD |
| `equityMinPct` / `equityMaxPct` | 0.1 / 0.5 |
| `jobType` | full-time |
| `remote` / `remoteKind` | true / "worldwide" |
| `locations` | \["United States"] |
| `yearsExperienceMin` / `yearsExperienceMax` | 2 / 5 |
| `postedAt` | 1791847814 (unix) |
| `description` | byte-identical text excerpt by default |
| `companyUrl` | direct startup profile URL |
| `companySize`, `companyHighConcept`, `companyLogoUrl` | with `enrich: true`; no extra fetches or charges |
| `companyBadges` | complete set of distinct, non-empty badge labels in first-seen order; whitespace-only labels ignored; labels kept verbatim; no badge-name allowlist |
| `companyStage` / `companyFundingStage` | machine-readable funding-stage token / display label from the same stage badge; token kept verbatim with no token allowlist |
| `companyYcFunded` | `true` when the company carries a YC badge |
| `descriptionMarkdown` | optional; when `descriptionFormat: "markdown"` |
| `descriptionHtml` | optional; when `descriptionFormat: "html"` |
| `url` | direct job listing URL |

With `enrich: true`, matching job rows include available company fields from
data collected during the crawl. These fields add no fetches or charges.
A missing company badge or funding-stage field means the source did not report
a usable value for it — not that the company has no stage and is not YC-backed;
the actor omits the key and never emits `null`. `company-rollups`
always includes per-company open-role counts, salary medians, recent hiring,
top titles, and remote share; rollups are free. The five single-valued company
string fields (`companySize`, `companyHighConcept`, `companyLogoUrl`,
`companyStage`, `companyFundingStage`) use the most frequent value across the
company's jobs, with ties broken alphabetically. `companyBadges` in a rollup
row is the ordered distinct union across the company's jobs; `companyYcFunded`
is `true` if any of the company's jobs carries it.

`companyStage` is the machine-readable funding-stage token, taken verbatim
from the stage badge; `companyFundingStage` is the display label of that same
badge. `companyYcFunded` is `true` when the company carries a YC badge.

`description` remains the byte-identical default text excerpt;
`descriptionMarkdown` or `descriptionHtml` is returned only when the matching
format is requested.

### Quick start

1. Run with defaults — crawls software-engineer + backend-developer roles.
2. Second run with the same input emits only newly posted jobs.
3. Set `compact: true` for core fields only; set `enrich: true` to include available company fields at no extra charge.

```json
{
  "roleSlugs": ["software-engineer", "backend-developer"],
  "keywords": [],
  "locations": [],
  "maxJobs": 500,
  "maxFetches": 30,
  "postedWithinDays": 7,
  "minComp": 100000,
  "compact": false,
  "enrich": false,
  "descriptionFormat": "text"
}
```

`roleSlugs`, `keywords`, and `locations` suggestions are non-exhaustive; custom
slugs are allowed. To find a slug, open the matching Wellfound page and copy
the URL path segment: after `/role/r/` for `roleSlugs`, after `/role/` for
job-title `keywords`, or after `/location/` for `locations`. Enter the slug,
not the full URL or display name. For example, `/location/new-york-city-ny`
uses slug `new-york-city-ny`. Wellfound pages and available listings can change.

### Webhooks and description formats

- **Webhook notifications.** Optional secret `webhookUrl` receives one
  `application/json` POST per run after dataset output is written, only when
  fresh jobs were emitted. Payload includes `runId`, `actorId`, `runUrl`,
  `counts` (`new`, `totalEmitted`), up to 50 newest compact `jobs`,
  `datasetUrl`, and `crawledAt`. Webhook failures warn but do not fail the run
  or lose data; requests time out after 10 seconds and retry once.
- **Description format.** `descriptionFormat` accepts `text` (default),
  `markdown`, or `html`. Default `description` excerpt stays byte-identical;
  requested formats add optional `descriptionMarkdown` or `descriptionHtml`.

### Pricing

- **$0.01 per run** (`apify-actor-start`).
- **$0.0025 per fresh job** (`apify-default-dataset-item`).
- **$0.0005 per cached job** (`cached-result`).
- A 1,000-job fresh run ≈ **$2.51**. Company fields with
  `enrich: true` add no fetches or charges.

### Filters

- `roleSlugs` — remote role pages; suggested values are non-exhaustive; custom
  Wellfound slugs are allowed.
- `keywords` — additional job-title slugs, not arbitrary free-text keywords;
  fetched as general Wellfound role pages.
- `locations` — location slugs; suggestions are non-exhaustive; custom values
  are slugified before fetching.
- `postedWithinDays` — only jobs posted in the last N days.
- `minComp` / `minYears` — lower bounds on salary / required experience.
- `maxJobs` defaults to `500`; `maxFetches` defaults to `30` and caps role
  and seed-page fetches only.
- `enrich` — include available company fields; output toggle, no extra fetch
  or charge; default `false`.
- `descriptionFormat` — `text` (default), `markdown`, or `html`; default text
  description is unchanged.
- `webhookUrl` — optional secret endpoint; one post after emitted jobs are
  written, fail-soft with one retry.
- `cached` / `maxCacheAgeHours` — snapshot serving and its freshness limit.

### Recipes

- **Job alert:** schedule identical input with `postedWithinDays` and `maxJobs`.
  Incremental state emits only new jobs; read the default dataset after each
  successful run and use a webhook to trigger downstream processing.

### Reliability contract

- Crawl fails loudly on: zero usable results, unparseable site state, missing
  company anchors, >5% fetch errors. Every failure names the cause.
- Incremental state is keyed to your exact input — change the input, get a
  clean partition. Clear the actor's key-value store to reset.
- Memory-capped 256–1024 MB: predictable platform cost.

### For AI agents

Machine-readable docs: [`AGENTS.md`](./AGENTS.md) — exact input/output schemas,
pricing events, error semantics, and a copy-paste run command. Output fields
are stable; `compact: true` returns the core subset agents need. If you are
driving this actor from an LLM tool-call, read AGENTS.md, not this README.

### Store sync

`scripts/sync_store_state.py` compares live Store metadata with `.actor/store-state.desired.json`; read-only by default, `--apply` writes.
`--publish` authorizes public flips; `--unpublish` authorizes private flips; neither authorizes the other.
Both checks are change-based: an apply leaving `isPublic` unchanged needs no flag. Refused apply sends no request; refusal happens before the write.
Visibility flips are human decisions; the tool refuses to guess.
Store categories are pinned to `JOBS`, `LEAD_GENERATION`, `AGENTS` in
`.actor/store-state.desired.json`; `--apply` writes them (no flag needed —
categories are not a visibility flip).
The Store's example run input is kept in `.actor/example-run-input.json`; set
it on the actor's `exampleRunInput` field so a Console test run uses a real
input instead of the template placeholder. `scripts/set_example_run_input.py`
applies it (`--check` to compare, `--dry-run` to preview; a bare run applies);
the Apify CLI cannot set this field, so the script updates the actor's
`exampleRunInput` field through the documented actor update endpoint (only
that field is sent).

### FAQ

**Is this affiliated with Wellfound?** No. Independent public-data extraction.
**Why does a run return 0 results but succeed?** It doesn't — empty crawls
fail with a reason. A successful run with no NEW jobs means everything was
already emitted (incremental mode working).
**Blocked or empty?** Open an issue with the run ID — failures are explicit by
design, so support starts from the real error.

# Actor input Schema

## `roleSlugs` (type: `array`):

Remote Wellfound role pages to search. Suggestions are not exhaustive; custom slugs are allowed. For custom values, open a Wellfound remote-role page and copy the slug after /role/r/ (for example, /role/r/backend-developer). Enter the slug, not the display label; not every role has remote postings.

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

Additional Wellfound job-title pages to search, fetched as general role pages rather than arbitrary full-text keywords. Suggestions are not exhaustive; custom slugs are allowed. For custom values, copy the slug after /role/ from a Wellfound title page (for example, /role/data-engineer).

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

Wellfound location pages to search. Suggestions are not exhaustive; custom slugs are allowed. For custom values, open a Wellfound location page and copy the slug after /location/ (for example, /location/new-york-city-ny). Enter the slug, not the display name; listings can change.

## `enrich` (type: `boolean`):

Include available companySize, companyHighConcept, companyLogoUrl, companyBadges, companyStage, companyFundingStage, and companyYcFunded when reported by the source, from startup data already present in role-page results. Output toggle only; no extra fetch or charge.

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

Optional secret URL to notify after newly emitted jobs are written.

## `descriptionFormat` (type: `string`):

Description output format. The default text excerpt remains unchanged.

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

Maximum unique jobs collected before output.

## `maxFetches` (type: `integer`):

Hard cap on role-page and seed-page fetches only. Requests are paced at least 400 ms apart.

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

Optional listing age limit; listings without a parseable posted date are excluded.

## `minYears` (type: `integer`):

Optional minimum lower-bound years of experience.

## `minComp` (type: `number`):

Optional minimum lower-bound salary; listings without a parseable compensation are excluded.

## `compact` (type: `boolean`):

Return core fields only for MCP and agent workflows.

## `cached` (type: `boolean`):

Replay latest full-crawl snapshot without fetching. Cached results may be stale and are billed through the cached-result custom event.

## `maxCacheAgeHours` (type: `number`):

Optional maximum snapshot age; older snapshots trigger a fresh full crawl. Zero always triggers a fresh crawl.

## Actor input object example

```json
{
  "roleSlugs": [
    "software-engineer",
    "backend-developer"
  ],
  "keywords": [],
  "locations": [],
  "enrich": false,
  "descriptionFormat": "text",
  "maxJobs": 500,
  "maxFetches": 30,
  "postedWithinDays": 7,
  "minComp": 100000,
  "compact": false,
  "cached": false
}
```

# Actor output Schema

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

One dataset item per emitted job: salary, equity, job type, remote policy, locations, listing URL, and (with enrich: true) company size, logo, concept, the complete set of distinct non-empty badge labels, funding-stage machine token and separate display label, and YC-funded status (true when company carries a YC badge).

## `console` (type: `string`):

Open the run in Apify Console to view the Jobs, Company fields, and All fields dataset views.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("nanomicon/wellfound-startup-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 = {}

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

```

## MCP server setup

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