Wellfound Jobs Scraper — Salary Breakdown & Benefits avatar

Wellfound Jobs Scraper — Salary Breakdown & Benefits

Pricing

from $4.99 / 1,000 results

Go to Apify Store
Wellfound Jobs Scraper — Salary Breakdown & Benefits

Wellfound Jobs Scraper — Salary Breakdown & Benefits

Wellfound Jobs Scraper: Extract job titles, salary ranges, equity, benefits, locations, experience, company details, and job URLs. Analyze compensation, benefits, hiring trends, and startup opportunities for recruitment and market research.

Pricing

from $4.99 / 1,000 results

Rating

0.0

(0)

Developer

API Empire

API Empire

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

15 days ago

Last modified

Share

Wellfound Jobs Scraper — Salary, Equity and Benefits as JSON

Wellfound Jobs Scraper pulls startup job listings from Wellfound (formerly AngelList Talent) and returns each posting as three linked data types: the job listing itself, its parsed pay band, and its structured benefits list. Salary numbers come from the job page's own schema.org baseSalary object — not a regex guess over a "175K-275K" label — so every salaryMin/salaryMax/salaryCurrency/salaryPeriod traces back to what the employer actually published. Every response is typed, normalized JSON: no HTML, no CSS selectors, no parsing step. Point it at a market, a role, a single employer, or a list of Wellfound URLs, and you get back a dataset you can filter by pay range before you ever open it.

What is Wellfound Jobs Scraper — Salary Breakdown & Benefits?

It is an Apify Actor that scrapes Wellfound job search results and individual job pages, and splits each posting's compensation block into machine-readable fields instead of leaving it as one display string. No Wellfound account or login is required — the Actor only reads pages that are publicly visible to any visitor. It needs no browser: every payload it reads — the search pages' Apollo cache and each job page's schema.org block and rendered Perks card — is already in the server-rendered HTML, so the Actor fetches them directly over an Apify proxy and handles Wellfound's anti-bot layer (session rotation, proxy-tier step-down, bounded backoff) without you managing any of that yourself.

  • Scrape job listings by market, role, employer, or a pasted list of Wellfound URLs
  • Get exact salary bands (salaryMin, salaryMax, salaryCurrency, salaryPeriod) parsed from the posting's own published pay object
  • Get the equity clause and the full benefits list, split into tags when Wellfound's own text is a tag list
  • Filter the whole run on real salary numbers, not keyword matches, before you're billed for a row
  • Export as JSON, CSV, Excel, XML or RSS straight from the Apify dataset — no parsing on your end

What data does Wellfound Jobs Scraper collect?

The dataset carries three distinct data types in the same run: the job listing, its parsed pay band, and its benefits/equity block. A single charged row merges all three; two additional uncharged row types (skipped, error) and one run-level summary row (run_summary) ride alongside them for auditability.

Data TypeKey FieldsJSON Field Names
Job listingtitle, company, location, employment type, posting age, descriptionjob_title, company_name, location, job_type, date, description, URL
Pay bandexact min/max, currency, pay period, provenance of the numbersalaryMin, salaryMax, salaryCurrency, salaryPeriod, salarySource, salaryDisclosed
Benefits & equitystructured benefit tags parsed from the job page's Perks card, benefit count, verbatim benefits text, equity clausebenefits, benefitsRaw, benefitsParsed, benefitsSource, benefitCount, equityOffered
Run coveragejobs sampled, jobs charged, disclosure and tagging rates, filter breakdownsampleSize, rowsCharged, payDisclosureRate, benefitsTaggedRate, filteredOut, filteredNoSalary

The date field on a job row is a relative label ("3 days ago", "today") computed from the posting's own published timestamp. Since v0.6 that timestamp is also emitted verbatim as datePosted (ISO-8601 UTC), so you can sort and filter on an absolute value; scrapedAt remains the moment the Actor captured the row.

Need more Wellfound data?

This variant deliberately keeps its scope to pay and benefits — it does not collect company size, startup badges, remote-work configuration, years-of-experience requirements, or ATS/auto-post metadata, because those are the focus of sibling Wellfound Actors published under API Empire. If your workflow needs company firmographics alongside the pay data, run this Actor for compensation and pair it with a company-profile-focused Wellfound scraper for the rest.

💰 How salary, equity and benefits actually get parsed

