# Wellfound Scraper — Startup Jobs, Salaries & Equity (`memo23/wellfound-scraper`) Actor

Scrape Wellfound (AngelList) startup jobs by keyword, role, city or remote. Every row has the full description, salary, equity, remote flag, company size and funding stage. Inputs saved for other Wellfound scrapers run as they are. About $0.002 per job.

- **URL**: https://apify.com/memo23/wellfound-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Jobs, Automation, Agents
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.84 / 1,000 results

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/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 startup job listings from **Wellfound.com** (formerly AngelList Talent) — title, compensation band, locations, remote flag, posted date, plus the **startup**'s name, logo, slug, company-size band, market tags, headcount, and Wellfound's **funding-stage / top-investor / recently-funded / growing-fast** signals — denormalized into every row. **Paste a `/role/` search URL** — or just type a keyword and let the actor build one — and get back self-contained flat rows ready for CSV, each carrying the **full job description**.

![How Wellfound Startup Jobs Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-wellfound.png)

### Why this actor

Wellfound is the largest English-language startup job board. Most scrapers for it read the `/jobs` landing page — a curated set of about **50 jobs** that returns the same rows no matter what filters you pass — and then charge premium prices for that thin slice.

This actor reads Wellfound's **role-search** surface instead. A single query like `/role/software-engineer` reaches **about 5,000 jobs across ~90 pages** (all locations; `/role/r/software-engineer` is the remote-only subset, about 1,850), filters by role and location server-side, and returns the **full job description** on every row — no enrichment step, no extra charge. Rows are de-duplicated by job ID across the whole run, so you are never billed for a repeat.