This is the part every "salary scraper" gets vague about, so here is the exact rule.

Salary — two sources, ranked, never blended. The primary source is the baseSalary object inside the schema.org JobPosting block that Wellfound embeds as <script type="application/ld+json"> on the job's own detail page (only there — the search-results page does not carry it). The Actor reads baseSalary.value.minValue, .maxValue, .currency (falling back to .currencyCode, then to value.currency), and .unitText, and normalizes the unit onto a fixed vocabulary (YEAR, MONTH, WEEK, DAY, HOUR); an unrecognized unit is passed through as-is rather than dropped. If baseSalary is a bare number instead of an object, both salaryMin and salaryMax are set to that value. Every number is rounded to the nearest integer, and if the parsed minimum somehow exceeds the maximum the two are swapped defensively.

The fallback — used only when the job page wasn't fetched (fetchPayDetails=false) or carried no baseSalary — parses the cash clause of the posting's own compensation string (e.g. "$100k – $137k • No equity", "₹5.5L – ₹8L"). It reads the segment before the first /·/| so an equity percentage can never be mistaken for a salary, multiplies k/m/L (lakh) / Cr (crore) suffixes, strips thousands separators, and — because Wellfound renders listing-card compensation as an annual figure — defaults the period to YEAR when no unit is otherwise known. Currency in this fallback path comes from a symbol table ($→USD, →EUR, £→GBP, →INR, ¥→JPY, →KRW, C$→CAD, A$→AUD, R$→BRL) or a bare 3-letter ISO code found in the text.

Every row carries salarySource so you can audit which path produced the number: published_ldjson, parsed_from_compensation_text, or none.

Equity is pulled by extract_equity() from the same listing-card compensation string, split on /·/|. It returns the first segment that contains the word "equity" verbatim, or a bare leading percentage range (Wellfound's shorthand for a "0.10% – 0.50%" equity band with no explicit label). It is never invented — most Wellfound postings carry no equity statement at all, and those return null.

Benefits — the Perks card first, the flattened string only as a fallback. The jobBenefits value inside the schema.org block is a flattened concatenation of every perk's title and its free-text description, so it cannot be split back into tags reliably — an employer who writes "Time off when you need it - Flexible vacation and sick policy, 12 weeks of paid parental leave…" produces one unsplittable blob. The same job page also renders those perks as discrete name + description pairs in its "Perks" card, and that is what the Actor parses: each perk's title becomes an entry in benefits[], benefitCount is the number of perks, and benefitsRaw keeps the publisher's verbatim jobBenefits text so nothing is lost. Rows produced this way carry benefitsSource: "perks_section" and benefitsParsed: true.

If a job page carries no Perks card but does carry a jobBenefits string, the Actor falls back to splitting that string on -, , ·, ;, |, or newlines, and only trusts the split as a tag list if every segment reads like a tag (six words or fewer, 60 characters or fewer, no internal sentence punctuation). Otherwise the text is preserved whole with benefitsParsed: false — prose is never chopped into fake tags. Those rows carry benefitsSource: "published_jobbenefits". A posting that publishes no benefits at all returns benefitsRaw: null, benefits: [], benefitCount: 0 and benefitsSource: "none".

Known failure modes, read straight from the code, not measured:

  • equityOffered is read from the compensation line the job page itself renders, falling back to the search card's compensation string. Wellfound stores an equity clause only when the employer filled one in — 66 of 247 listings in a live sample — so equityOffered: null next to salarySource: "published_ldjson" normally means the posting genuinely states no equity, not that a fetch failed.
  • salaryCurrency can be null even when salaryMin/salaryMax are populated, if Wellfound's baseSalary object omits currency, currencyCode, and value.currency all at once.
  • The plain-text salary field is built from the authoritative numbers (e.g. "USD 175,000 – 225,000 per year") and falls back to the posting's own cash clause only when no numbers were published, so it can no longer disagree with salaryMin/salaryMax.
  • When a posting discloses only one bound (a fixed number, not a range), both salaryMin and salaryMax are set to that same value — there is no way to tell "exactly $150,000" apart from "$150,000, no stated ceiling" downstream.

Why not build this yourself?

Wellfound publishes no general-purpose jobs API, and the endpoints people reach for first are all closed: POST /graphql answers 403 "Security Check" without an authenticated session, /_next/data/<buildId>/…json answers 404 (the route is not proxied to Next.js), /sitemap.xml answers 403, and /company/<slug> and /company/<slug>/jobs answer 403 as well — all verified live. What is public is the server-rendered HTML, and it carries two different payloads you have to know about separately.

Search and listing pages (/role/r/<role>, /role/l/<role>/<location>, /location/<slug>) are Next.js pages that inline their entire Apollo cache in <script id="__NEXT_DATA__"> — but Wellfound nests it one level deeper than a stock Next.js app (pageProps.apolloState.data, not pageProps.apolloState), a shape that has already changed once in this Actor's own history and silently emptied every company name and location until the extractor was updated. Job detail pages are Rails-rendered and ship no __NEXT_DATA__ at all; their structured payload is a schema.org JobPosting ld+json block plus the rendered Perks card. A scraper that gates page-readiness on __NEXT_DATA__ — the natural first attempt — misjudges every successfully loaded job page as blocked.

Two more traps are easy to hit. /jobs?keywords=<anything> looks like a search but is not: Wellfound ignores the parameter and serves the same generic featured landing page for every keyword, so an "employer search" built on it quietly samples the wrong jobs. And the benefits text in the ld+json block is a flattened concatenation of each perk's title and description, so splitting it yields either one unusable blob or invented tags — the structured version exists only in the rendered Perks markup.

Finally there is the anti-bot layer, which answers a 403 "Security Check" page rather than an error, so a naive client happily parses the challenge and emits empty rows. This Actor treats that page as a block, rotates its sticky proxy session (and therefore its exit IP), backs off with bounded exponential delay, and steps the proxy tier down only after repeated failures — with every request under an explicit timeout and every retry loop bounded by a wall-clock run deadline, so a blocked run ends cleanly instead of hanging.

Why do developers and teams scrape Wellfound pay data?

For compensation analysts and recruiters

Wellfound is one of the few large job boards where a meaningful share of startup postings publish an exact number instead of "competitive salary." Pull every listing for a role and market (payLocation + roleKeyword), keep only postings with salaryDisclosed: true, and you have a real, sourced pay-band sample for that segment — company name, exact band, currency and equity clause per row — without touching a single job posting by hand.

For AI engineers and agent builders

Because every field is typed JSON with stable key names, a compensation-lookup agent can call this Actor as a tool, filter the returned rows on salaryMin/salaryMax, and pass the result straight into an LLM context or a RAG index — no HTML parsing step sits between the scrape and the model. The minSalary/maxSalary/includeNoSalary inputs let an agent constrain the search itself, so it isn't fetching and then discarding rows it doesn't need.

For labour-market researchers

postedWindow, roleType, and the run-level payDisclosureRate in the run_summary row make it possible to track how much of a market actually discloses pay over time, not just what the disclosed numbers are — useful for studying the effect of pay-transparency norms on a startup-heavy labour segment specifically.

For developers building data products

The emitSkippedRows and run_summary rows mean a pay-benchmarking product built on top of this Actor can show its own coverage honestly ("8 of 100 postings in this market disclosed pay this week") instead of silently presenting a biased sample as complete.

🧭 How to scrape Wellfound salaries (step by step)

  1. Open Wellfound Jobs Scraper — Salary Breakdown & Benefits on its Apify Store listing and click Try for free (or Run, if you've already added it).
  2. No input is required — every field has a default. To target a specific pay sample, set payLocation (e.g. "San Francisco") and/or roleKeyword (e.g. "backend engineer"), or paste specific Wellfound URLs into startUrls.
  3. Narrow the sample with roleType, postedWindow, minSalary/maxSalary, and employerName if you only want one company's listings.
  4. Click Start and watch the run log — it prints the pay filter, the listing pages per seed, and whether the pay/benefits lookup is on.
  5. Download the finished dataset as JSON, CSV, Excel, XML or RSS from the Storage tab, or pull it programmatically with the Apify API/SDK.

What to do when Wellfound changes its structure

This Actor reads Wellfound's data through two independent channels — the embedded Apollo GraphQL cache for listings, and the schema.org ld+json block for job-detail pay data — precisely because relying on rendered DOM markup alone breaks on every visual redesign. It has already absorbed one real Wellfound cache-nesting change without a schema break for end users (see the changelog). Field names and types on your side stay stable across maintenance updates; no specific turnaround time is promised for any given break.

⬇️ Input

No parameter is required — every field has a default, and an empty input run falls back to Wellfound's generic /jobs landing page.

ParameterRequiredTypeDescriptionExample Value
startUrlsNoarrayWellfound search/listing URLs or individual job page URLs. Takes priority over the search fields below. Default [].["https://wellfound.com/role/r/software-engineer"]
payLocationNostringCity or region to benchmark, e.g. "New York", "San Francisco", "Remote". Builds the Wellfound search URL when no startUrls are given. Default "New York"."San Francisco"
roleKeywordNostringRole, title or skill to narrow the pay sample. Default ""."backend engineer"
employerNameNostringRestrict the sample to one employer — a loose, normalized substring match against the company name on each card, applied client-side over whichever market/role search you configure (Wellfound publishes no company-scoped job search). Default ""."Notion"
listingPagesToScanNointegerResult pages to walk per URL. Minimum 1, maximum 100. A page carries roughly 50 job cards per the Actor's own input description. Default 1.3
maxItemsNointegerHard cap on charged job rows across all URLs. 0 = no cap. Minimum 0. Default 0.100
minSalaryNointegerKeep only postings whose published band reaches at least this amount (own currency/period). 0 = off. Minimum 0. Default 0.150000
maxSalaryNointegerKeep only postings whose band starts at or below this amount. 0 = off. Minimum 0. Default 0.220000
includeNoSalaryNobooleanKeep postings with no published pay (salaryMin/salaryMax stay null, never guessed). Default true.true
roleTypeNostring (enum)Employment type filter. One of "" (Any type), FULLTIME, PARTTIME, CONTRACTOR, INTERN. Default ""."FULLTIME"
postedWindowNostring (enum)Posting-age filter. One of all, today, 3days, week, month. Default "all"."week"
fetchPayDetailsNobooleanOpen each job's detail page to read its baseSalary ld+json object and its Perks card — the only places the exact numbers and the structured benefits list live. Turning this off falls back to the compensation-text parser and leaves benefits empty. Default true.true
detailPageDelayNonumberPause, in seconds, before each job-page request (job pages are fetched up to 4 at a time; the delay paces each worker). Minimum 0, maximum 30. Default 1.1
fetchDescriptionsNobooleanInclude the job description text. Independent of fetchPayDetails since v0.6. Default true.true
emitSkippedRowsNobooleanWrite an uncharged type: "skipped" row for every posting your pay filters removed. Default true.true
requestDelayNonumberPause, in seconds, before each search-results page load. Minimum 0, maximum 30. Default 1.1
headlessNobooleanLegacy no-op, kept for input compatibility. Since v0.6 the Actor starts no browser. Default true.true
countryNameNostring (enum)Proxy exit country (249 supported, per the Actor's own input description). Geo-targets the connection only — it does not filter which jobs are returned. Default "United States"."United States"
proxyConfigurationNoobjectApify Proxy configuration used for both search and job pages. Default {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

⚠️ fetchPayDetails is what buys you the exact numbers and the benefits list. Turning it off skips the job page entirely, so salarySource falls back to parsed_from_compensation_text, benefits stays empty and equityOffered is read only from the search card. As of v0.6 it no longer inherits fetchDescriptions's value — the two switches are independent.

⚠️ If you're building raw JSON input by hand (e.g. via the API rather than the Console form), stick to the parameter names in the table above. Internally, several fields also accept an older, undocumented alias inherited from a sibling Actor's input shape (for example locationName instead of payLocation, companyName instead of employerName, jobType instead of roleType, datePosted instead of postedWindow, pagesToFetch instead of listingPagesToScan, jobUrls instead of startUrls). When both an alias and its documented equivalent are present in the same input JSON, the alias wins silently — there is no warning in the run log. This only matters if you assemble input programmatically from another Actor's template; the Console form only ever writes the documented names.

Example input:

{
"startUrls": [],
"payLocation": "San Francisco",
"roleKeyword": "backend engineer",
"employerName": "",
"listingPagesToScan": 3,
"maxItems": 100,
"minSalary": 150000,
"maxSalary": 220000,
"includeNoSalary": true,
"roleType": "FULLTIME",
"postedWindow": "week",
"fetchPayDetails": true,
"detailPageDelay": 2,
"fetchDescriptions": true,
"emitSkippedRows": true,
"requestDelay": 1,
"headless": true,
"countryName": "United States",
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}

⬆️ Output

Every run writes to one Apify dataset, but not every row is a job. Four row shapes ride together, distinguished by type; only type: "job" rows are ever billed.

Scraped job (type: "job")

The default Store view shows 20 columns, but the row itself carries 28 fields — posted_via, jobId, datePosted, detailPageParsed, salaryDisclosed, description, charged and scrapedAt are written to every job row but are only visible in the dataset's Full Details view or via the API/CSV export.

{
"type": "job",
"job_title": "Senior Backend Engineer",
"company_name": "Acme Robotics",
"location": "San Francisco, CA",
"posted_via": "Wellfound",
"salary": "USD 175,000 – 225,000 per year",
"job_type": "Full-time",
"date": "3 days ago",
"datePosted": "2026-07-22T09:14:03Z",
"URL": "https://wellfound.com/jobs/3324973-senior-backend-engineer?utm_campaign=google_jobs_apply&utm_source=google_jobs_apply&utm_medium=organic",
"description": "Acme Robotics is hiring a senior backend engineer to own our fleet-control API...",
"compensationRaw": "$175k – $225k • 0.05% – 0.15%",
"salaryMin": 175000,
"salaryMax": 225000,
"salaryCurrency": "USD",
"salaryPeriod": "YEAR",
"salarySource": "published_ldjson",
"salaryDisclosed": true,
"equityOffered": "0.05% – 0.15%",
"benefitsRaw": "Health Insurance - Full medical, dental and vision for you and your dependents. 401K Plan - With company matching. Unlimited PTO - Take the time you need.",
"benefits": ["Health Insurance", "401K Plan", "Unlimited PTO"],
"benefitsParsed": true,
"benefitsSource": "perks_section",
"benefitCount": 3,
"detailPageParsed": true,
"jobId": "3324973",
"charged": true,
"scrapedAt": "2026-07-25T14:02:11Z"
}

Uncharged accounting rows

A run also writes rows that document what didn't become a job — none of these are billed.

type: "skipped" — a posting your pay filters removed (errorReason is one of no_salary_disclosed, below_min_salary, above_max_salary):

{
"type": "skipped",
"errorReason": "below_min_salary",
"job_title": "Junior Support Engineer",
"company_name": "Acme Robotics",
"URL": "https://wellfound.com/jobs/3324980-junior-support-engineer",
"salaryMin": 65000,
"salaryMax": 85000,
"charged": false,
"scrapedAt": "2026-07-25T14:02:40Z"
}

type: "run_summary" — one row per run, written once at the end, giving coverage over the whole sample:

{
"type": "run_summary",
"runId": "a1b2c3d4e5f6g7h8i9",
"sampleSize": 100,
"rowsCharged": 63,
"detailPagesAttempted": 100,
"detailPagesParsed": 91,
"detailParseRate": 0.91,
"payDisclosureRate": 0.78,
"publishedSalaryRate": 0.72,
"textParsedSalaryRate": 0.06,
"benefitsPresentRate": 0.54,
"benefitsTaggedRate": 0.41,
"benefitsProseCount": 13,
"equityDisclosureRate": 0.09,
"filteredOut": 37,
"filteredNoSalary": 22,
"filteredBelowMin": 11,
"filteredAboveMax": 4,
"detailPagesThrottled": 6,
"detailPagesNavFailed": 3,
"httpRequests": 104,
"proxySessionRotations": 2,
"proxyTier": "residential",
"charged": false,
"scrapedAt": "2026-07-25T14:05:02Z"
}

type: "error" rows carry only type, errorReason, URL, charged: false and scrapedAterrorReason is one of unsupported_page_template (the page carried no parseable job payload), listing_page_unavailable (a search page could not be retrieved after all retries and tier step-downs), seed_failed (an unexpected exception on one seed URL), or blocked (a single job URL couldn't be retrieved after retries).

How does Wellfound Jobs Scraper compare to other Wellfound scrapers?

FeatureWellfound Jobs Scraper — Salary Breakdown & BenefitsGeneric alternative
Salary sourceJob page's own schema.org baseSalary object, with the parsing method reported per row (salarySource)Usually a single regex over listing-card text, provenance not reported
BenefitsReal tag list parsed from the job page's rendered Perks card, with the verbatim publisher text kept alongside and the parsing path reported per row (benefitsSource)Often a flat string, or the ld+json blob passed through unsplit
Filtered-out rowsWritten as uncharged skipped rows with a reason, so a filtered run is auditablethirdwatch/wellfound-jobs-scraper's listing does not document a skipped-row concept (checked on the Apify Store, 2026-07-25)
Run-level coveragerun_summary row reports pay-disclosure and benefits-tagging rates for the whole sampleNot documented on either competing Wellfound listing (checked 2026-07-25)
Login requirementNone — public pages onlykhadinakbar/wellfound-jobs-scraper likewise documents no login required (checked 2026-07-25); thirdwatch/wellfound-jobs-scraper offers an optional session cookie "for higher reliability on heavy runs" (checked 2026-07-25)

If you're building an AI agent or RAG pipeline, the row-level salarySource field is the decision-maker — an agent that can tell "exact published number" apart from "regex guess" can weight or discard rows accordingly, instead of treating every salary figure as equally reliable.

How many jobs can you scrape with Wellfound Jobs Scraper?

There is no hard cap in the Actor itself — maxItems defaults to 0, meaning unlimited, and stops the run only once you set it. listingPagesToScan is capped at 100 pages per seed URL by the input schema; each listing page carries roughly 50 job cards per the schema's own description, so 100 pages is on the order of 5,000 candidate postings per seed before your pay filters run. Wellfound itself imposes one real limit worth knowing: its generic /jobs?location=<slug> query-string search is not a real paginated search — it silently serves a landing page hard-capped at roughly 49 featured cards with no further pages. This Actor avoids that trap by building /location/<slug> URLs instead, which are genuinely paginated. It also reads the real page count the search page publishes (seoLandingPageJobSearchResults.pageCount) and stops there, so a listingPagesToScan larger than the search actually has never wastes requests on empty pages.

The other practical ceiling is the anti-bot layer, and since v0.6 it is handled per request rather than by disabling enrichment: each request gets up to four attempts, every failed attempt rotates the sticky proxy session (a fresh exit IP) and waits a bounded, jittered backoff, and three consecutive blocks step the proxy tier down (residential → datacenter → direct). Nothing is ever retried without a timeout, and the whole run is bounded by a wall-clock deadline derived from the Actor's own configured timeout, so the run_summary row is always written even on a bad day. For throughput: a live benchmark of 25 job pages at 4-way concurrency through the Apify proxy completed in 3.8 s (datacenter) / 6.4 s (residential), all 25 returning HTTP 200.

Integrate Wellfound Jobs Scraper and automate your workflow

Wellfound Jobs Scraper works with any language or tool that can send an HTTP request, through the standard Apify API.

REST API integration

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<YOUR_USERNAME>/wellfound-jobs-scraper-salary-breakdown-benefits").call(
run_input={
"payLocation": "San Francisco",
"roleKeyword": "backend engineer",
"minSalary": 150000,
"fetchPayDetails": True,
}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item.get("type") == "job":
print(item["job_title"], item["salaryMin"], item["salaryMax"], item["salaryCurrency"])

Works in Python, Node.js, Go, Ruby, and cURL — the call shape is the same Apify Actor-run API for every client.

Automation platforms (n8n, Make, LangChain)

Apify's official n8n node and Make app can both start this Actor with a fixed input object and read the resulting dataset back into your workflow, without writing any HTTP code. A LangChain or similar agent framework can wrap the same apify_client call shown above as a tool function, passing payLocation/roleKeyword/minSalary as the tool's arguments and filtering the returned items on type == "job".

Yes — this Actor collects only data that is publicly visible on Wellfound without logging in. Job postings are business/product listings, not personal data about a private individual, so the operative framework is Wellfound's own Terms of Service and database-rights law in your jurisdiction, not GDPR or CCPA. You are responsible for using the data in line with Wellfound's terms and any applicable law in your jurisdiction, particularly around reuse, redistribution, and rate of access. Consult legal counsel for commercial use cases, especially any that involve bulk collection or downstream use of company or personal identifiers found inside job descriptions.

❓ Frequently asked questions

Does Wellfound Jobs Scraper work without a Wellfound account?

Yes. No login, cookie, or credential input exists in this Actor's schema — it reads only pages any visitor can open.

How often is the scraped data updated?

Every run fetches Wellfound live at request time. Nothing is cached between runs; re-running with the same input re-scrapes current listings.

What happens if a job posting doesn't publish a salary?

By default (includeNoSalary: true) it's still returned, with salaryMin/salaryMax left null — never guessed or backfilled. Set includeNoSalary: false to get a pay-transparency-only dataset; excluded postings are still written as uncharged skipped rows with errorReason: "no_salary_disclosed" when emitSkippedRows is on, so you can see what was left out and why.

Can I scrape private or restricted Wellfound content?

No. Only publicly accessible job listings and search pages are returned — there is no path in this Actor for candidate accounts, recruiter dashboards, or messages.

Can I restrict a run to a single employer?

Yes — set employerName. Matching is a loose, normalized substring comparison against the company name shown on each job card (punctuation and case are stripped before comparing), so "acme" matches "Acme Robotics, Inc.". Be aware of what it can and cannot do: Wellfound publishes no company-scoped job search — /company/<slug>/jobs answers 403 and /jobs?keywords=<company> is ignored by the server — so the filter is applied client-side over whichever market/role search you configure. Combine it with payLocation/roleKeyword and raise listingPagesToScan so the filter has enough listings to match against; the Actor warns in the log when employerName is the only filter you supplied.

Is the date field an absolute timestamp I can sort by?

No — date is a relative label ("3 days ago", "today") computed at scrape time, so it drifts. Sort on datePosted instead: since v0.6 every job row carries the posting's own publication timestamp as ISO-8601 UTC, taken from the search payload's liveStartAt or the job page's schema.org datePosted. scrapedAt remains the moment the Actor captured the row, and postedWindow filters by posting age during the run.

Does maxItems cap billing, or just the number of returned rows?

It caps charged job rows directly — the run stops pulling new listings once the number of charged rows reaches maxItems. Uncharged skipped/error/run_summary rows aren't separately capped, but because the run stops scraping once the cap is hit, you won't see a large batch of them appear after it.

Does Wellfound Jobs Scraper work for AI agent workflows and LLM pipelines?

Yes. It's callable as a standard HTTP endpoint through the Apify API by any agent framework — see the integration example above. Every response is typed JSON with stable field names, so no parsing step sits between the scrape and an LLM context window or vector store.

How does Wellfound Jobs Scraper handle Wellfound's anti-bot system?

Wellfound answers clients it dislikes with a 403 "Security Check" page, not an error, so the first job is to recognise it: the Actor treats a 403/429/503, or a document whose head matches the challenge markers, as a block and never parses it into a row. On a block it rotates the sticky proxy session (a fresh exit IP) and retries with bounded exponential backoff, up to four attempts per URL; three consecutive blocks step the proxy tier down — residential (the default) → datacenter → direct. Every request carries an explicit timeout and the retry budget is bounded by both an attempt count and a wall-clock run deadline, so a blocked run finishes cleanly with an uncharged listing_page_unavailable/blocked error row instead of hanging or billing you for empty rows.

How does Wellfound Jobs Scraper compare to other Wellfound scrapers?

Both competing Wellfound listings on the Apify Store — thirdwatch/wellfound-jobs-scraper and khadinakbar/wellfound-jobs-scraper — document salary and equity extraction (checked 2026-07-25), but neither documents reporting how a given number was derived, nor an uncharged skipped-row mechanism for filtered-out postings. This Actor exposes both via salarySource and type: "skipped" rows.

Does Wellfound Jobs Scraper return data in a format LLMs can use directly?

Yes. Typed, normalized JSON with stable field names — no HTML, no selectors, no cleanup pass required before passing rows to a model.

Can I use Wellfound Jobs Scraper without managing proxies?

Yes. It defaults to Apify residential proxy sessions automatically — proxyConfiguration only needs to be set if you want to override that default with your own proxy groups.

What happens when Wellfound changes its structure or blocks the scraper?

The Actor is maintained, and the output schema stays stable on your end — field names and types don't change without notice. No specific turnaround time is promised for any given fix.

💬 Your feedback

Found a bug, or need a field this Actor doesn't collect? Open an issue from the Issues tab on this Actor's Apify Console page — that's the fastest way to reach the maintainer, and it helps everyone else running the same Actor see what's already been reported.