- **Three-stack race fetch** — impit (Rust + rustls Firefox), gotScraping (Node + headerGenerator), and **impers** (libcurl-impersonate Chrome 142). The fastest legit response wins per page, so a single fingerprint hiccup doesn't burn the retry budget
- **Apollo-state extraction** — one `JSON.parse` of `__NEXT_DATA__` resolves `JobListing` → `Startup` → `Badge` / `NewTag` / `JobListingRemoteConfig` refs in-memory. No DOM walking
- **Real pagination + de-duplication** — role searches page through Wellfound's own reported page count, and every row is de-duplicated by job ID across the run, so you are never billed for a repeat. Free-tier users have a global ceiling of 100 rows
- **Proxy handled for you**: leave the proxy setting at its default and the actor picks a US residential exit, because Wellfound blocks most datacenter IPs on its search pages. Paying runs go out through a dedicated residential pool.
- **One flat row per job** — company-level fields are denormalized on every row so CSV consumers don't need to join
- **Cheap per row** — about `$0.0011` per job row (`~$1.09 / 1,000 jobs`), plus a small per-run start fee. See the [Pricing](#pricing) table for the exact events

### Company enrichment — funding, founders & socials (opt-in)

When you set `enrichCompanyProfile: true`, after the listings phase completes the actor runs a second pass over each unique startup in the dataset. Wellfound's company pages sit behind a Cloudflare check that plain HTTP clients never pass, so the actor opens one real browser session on a residential IP, lets the check complete, and then fetches every company page with that session (about 1-2 s per company). It parses the Apollo state and merges these fields into **every job row for that company**:

| Field | What it carries | Example (Airbnb) |
|---|---|---|
| `companyTotalRaised` | Total funding amount in USD (integer) | `11333560000` ($11.3 B) |
| `companyFundingRoundsCount` | Number of recorded rounds | `20` |
| `companyLatestRoundType` | Most recent round name | `"IPO"` |
| `companyLatestRoundClosedAt` | ISO timestamp of latest round | `"2020-11-27T..."` |
| `companyFundingRounds[]` | Full round history (`roundType`, `closedAt`, `valuation`) | 20 rounds back to Series B 2011 |
| `companyProductDescription` | Long-form product/company bio | Full Airbnb marketing copy |
| `companyWebsiteUrl` | Official company URL (not Wellfound) | `"http://airbnb.com"` |
| `companyTwitterUrl` / `companyLinkedinUrl` / `companyFacebookUrl` / `companyBlogUrl` / `companyProductHuntUrl` | All social URLs | Populated when present |
| `companyIsOperating` | Whether Wellfound flags it as operational | `true` |
| `companyIsIncubator` | Whether the profile is an incubator | `false` |
| `companyIsShell` | Wellfound's shell-company flag | `false` |
| `companyIsYcFunded` | Y Combinator funding. **Now populated for free on every row** from Wellfound's `YC_BADGE` — no enrichment needed | `true` |
| `companyProfileCompletenessScore` | Wellfound's internal 0-100 completeness | `78` |
| `companyFounders[]` | Founder identities resolved from User refs | `[{name: "Joe Gebbia", slug: "joegebbia", ...}, {name: "Brian Chesky", ...}]` |
| `enrichmentStatus` | `'enriched'` / `'failed'` / `null` | — |
| `enrichmentAttempts` | How many fetch attempts the burst used | `7` |

#### Extra fields unlocked with enrichment

The `/company/{slug}` Apollo state embeds the company's recent JobListings **with richer fields than the listing-page versions**. When company enrichment succeeds, the embedded data fills any of these fields that are still empty on the matching job rows — no extra fetches, no extra charges. Rows from a `/role/` search usually have them already; this mostly helps `/jobs` landing-page rows:

| Field | Description | Coverage example (Airbnb) |
|---|---|---|
| `jobDescriptionSnippet` | First ~500 chars of the job description as HTML (the "snippet" Wellfound shows in cards). **Partial answer to "full job description" without /jobs/{id}.** | 10/10 jobs populated |
| `jobType` | `"full_time"` / `"contract"` / `"part_time"` / `"internship"` | 10/10 |
| `equity` | Equity component separately from compensation (e.g. `"0.1% – 0.5%"`) | 0/10 (Airbnb's public, no equity offered) |
| `yearsExperienceMin` / `yearsExperienceMax` | Required experience range | 10/10 |
| `autoPosted` | `true` if the job is auto-syndicated from an ATS | 10/10 (Airbnb is auto-posted) |
| `atsSource` | Original ATS source (`"greenhouse"` / `"lever"` / etc.) when syndicated | 0/10 (Airbnb returns null) |
| `recruitingContact` | **Hiring-manager identity** when Wellfound exposes it — `{ name, slug, avatarUrl, pathName }`. Typically populated for manually-posted jobs at smaller startups, null for auto-posted ATS jobs. | 0/10 (Airbnb is ATS-auto-posted) |

#### Pricing

| Event | When charged | Rate |
|---|---|---|
| `apify-actor-start` | Once per run, ×memory GB | `$0.01` per GB |
| `apify-default-dataset-item` | Every job row written | `$0.00099` ($0.99 / 1k rows) |
| `additional-data` | Every job row written | `$0.0001` ($0.10 / 1k rows); **`$0.001` ($1.00 / 1k rows) from 30 Sep 2026** |
| `company-enrichment` | **Per successful company** (de-duped — a company with 56 jobs = 1 charge) | `$0.0015` per company |
| `job-detail-enrichment` | Per successful job-detail fetch | `$0.0015` per job |

Per row that's **`$0.00109` (~$1.09 / 1,000 jobs)** until 29 September 2026, and **`$0.00199` (~$1.99 / 1,000 jobs)** from 30 September 2026, when `additional-data` moves to $0.001. Rows dropped by the keyword/job filters are never charged — filtering happens before the write.

Enrichment is opt-in and only billed when it **succeeds**: a 1,000-row run with `enrichCompanyProfile` on across ~150 unique companies adds roughly **$0.225**. Failed enrichments cost nothing.

#### Enrichment reliability

- **`enrichCompanyProfile` works again (September 2026).** Measured across three proxy routes: 12 of 12 companies enriched on each. It needs a residential proxy, which is the default; on a datacenter proxy Wellfound shows an interactive check, so the actor skips company enrichment and says so in the log. A company whose page can't be fetched is marked `enrichmentStatus: "failed"` and not charged. Not every company lists funding: in testing about 60% had a non-zero `companyTotalRaised`, all had a website and most had LinkedIn.
- **`enrichJobDetail` is mostly unnecessary.** Jobs from a `/role/` search already include the full description at no extra cost, and the actor skips the enrichment for those rows. It only does anything for `/jobs` landing-page rows. Failed enrichments are not charged.

***

### Field coverage — what every row carries

Coverage depends on which surface you scrape. On a **`/role/` search** (measured over 300 rows, 2026-08-15): `jobTitle`, `jobDescription`, `jobType`, `companyName`, `companySize`, `remote` and `postedAt` are **100%** populated, `companyHighConcept` 98%, `compensation` 83%, `companyStage` 76%, `atsSource` 66%, `yearsExperienceMin` 33%. `marketTags` and `liveJobListingsCount` are not exposed on that surface and come back empty.

On the **`/jobs` landing page**, Wellfound only attaches the full structured graph to featured listings (~4 of ~50 per page); the rest carry the basic job + company-identity fields and leave funding-stage / market-tags / badges / company-size null.

**What this means for you:**

| Always populated (every row) | Sparse — only on featured rows (~8%) |
|---|---|
| `jobId`, `jobTitle`, `jobUrl`, `jobSlug` | `companySize`, `companySizeBand` |
| `compensation` + parsed min/max/currency/equity | `companyHighConcept` |
| `locations[]`, `remote`, `remoteKind`, `wfhFlexible` | `liveJobListingsCount` |
| `primaryRoleSlug`, `postedAt`, `postedAtUnix` | `marketTags[]`, `locationTags[]` |
| `companyId`, `companyName`, `companySlug`, `companyUrl`, `companyLogoUrl` | `companyStage`, `companyStageLabel` |
| `recentlyFunded`, `hasTopInvestors`, `growingFast`, `activelyHiring`, `topResponder`, `quickResponder` (default `false` for non-featured) | `businessType` (B2B / B2C) |
|  | `allBadgeLabels[]` |

To get only the rich-featured rows, filter your output for `companyStage != null` post-run. Across multiple pages the featured set rotates, so paginating to `?page=N` reveals progressively more featured startups.

### Use cases

- **Startup hiring tracker** — paginate `/jobs?market=ai` daily to track which AI startups are hiring fastest, what salary bands they advertise, and which companies hit Wellfound's `GROWING_FAST` or `RECENTLY_FUNDED` flags
- **Sourcer / recruiter tooling** — feed `companySlug` + `companyName` into outbound CRMs to build a "currently hiring in {market}" lead list; the compensation band on every row lets you pre-filter by salary tier
- **Compensation benchmarking** — `compensationMin` / `compensationMax` / `compensationCurrency` / `hasEquity` are parsed from Wellfound's raw band strings, so you can run quantile analysis without re-parsing
- **VC / analyst feeds** — featured-row signals (`companyStage`, `recentlyFunded`, `hasTopInvestors`, `growingFast`) are useful for tracking which startups Wellfound itself flags as venture-backed and growing. Note: investor names + raise amounts aren't published on the listing surface, only the boolean flags
- **Remote-job aggregation** — `remote: true` + `acceptedRemoteLocations[]` lets you filter for region-eligible remote roles
- **Daily "new startup jobs" alerts** — schedule the same search with `incrementalMode: true` + a Telegram/Slack webhook and get pinged only about jobs that appeared or changed since the last run — you pay only for the diff

### Monitoring & alerts — incremental mode + notifications (opt-in)

Turn the scraper into a scheduled monitor. With `incrementalMode: true` the actor remembers every job it has seen for your search (per `stateKey`, stored in your account) and later runs emit **only NEW / UPDATED / REAPPEARED** records — unchanged jobs are detected but not emitted and **not charged**. A daily schedule on a stable search typically bills 80–95% less than re-scraping everything.

- Emitted records carry `monitorStatus` (`NEW` / `UPDATED` / `REAPPEARED`), `monitorFirstSeen`, `monitorLastSeen`
- `emitUnchanged` / `emitExpired` opt the other two statuses back in (expired records are small tombstones with the job title + URL; each emitted record is charged as a normal result)
- `skipReposts: true` suppresses listings that vanish and come back under a new job ID with identical content
- `maxAgeMinutes` keeps only jobs newer than N minutes — pair with a daily schedule (`maxAgeMinutes: 1440`) for a fresh-jobs feed. Wellfound does not list jobs by date, so each run pages until it finds enough recent jobs (up to 25 pages, or 8 pages in a row with none); a very short window such as 70 minutes mostly pays for pages with nothing new
- The first run builds the baseline (everything is NEW); leave `stateKey` empty and one is derived from your search inputs automatically

**Notifications** (work with or without incremental mode): set any of `telegramBotToken` + `telegramChatId`, `slackWebhookUrl`, `discordWebhookUrl`, or `webhookUrl` (+ optional `webhookHeaders`) and each run posts a summary with the top new jobs — straight to your hiring channel, n8n, Make, or Zapier flow. `notifyOnlyChanges: true` keeps scheduled runs silent unless something actually changed. `notificationLimit` caps how many jobs are listed per message (the dataset always has everything).

#### Example scheduled-monitor input

```json
{
  "startUrls": ["https://wellfound.com/jobs?market=ai&location=san-francisco"],
  "maxItems": 300,
  "incrementalMode": true,
  "skipReposts": true,
  "notifyOnlyChanges": true,
  "telegramBotToken": "123456:ABC...",
  "telegramChatId": "@my_hiring_channel"
}
```

### Input

| Field | Type | Required | Notes |
|---|---|---|---|
| `startUrls` | `string[]` | no | **Role search (recommended):** `/role/{role}` (all locations), `/role/r/{role}` (remote only) or `/role/l/{role}/{location}` — paginated, server-side filtered, full descriptions. **Place search:** `/location/{place}` lists every role in one place (San Francisco: about 3,000 jobs) and `/remote` every remote job; both paginate the same way. **Landing page:** `/jobs` (± `?market`/`?industry`/`?location`) returns the same ~50 curated jobs regardless of filters. Leave empty and the actor builds role searches from your `keyword` + `location`; list several roles with commas (`data analyst, data engineer`) and each gets its own role search, sharing `maxItems` between them. A single job (`/jobs/{id}`) or company page (`/company/{slug}`) is not a listing and is skipped with a message. |
| `maxItems` | `integer` | no | Maximum job rows emitted **per startUrl**. 3 listings × `maxItems: 200` → up to 600 total rows (200 each). Each row = one paid `additional-data`. Default `1000`. Free-tier users have a hidden global ceiling of `100` rows. |
| `maxConcurrency` | `integer` | no | Parallel HTTP requests across listing URLs. Sweet spot 3–5 via Apify Residential US. Default `4`. |
| `maxRequestRetries` | `integer` | no | Retry budget per page. Each retry uses a fresh proxy session (a new exit IP); an occasional retry uses a real browser. A page gets at most 150 s in total. Default `10`. |
| `proxy` | object | no | Leave at the default and the actor picks a **US residential** exit (Wellfound blocks most datacenter IPs on `/role/` pages). On free-plan runs, an explicit proxy group or custom proxy URLs are used exactly as given; paid runs use the actor's own residential pool. |
| `incrementalMode` | `boolean` | no | Only emit (and charge) NEW / UPDATED / REAPPEARED records vs the previous run of the same search. See the Monitoring section. Default `false`. |
| `stateKey` / `emitUnchanged` / `emitExpired` / `skipReposts` / `maxAgeMinutes` | mixed | no | Incremental-mode tuning — tracked-universe name, opt-in unchanged/expired records, repost suppression, freshness cutoff. |
| `telegramBotToken` + `telegramChatId`, `slackWebhookUrl`, `discordWebhookUrl`, `webhookUrl` (+`webhookHeaders`) | `string` | no | Post a run summary with the top new jobs to any of these channels. `notifyOnlyChanges` silences no-change runs; `notificationLimit` caps listed jobs per message (1–20, default 5). |

#### Example input

```json
{
  "startUrls": [
    "https://wellfound.com/jobs",
    "https://wellfound.com/jobs?market=ai",
    "https://wellfound.com/jobs?location=san-francisco"
  ],
  "maxItems": 200,
  "maxConcurrency": 4,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

`maxItems` is an upper bound, not a target, and what you actually get depends on the URL. A **`/role/…`** URL paginates for real — `/role/r/software-engineer` reports 1,855 matching jobs across 47 pages, and the scraper reads that page count and stops exactly at the end. A **`/jobs`** URL exposes roughly **50 distinct jobs** and serves that same set for every `?page=`/`?market=`/`?location=` value, so the scraper stops as soon as a page returns nothing new. Either way rows are de-duplicated by job ID across the whole run — you are never charged for a repeat.

### Output schema

One flat row per `JobListing`. All identity + compensation + remote + posted fields are populated on every row; the structured "featured" fields are sparse (see honest note above).

```jsonc
{
  // ── Job identity ──
  "jobId":           "4246042",
  "jobUrl":          "https://wellfound.com/jobs/4246042-sales-development-representative",
  "jobSlug":         "sales-development-representative",
  "jobTitle":        "Sales Development Representative",

  // ── Compensation (parsed best-effort from Wellfound's free-text band) ──
  "compensation":         "$40k – $80k • No equity",
  "compensationMin":      40000,
  "compensationMax":      80000,
  "compensationCurrency": "USD",
  "hasEquity":            false,

  // ── Location + remote ──
  "locations":               ["Dubai", "New York City", "San Francisco", "London", "Paris", "Bengaluru"],
  "acceptedRemoteLocations": [],
  "remote":                  true,
  "remoteKind":              "REMOTE",       // REMOTE / ONSITE / HYBRID
  "wfhFlexible":             false,

  // ── Role + time ──
  "primaryRoleSlug":  "sales-development-3",
  "postedAt":         "2026-05-22T06:49:02.000Z",
  "postedAtUnix":     1779432542,

  // ── Company identity (denormalized — always populated) ──
  "companyId":         "10559109",
  "companyName":       "CredShields Technologies",
  "companySlug":       "credshields-blockchain-security",
  "companyUrl":        "https://wellfound.com/company/credshields-blockchain-security",
  "companyLogoUrl":    "https://photos.wellfound.com/startups/i/10559109-….jpg",

  // ── Company structured data (FEATURED ROWS ONLY — ~8% of rows have these) ──
  "companySize":           "SIZE_201_500",       // null on non-featured rows
  "companySizeBand":       "201-500 employees",   // null on non-featured rows
  "companyHighConcept":    "Unlock the potential of every investment partnership",  // null on non-featured rows
  "liveJobListingsCount":  56,                    // null on non-featured rows — total open jobs at this company
  "marketTags":            ["Real Estate Tech", "B2B Software"],   // [] on non-featured rows
  "locationTags":          ["San Francisco"],     // [] on non-featured rows

  // ── Wellfound badge signals (FEATURED ROWS ONLY) ──
  "companyStage":      "scale_stage",        // "early_stage" / "scale_stage" / null
  "companyStageLabel": "Scale Stage",         // human label / null
  "recentlyFunded":    false,                  // raised in last 6 months (boolean)
  "hasTopInvestors":   true,                   // Wellfound flagged top investors (boolean)
  "growingFast":       true,                   // strong hiring growth in last month (boolean)
  "activelyHiring":    true,                   // actively processing applications (boolean)
  "topResponder":      false,                  // top 1% of responders (boolean)
  "quickResponder":    false,                  // responds within a day (boolean)
  "businessType":      "B2B",                  // "B2B" / "B2C" / null
  "allBadgeLabels":    ["Actively Hiring", "B2B", "Scale Stage", "Top Investors", "Growing fast"],

  // ── Meta ──
  "sourcePageUrl":    "https://wellfound.com/jobs",
  "sourcePageIndex":  1,
  "scrapedAt":        "2026-05-22T09:13:58.526Z"
}
```

### Key output fields

- **`jobId` + `jobUrl`** — stable Wellfound identifiers. `jobUrl` is the canonical permalink; useful as a join key even though Wellfound itself blocks HTTP fetches of the URL
- **`compensation` parsed band** — `compensationMin` / `compensationMax` come from Wellfound's free-text strings via a best-effort regex. `null` when Wellfound shows "Competitive" or omits a band entirely
- **`remote` + `remoteKind` + `wfhFlexible`** — three flags so you can distinguish "fully remote", "on-site with WFH flexibility", and "hybrid"
- **`postedAt` ISO + `postedAtUnix`** — the moment Wellfound first published the listing (`liveStartAt` in their schema)
- **Company "rich" fields** — only populated on featured rows. The honest note above explains why — Wellfound only attaches the structured graph to paid-promoted listings, and we report what Wellfound actually serves rather than padding non-featured rows with fake data

### How it works

1. **Resolve the search** — pasted `/role/…` URLs are checked against Wellfound's own list of roles and locations (a misspelt slug is corrected to the closest real one); with no URLs, `keyword` + `location` become one role search per listed role, and a term with no role of its own searches the broader role it contains (`performance marketing` → marketing) with the term kept as a filter there
2. **Fetch each page** by racing several HTTP stacks on a fresh proxy session; a blocked attempt retries on a new exit IP, with an occasional real-browser attempt, inside a time budget per page
3. **Check what Wellfound served** — an unknown role redirects to a generic job list, which is detected and skipped rather than billed
4. **Parse `__NEXT_DATA__`** — one `JSON.parse` resolves the Apollo graph (`JobListingSearchResult`, `StartupResult`, `Badge`, remote settings) into one flat row per job with the company denormalized
5. **Filter while paginating** — keyword, title, seniority, pay, remote and age filters run on each page, so `maxItems` counts rows you keep; a narrow filter that keeps under a quarter of the jobs it scans stops after about 25 pages (more for a large `maxItems`) rather than walking the whole role
6. **Push and charge** each kept row once; filtered and duplicate rows are never charged

### Free Apify plan

On the free Apify plan, the first 3 jobs of each run come back in full, so you can check the data. After that, rows keep the job title, company name, Wellfound links, logo, size, stage, badges, locations, remote flags and posting date, while compensation and equity, base salary, the funding history (total raised is rounded), founders, the company's website and social links, the recruiting contact, the description, benefits and apply URL are left empty (the description snippet is cut to a few characters), and the row carries a `freePlanNote`. Contact emails from the email add-on stay visible. Monitoring still compares full rows. Any paid Apify plan returns every field.

### FAQ

**Why are the company-size / badge fields empty on some rows?**
On `/role/` searches every row carries company size, one-liner and badges (measured 100% on 300 rows). On the `/jobs` landing page Wellfound only attaches the full company payload to featured listings — roughly 4 of every 50 — and the rest get the lean shape (`id`, `name`, `slug`, `logoUrl`). We surface what Wellfound serves; we don't fabricate data. Market tags are empty on role-search rows because Wellfound does not send them there.

**Do I get the full job description?**
Yes, on every row from a `/role/` search, at no extra cost. `/jobs` landing-page rows carry only a snippet; `enrichJobDetail` fetches the full job page for those.

**Is the actor US-only?**
With the default proxy setting the actor uses a US residential exit. Wellfound blocks most datacenter IPs on its `/role/` search pages: in September 2026, free runs left on the plain automatic proxy were blocked about one time in four, while runs on residential were not. An account without Residential access still runs; the actor falls back to the automatic proxy and says so in the log.

**I have an input saved for another Wellfound actor. Will it run here?**
Yes, the common field names are the same: `startUrls` as plain URL strings, `keyword`, `jobTitle`, `location`, `remoteOnly`, `jobType`, `experience`, `minSalary`, `maxSalary`, `includeNoSalary`, `companyCategories`, `includeCompanies`, `excludeCompanies`, `sort` and `maxItems`. Start URLs on `/jobs`, `/jobs?remote=true`, `/location/{place}` and `/role/l/{role}/{place}` are all accepted, and `location` next to start URLs filters the rows as it did there. Two differences to know: `maxItems` counts **per start URL** and defaults to 1,000, so set it if you relied on a lower default; and a `/location/` or `/role/` URL returns every matching job Wellfound has up to `maxItems`, where a single landing page stops at about 50.

**How many jobs can I actually get?**
It depends entirely on the URL.

**`/role/` search — thousands.** `/role/software-engineer` reports about 4,970 matching jobs across 91 pages (all locations); the remote-only `/role/r/software-engineer` about 1,850. Pages are genuinely different from one another and role+location combinations filter server-side, so `/role/l/software-engineer/san-francisco` and `/role/l/product-manager/new-york` share no results at all. This is the surface to use. A keyword with no start URL builds the all-locations search, or the remote-only one when `remoteOnly` is on.

**`/jobs` landing page — about 50.** It is an SEO page built from a curated `seoLandingPageFeaturedJobs` set and returns the **same ~50 jobs** for `?page=2`, `?page=500`, `?market=ai`, `?location=san-francisco` and `?industry=fintech` alike. (An earlier version of this README claimed pagination ran to page 500 — every page did return 200 OK with full Apollo state, which is what made it look like it worked, but the contents are identical.)

Either way the scraper de-duplicates by job ID across the entire run, so you are never billed twice for the same job.

**What happens if Wellfound changes the Apollo schema?**
The parser emits a structured warning (`apolloState.data missing or malformed`) and the page is treated as the end of pagination — noisy degradation rather than silent breakage. You'll see the warning in the log immediately on the first run.

**How do I get only remote jobs, or filter by salary?**
Set `remoteOnly: true` to keep only remote roles (with a keyword and no start URL it also searches Wellfound's remote-only listing), and/or `minSalary` / `maxSalary` to keep jobs in a pay band. Pay filters are applied after the scrape but **before charging**, so you never pay for filtered-out rows. Jobs with no listed compensation are kept (not dropped just for missing pay).

**Why does my narrow filter stop early?**
With filters or `maxAgeMinutes` on, a URL is scanned for at most 25 pages (more when `maxItems` is large enough to need them), or until 8 pages in a row add nothing. Wellfound does not list jobs by date, so a very rare combination could otherwise read every page of a big role. The log says when this happens and how many jobs were scanned.

#### More filters

Every filter below runs the same way — client-side on the fetched rows, **before charging**:

| Input | What it does |
|---|---|
| `location` | On its own (no keyword, no start URLs) it runs Wellfound's search of every role in that place (`/location/london`). With a keyword it narrows the role search (`/role/l/{role}/{place}`). With start URLs, or a place Wellfound has no page for, it filters rows: a job is kept when its listed locations include the place (`NYC` also matches New York). `remote` switches on `remoteOnly` |
| `jobTitle` | Keeps jobs whose **title** contains the word or phrase (whole words: `intern` matches Internship, not Internal). Narrower than `keyword` (which also searches company, tags and description). Separate several with commas, semicolons or "or" |
| `jobType` | `full-time` / `part-time` / `contract` / `internship`. On `/role/` URLs this uses Wellfound's structured employment type, so it is exact. On `/jobs` URLs it falls back to whole words in the job title |
| `experience` | `junior` / `mid` / `senior` / `executive`, read from the level the **job title** states (Senior/Lead/Principal/Staff; Head of/Director/VP/Chief/Founder; Junior/Entry-level/Graduate/Intern/Associate). When the title states none, Wellfound's years-of-experience decides (0–2 junior, 3–4 mid, 5+ senior); a title with neither counts as mid-level |
| `includeNoSalary` | Default `true`. Set `false` to keep only jobs that list pay |
| `companyCategories` | Keep companies whose market tags / one-liner / business type match, e.g. `["saas","fintech"]` |
| `includeCompanies` | Allow-list of company names or slugs |
| `excludeCompanies` | Block-list — takes precedence over the allow-list |
| `sort` | `newest` / `oldest` by posted date. Orders the **whole run**, not just each page — the actor buffers all rows before writing when sorting is on. Jobs with no posted date go last |

`experience` is a narrowing heuristic based on how the job is titled, not an exact taxonomy. The job description is deliberately not searched for it: words like "leadership" and "director" appear in almost every posting and would make the filter meaningless.

### Support

Issues / feature requests / pricing questions — open a ticket on the [Apify console](https://console.apify.com/) issues tab for this actor.

### Additional services

Apify hosts a portfolio of complementary actors covering related surfaces. If you need scrapers for LinkedIn jobs, Indeed, Glassdoor reviews, or AmbitionBox employee feedback, check the same author's other listings.

### Explore more scrapers

If Wellfound covers your startup-job needs, you may also be interested in:

- **AmbitionBox Reviews Scraper** — employee reviews from India's leading review platform
- **TotalJobs Scraper** — UK-wide job listings from the Stepstone Group brands
- **Expedia Hotel Reviews Scraper** — Expedia + Hotels.com reviews with sort/filter URN support

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/wellfound-jobs-scraper`).

**Purpose:** scrape startup job listings from Wellfound.com (formerly AngelList Talent) role searches — one flat row per job with the full description, compensation, remote flags, employment type, years of experience and denormalized company identity and badges.

**Minimal input:**

```json
{ "keyword": "data engineer", "location": "london", "maxItems": 50 }
```

or `{ "startUrls": ["https://wellfound.com/role/data-engineer"], "maxItems": 50 }`. Several roles in one keyword (`"data engineer, data analyst"`) run one search each and share `maxItems`. A term that is not a Wellfound role but contains one (`"performance marketing"`) searches that broader role (`/role/marketing`) and keeps only the jobs that mention the term. `remoteOnly: true` searches the remote-only listing.

**Output:** one row per job — `jobId, jobUrl, jobSlug, jobTitle, compensation, compensationMin, compensationMax, compensationCurrency, hasEquity, locations[], acceptedRemoteLocations[], remote, remoteKind, wfhFlexible, primaryRoleSlug, postedAt, postedAtUnix, companyId, companyName, companySlug, companyUrl, companyLogoUrl, sourcePageUrl, scrapedAt`. Featured rows also carry `companySize, companySizeBand, companyHighConcept, liveJobListingsCount, marketTags[], locationTags[], companyStage, companyStageLabel, recentlyFunded, hasTopInvestors, growingFast, activelyHiring, businessType, allBadgeLabels[]` (null/empty on non-featured rows).

**Behaviors an agent should know:**

- `maxItems` is **per startUrl** (default 1000) and is a ceiling, not a target — rows are de-duplicated by job ID across the whole run. A `/role/…` URL can supply thousands of jobs; a `/jobs` URL exposes about 50. Free-tier runs are additionally capped at 100 rows total.
- `/role/{role}` (all locations), `/role/r/{role}` (remote only), `/role/l/{role}/{location}`, `/location/{place}` (every role in one place), `/remote` and `/jobs[?…]` URLs are accepted. `?remote=true` on a pasted URL keeps only that URL's remote jobs. `location` next to start URLs filters rows by their listed locations. A single job (`/jobs/{id}`) or company page (`/company/{slug}`) is not a listing and is skipped with a message. A role Wellfound does not publish is detected and skipped (Wellfound redirects it to a generic list).
- `keyword` (comma = OR; whole-word match), `jobTitle`, `jobType`, `experience`, `remoteOnly`, `minSalary`, `maxSalary`, `includeNoSalary`, `companyCategories`, `includeCompanies`, `excludeCompanies`, `maxAgeMinutes` filter rows while paginating, before charging — you never pay for filtered-out rows, and `maxItems` counts kept rows. `sort` orders the whole run by posted date.
- `enrichCompanyProfile` (funding, founders, socials — charged once per unique company, de-duped) and `enrichJobDetail` (full description, structured salary) are opt-in; failed enrichments are never charged. `enrichEmails` is experimental, billed per email found.
- Billing: `apify-actor-start` $0.01 per GB of memory per run; each job row is `apify-default-dataset-item` $0.00099 plus `additional-data` ($0.0001 until 29 Sep 2026, $0.001 from 30 Sep 2026); `company-enrichment` $0.0015 per successful unique company; `job-detail-enrichment` $0.0015 per successful job detail; `enrichEmails` $0.05 per email found. Failed enrichments and filtered rows are never charged.

### ⚠️ Disclaimer

This actor reads only publicly accessible Wellfound pages: the `/role/` job search, the `/jobs` listing, and (when you turn enrichment on) public job and company pages. It does not log in, use accounts, access private data, or solve interactive CAPTCHAs — when Wellfound asks for one, that page is skipped. You are responsible for compliance with Wellfound's Terms of Service in your jurisdiction. The scraper is provided "as is" without warranty; use is at your own risk.

### SEO Keywords

wellfound scraper, wellfound jobs scraper, angellist talent scraper, angellist jobs scraper, startup job scraper, startup hiring scraper, wellfound api, wellfound listings scraper, vc-backed startup jobs, ai startup jobs scraper, fintech startup jobs scraper, san francisco startup jobs scraper, remote startup jobs scraper, wellfound salary scraper, wellfound compensation scraper, startup sourcing tool, recruiter sourcing scraper, hiring intelligence scraper, wellfound data export, apify wellfound, wellfound csv export, recently funded startup jobs, growing startups jobs scraper, top investor startups, scale stage startups, early stage startups jobs, wellfound market filter, wellfound industry filter, wellfound location filter, datadome bypass wellfound, headless wellfound scraper

# Actor input Schema

## `startUrls` (type: `array`):

Paste one or more Wellfound URLs. Role searches are the surface to use:

**✅ Role search — volume and real filtering**
• `https://wellfound.com/role/software-engineer` — every job for a role, **all locations** (about 5,000 for software engineer)
• `https://wellfound.com/role/r/software-engineer` — the same role, **remote jobs only**
• `https://wellfound.com/role/l/product-manager/new-york` — role + location
• `https://wellfound.com/location/san-francisco` — **every role in one place** (about 3,000 jobs for San Francisco)
• `https://wellfound.com/remote` — every remote job, any role

These paginate for real and are filtered by Wellfound itself, and every row comes back with the **full job description**, structured employment type and years-of-experience — no enrichment needed. A role Wellfound does not publish is detected and skipped (Wellfound would otherwise answer with an unrelated generic job list), and a misspelt role is corrected to the nearest real one where there is a clear match.

**⚠️ Landing page — limited**
• `https://wellfound.com/jobs` (with or without `?market=` / `?industry=` / `?location=`)

This is an SEO landing page: it returns the same **~50 curated jobs** whatever filters or `?page=` you give it. Fine for a quick sample, useless for coverage.

**❌ Not start URLs:** a single job (`/jobs/{id}-{slug}`) or a company page (`/company/{slug}`) is not a job listing. For company funding, founders and social links, run a role search with **Enrich company profile** on.

Rows are de-duplicated by job ID across the whole run, so overlapping URLs never bill twice.

**Leave empty to search by keyword and location instead.**

## `keyword` (type: `string`):

**No start URL?** Then the keyword picks the search itself: a role name such as `product designer` becomes Wellfound's own role search (`/role/product-designer`, all locations, or the remote-only `/role/r/product-designer` when **Remote jobs only** is on), which reaches the full job pool instead of the ~50-job landing page. List several roles with commas (`data analyst, data engineer, data scientist`) and each gets its own search, sharing your `maxItems` between them. A term that is not a Wellfound role but contains a broader one (`performance marketing`, `B2B sales`) searches that broader role (`/role/marketing`, `/role/sales`) and keeps the term as a filter there, so you get the jobs that mention it. Other words that are not a Wellfound role are left out of the search and named in the log. A role with "intern" in it (`machine learning intern`) searches that role and keeps internships only. Used this way, the keyword is not re-applied as a text filter, except on those broader-role searches.

**With start URLs**, the keyword is a filter: a case-insensitive whole-word match (a word may carry a common ending, so `engineer` also finds "engineering" and `intern` finds "internship" but not "internal"), across job title, role slug, company name, market tags, location tags, badge labels, and (when enrichment is on) job description. Rows that don't match are filtered out before being written to the dataset.

Examples:
• `rust` → keeps rows mentioning Rust
• `rust, golang, scala` → keeps rows mentioning ANY of the three (OR logic)
• `senior` → keeps senior-level roles

**How well this works depends on your start URL.** On a **`/role/…`** URL the scraper searches thousands of jobs and the full job description is included in the match, so even a narrow keyword has a real corpus to hit. On a **`/jobs`** URL it is filtering a curated set of only ~50 jobs, so a narrow keyword (`Cloud Security GRC Specialist`) will usually match nothing at all — not a failure, just a tiny pool. The run log says so explicitly when it happens.

**Cost behaviour**: filtering happens after fetching, so compute is unchanged — but result charges only fire for rows that pass the filter. You never pay for rows you filter out, and `maxItems` counts kept rows, not fetched ones.

Leave empty to disable filtering.

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

Keep only jobs flagged remote. With a keyword and no start URLs this also switches the search to Wellfound's remote-only listing (`/role/r/…`). Applied before charging — you don't pay for filtered-out rows.

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

Keep only jobs whose compensation band reaches at least this amount, e.g. 120000. Jobs with no listed compensation are kept (never dropped just for missing pay). Applied before charging.

## `maxSalary` (type: `integer`):

Keep only jobs whose compensation band starts at or below this amount, e.g. 250000. Jobs with no listed compensation are kept. Applied before charging.

## `jobTitle` (type: `string`):

Keep only jobs whose <b>title</b> contains this word or phrase (case-insensitive, whole words — <code>intern</code> matches "Intern" and "Internship" but not "Internal"). Narrower than <b>Keyword</b>, which also searches company name, tags and description. Comma-separated for OR: <code>engineer, developer</code>.

## `jobType` (type: `string`):

On **`/role/…`** URLs this matches Wellfound's own structured employment-type field, so it is exact. On **`/jobs`** URLs no such field exists, so it falls back to matching the job title/role text.

## `experience` (type: `string`):

Uses the level the **job title** states: Senior / Lead / Principal / Staff → senior; Head of / Director / VP / Chief / Founder → executive; Junior / Entry-level / Graduate / Intern / Associate → junior. When the title states no level, Wellfound's structured years-of-experience decides where it is given (0–2 years junior, 3–4 mid, 5+ senior). A title with neither counts as mid-level. The job description is not searched for this: words like "leadership" or "director" appear in almost every posting and would make the filter meaningless.

## `includeNoSalary` (type: `boolean`):

Wellfound publishes many roles with no compensation shown. Leave on to keep them; turn off to get only jobs with pay data.

## `companyCategories` (type: `array`):

Keep only companies whose market tags, one-liner or business type mention any of these (case-insensitive, whole words; hyphens and spaces are interchangeable). Example: <code>saas</code>, <code>fintech</code>, <code>artificial intelligence</code>.

## `includeCompanies` (type: `array`):

Allow-list of company names or Wellfound slugs (case-insensitive substring match).

## `excludeCompanies` (type: `array`):

Block-list of company names or slugs. Takes precedence over the allow-list above.

## `equityMin` (type: `number`):

Keep only jobs whose equity range reaches at least this percent (compared with the TOP of the range, e.g. 0.1%–0.5% passes a 0.3 floor). Jobs that list no equity are dropped when this is set. Leave empty for no filter.

## `visaSponsorship` (type: `string`):

Read from the job description. 'Only jobs that offer sponsorship' keeps postings that say so explicitly; 'Drop jobs that refuse sponsorship' removes postings that explicitly rule it out and keeps the rest. Detection is conservative: postings that say nothing clear are treated as unknown. /role/ search URLs carry full descriptions; /jobs URLs need 'Enrich job detail' for full text.

## `sort` (type: `string`):

Orders the <b>whole run</b> (not just each page) — the actor buffers all rows before writing when sorting is on. Jobs with no posted date are placed last.

## `market` (type: `string`):

Wellfound market slug. Examples: `ai`, `fintech`, `web3`, `saas`, `cyber-security`, `developer-tools`, `mobile`, `e-commerce`, `consumer`, `b2b`. Used to build `https://wellfound.com/jobs?market={value}` when no `startUrls` is provided. See the section note above — Wellfound currently ignores this filter server-side.

## `industry` (type: `string`):

Wellfound industry slug. Examples: `real-estate-tech`, `cyber-security`, `blockchain-cryptocurrency`, `health-care`, `education`, `gaming`. Used to build `https://wellfound.com/jobs?industry={value}` when no `startUrls` is provided. Note: `market` and `industry` overlap; pick one.

## `location` (type: `string`):

City or region, e.g. `new-york`, `san-francisco`, `london`, `berlin`, `bangalore`, `europe`, `united-states`. Together with a keyword (and no start URLs) it narrows the role search to that place (`/role/l/product-designer/new-york`). **On its own** (no keyword, no start URLs) it runs Wellfound's search of every role in that place (`/location/london`, about 1,000 jobs), limited by `maxItems`. **With start URLs**, or with a place Wellfound has no page for, it is a filter: a job is kept when its listed locations include the place you typed (`NYC` also matches New York), and the rows it removes are never charged. `remote` is not a place on Wellfound: it switches on **Remote jobs only**, which is a real filter. A start URL pasted with `?remote=true` is treated the same way for that URL.

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

Upper bound on job rows emitted **per startUrl** — a ceiling, not a target. Each row is one paid event, and you are only ever charged for **unique** jobs.

How much data actually exists depends entirely on the URL you paste:
• **`/role/…` URLs** — thousands. `/role/r/software-engineer` reports 1,855 matching jobs across 47 pages; the scraper reads the page's own page count and stops exactly at the end. Raise `maxItems` here and you really do get more.
• **`/jobs` URLs** — roughly **50 unique jobs**, and the same 50 regardless of `?page=` or any filter. Setting `maxItems: 5000` on a `/jobs` URL still returns ~50; the scraper stops as soon as a page yields nothing new rather than billing you for repeats.

Default 1000. Free-tier users are additionally capped at 100 total rows across the whole run.

## `maxConcurrency` (type: `integer`):

Parallel HTTP requests across listing URLs. Wellfound's listing surface is friendly; concurrency 3-5 is the sweet spot. Higher values risk rate-limiting on the residential proxy pool, not on Wellfound's edge.

## `maxRequestRetries` (type: `integer`):

Per-URL retry budget. Each retry rotates the proxy session with mild exponential backoff and re-races three HTTP stacks (impit Firefox / gotScraping / impers Chrome142). Default 10 — Wellfound's listing surface is reliable enough that ≤3 retries usually suffice, but the budget is there for proxy hiccups.

## `enrichCompanyProfile` (type: `boolean`):

💰 **Pricing**: $0.0015 per unique company successfully enriched ($1.50 per 1,000 companies), on top of the row charge. De-duplicated: a company with 56 jobs = 1 enrichment charge, not 56. Failed enrichments are NOT charged. Example: 1,000 rows across 150 unique companies ≈ $1.09 for the rows (≈ $1.99 from 30 Sep 2026) + ≈ $0.23 for 150 companies.

Every job row also includes the company's:

• Total funding raised (e.g. Airbnb: $11.3B)
• Funding-round history — Series A/B/C, IPO, dates
• Founders — names, slugs, avatars
• Social URLs — Twitter, LinkedIn, Facebook, blog, ProductHunt
• Official company website
• Operating / incubator / shell flags + Wellfound's profile-completeness score

**How it works:** company pages sit behind a Cloudflare check that plain HTTP clients never pass, so the actor opens one real browser session on a residential IP, lets the check complete, then fetches each company with that session (about 1-2 s per company). It needs a residential proxy — the default. On a datacenter proxy Wellfound asks for an interactive check, so company enrichment is skipped (and not charged).

Reliability (September 2026): 12 of 12 companies enriched on each of three proxy routes in testing. Not every company lists funding; websites and LinkedIn are almost always present.

## `enrichJobDetail` (type: `boolean`):

⚠️ **You probably don't need this.** Jobs scraped from a **`/role/…`** search already include the **full job description**, structured employment type and years-of-experience, at no extra charge — this toggle skips them and they are never billed for it.

It only does something for **`/jobs`** landing-page rows, where the listing carries just a snippet: the actor fetches each job's own page for the full description and structured salary. Failed fetches are never charged, and the actor stops trying if most of the first job pages fail.

💰 Pricing when it does fire: $0.0015 per job enriched ($1.50 per 1,000), on top of the row charge.

**Recommended instead:** paste a role URL such as `https://wellfound.com/role/software-engineer` and leave this off.

## `enrichEmails` (type: `boolean`):

If enabled, tries to find a contact email for each company by reading its website's contact/about pages (e.g. info@, careers@). Adds contactEmail + contactWebsite columns plus a detailed emailEnrichment object. Best-effort, billed per contact email found; only charged when an email is returned, never for misses.

## `incrementalMode` (type: `boolean`):

Track results across runs and only emit (and charge for) NEW, UPDATED, and REAPPEARED records. First run builds the baseline; schedule the same input daily/hourly and pay only for the diff.

## `stateKey` (type: `string`):

Stable name for the tracked search universe. Leave empty to derive one automatically from your search inputs. Set it explicitly if you tweak filters but want to keep the same history.

## `emitUnchanged` (type: `boolean`):

In incremental mode, also push records that haven't changed since the last run (each pushed record is charged as a normal result).

## `emitExpired` (type: `boolean`):

In incremental mode, push a small tombstone record when a previously-seen result disappears (charged as a normal result).

## `skipReposts` (type: `boolean`):

In incremental mode, skip listings that look like reposts of previously-expired ones (same content under a new ID).

## `maxAgeMinutes` (type: `integer`):

Only keep jobs posted within this many minutes. Handy for scheduled runs, e.g. 1440 for a daily schedule. Only in-window jobs count toward <b>maxItems</b>, so the actor keeps paging to find them (Wellfound does not list jobs by date), up to a scan limit of 25 pages per URL. Older jobs are skipped and never charged.

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

Bot token from @BotFather. Required together with the chat ID for Telegram alerts.

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

Chat or channel that receives the alerts, e.g. "-1001234567890" or "@mychannel". The bot must be a member.

## `slackWebhookUrl` (type: `string`):

Slack incoming-webhook URL (api.slack.com/messaging/webhooks).

## `discordWebhookUrl` (type: `string`):

Discord webhook URL (Server Settings → Integrations → Webhooks).

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

Any endpoint (n8n, Make, Zapier, your API) — receives a JSON POST with run metadata, diff counts, and the notable records.

## `webhookHeaders` (type: `object`):

Optional headers sent with the generic webhook POST, e.g. {"Authorization": "Bearer xyz"}.

## `notificationLimit` (type: `integer`):

How many records to list inside each alert message (1–20). Everything is always in the dataset regardless.

## `notifyOnlyChanges` (type: `boolean`):

With incremental mode on, stay silent when a run finds nothing new or updated.

## `maxUnblockerRequests` (type: `integer`):

When the free HTTP + browser lanes are blocked by DataDome, the actor falls back to a paid WAF-bypass chain to reliably fetch the page. This caps how many metered requests (scrape.do, scrapingbee) a single run may make, bounding cost. GeoNode leads the chain and is on an unlimited plan, so its pages do NOT count toward this cap. Default 0 — the free lanes have been enough; raise it if a run ends empty with `[FETCH] free lanes exhausted` in the log.

## `proxy` (type: `object`):

Leave as-is. With the default setting the actor picks the proxy for you: a US residential exit, because Wellfound blocks most datacenter IPs on its search pages (runs on the plain automatic proxy were being blocked about one time in four). On free-plan runs, a proxy group or custom proxy URLs you choose here are used exactly as given. Paid runs always use the actor's own residential pool.

## Actor input object example

```json
{
  "startUrls": [
    "https://wellfound.com/role/software-engineer",
    "https://wellfound.com/role/l/product-manager/new-york"
  ],
  "remoteOnly": false,
  "jobType": "any",
  "experience": "any",
  "includeNoSalary": true,
  "visaSponsorship": "any",
  "sort": "none",
  "maxItems": 1000,
  "maxConcurrency": 4,
  "maxRequestRetries": 10,
  "enrichCompanyProfile": false,
  "enrichJobDetail": false,
  "enrichEmails": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "skipReposts": false,
  "notificationLimit": 5,
  "notifyOnlyChanges": false,
  "maxUnblockerRequests": 0,
  "proxy": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "startUrls": [
        "https://wellfound.com/role/software-engineer",
        "https://wellfound.com/role/l/product-manager/new-york"
    ],
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/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 = {
    "startUrls": [
        "https://wellfound.com/role/software-engineer",
        "https://wellfound.com/role/l/product-manager/new-york",
    ],
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/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 '{
  "startUrls": [
    "https://wellfound.com/role/software-engineer",
    "https://wellfound.com/role/l/product-manager/new-york"
  ],
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call memo23/wellfound-scraper --silent --output-dataset

```

## MCP server setup

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