# Wellfound Jobs Scraper | Salaries & Job Changes (`peerless_columbine/wellfound-jobs-scraper-api`) Actor

Collect public Wellfound jobs with descriptions, salary and equity fields where available. Filter roles and locations, paginate results and track new or changed jobs. Coverage checks help prevent false expired-job alerts.

- **URL**: https://apify.com/peerless\_columbine/wellfound-jobs-scraper-api.md
- **Developed by:** [tingyou333 zhuang](https://apify.com/peerless_columbine) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 job or company records

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Wellfound Jobs Scraper | Salaries & Job Changes

Collect public Wellfound startup jobs with role, company, location, salary and equity data where available. Use filtered searches or recurring scans to track new and changed listings.

[Quick start](#quick-start) · [Workflows](#common-workflows) · [Pricing](#pricing) · [Input reference](#input-reference) · [Output reference](#output-reference) · [API and automation](#api-and-automation) · [FAQ](#faq) · [Technical reference](#technical-reference)

### What you can do

- **Search and detailed records** — Use public role/search URLs with job and company enrichment controls.
- **Compensation and location filters** — Filter salary, role, remote eligibility, keywords and other documented fields.
- **Recurring change tracking** — Use a persistent stateKey to label new, updated and unchanged records.
- **Safer expiry reporting** — Coverage checks prevent incomplete scans from automatically marking unseen jobs as expired.

#### Who uses it

- Recruiting teams: monitor startup hiring by role and location.
- Job-market analysts: study salary, equity and remote-work signals in public listings.
- Automation teams: compare recurring scans and send relevant changes downstream.

### Quick start

1. Open **Input**, enter your target and a small result limit.
2. Click **Start** and inspect the dataset and run log.
3. Export JSON/CSV, or save the input as a Task for recurring runs.

```json
{
  "startUrls": [
    "https://wellfound.com/role/r/software-engineer"
  ],
  "maxItems": 20,
  "maxPages": 1
}
```

Form prefills, schema defaults and empty API input can differ. Use explicit targets and limits; see the technical reference for source and default behavior.

<details>
<summary>Default input behavior and source options</summary>

Empty input targets the public `/jobs` page with a 100-row cap and up to 10 pages; the form instead prefills a software-engineer query and 20 rows. General jobs pages may be restricted. For a predictable source scope use an explicit public role URL as below; no result count is guaranteed.

</details>

#### Import an existing configuration

`crawlerbrosInput` accepts the Crawler Bros JSON contract, including its string `location`. `blackfalconInput` accepts the Black Falcon contract, including array `location`, and selects its corresponding filter semantics. Paste the entire original configuration into one object and keep overrides inside it. Native form defaults do not override imported values.

```json
{"crawlerbrosInput": {"startUrls": ["https://wellfound.com/role/r/software-engineer"], "location": "San Francisco", "maxItems": 5}}
```

Import compatibility does not create unavailable company information: missing source values remain null, and collection diagnostics distinguish source restrictions from empty matches.

### Common workflows

#### Find remote software roles

```json
{
  "role": "software engineer",
  "remoteOnly": true,
  "maxItems": 20,
  "maxPages": 2
}
```

#### Start recurring change monitoring

```json
{
  "startUrls": [
    "https://wellfound.com/role/r/software-engineer"
  ],
  "incrementalMode": true,
  "stateKey": "my-software-jobs",
  "maxItems": 20,
  "maxPages": 2,
  "emitExpired": false
}
```

#### Collect company summaries

```json
{
  "startUrls": [
    "https://wellfound.com/role/r/software-engineer"
  ],
  "companyOnlyMode": true,
  "maxItems": 20,
  "maxPages": 1
}
```

### Output at a glance

Illustrative field subset below; these are example values, not a captured customer review or a claim about source coverage. Native and compatibility output profiles can differ.

```json
{
  "type": "job",
  "jobId": "example-job-id",
  "title": "Software Engineer",
  "companyName": "Example Company",
  "salaryMinUsd": null,
  "salaryMaxUsd": null,
  "coverage": "partial",
  "warnings": [
    "Illustrative warning: compensation was not disclosed."
  ]
}
```

Public Wellfound pages can be restricted or incomplete. Salary, equity, visa and company fields remain unknown when the source does not expose them. An incomplete scan is not evidence of expiry. Notification adapters have not been verified end to end for every customer channel.

### Pricing

Pay per delivered event, with automatic discounts based on **your Apify subscription plan**. These are result fees in USD, not separate subscriptions sold by this Actor. All tiers have the same Actor features. **Apify compute, storage, transfers and any proxy usage are charged additionally.**

| Billable event (1,000 events) | Free | Starter | Scale | Business |
|---|---:|---:|---:|---:|
| Job or company record | $0.75 | $0.6 | $0.45 | $0.3 |

Platinum and Diamond currently use the Business rate. The **Pricing** tab is the source of truth for the active schedule and your applicable plan.

- **Job or company record**: One delivered job, change record, or company aggregation row. Internal state-only rows are not delivered or charged. Platform usage is additional.

Example on the Free plan: 10 / 1,000 / 10,000 events cost $0.0075 / $0.75 / $7.5 in result fees, plus platform usage.

There is no additional developer setup or monthly rental fee. A run with no billable results can still consume platform resources. Start small, cap results and review run costs before scheduling a larger workload. Requested counts are ceilings, not guaranteed deliveries.

### Input reference

Fields below are grouped like the Input form. Optional fields need not be supplied. Compatibility input envelopes and transformations are explained in the technical reference.

#### Targets and collection

| Field | Type | Default | Meaning |
|---|---|---|---|
| `startUrls` | array | Not set | Wellfound /jobs, /role, /role/r, or /role/l URLs. When omitted, role/location/remoteOnly can build a source URL automatically. In blackfalcon compatibility mode these replace query/roles/location/remote search constraints; job and post-fetch filters still apply. |
| `role` | string | Not set | Role name or slug, for example software engineer. Enables server-rendered role pagination. |
| `jobTitle` | string | Not set | Compatibility filter: title contains this phrase, case-insensitive. |
| `keyword` | string | Not set | Compatibility filter across title, company, locations and description. |
| `location` | array | Not set | One or more location slugs (e.g. \["new-york", "san-francisco", "berlin"]). Each location × role combination spawns a separate search thread. For /jobs?q= searches, use a human-readable city name. Ignored for pasted searchUrls (they carry their own location). |
| `jobType` | string | "any" | Compatibility filter for the jobType field exposed in Wellfound's public search payload. |
| `experience` | string | "any" | Best-effort experience filter using title and description terms. |
| `includeNoSalary` | boolean | true | When false, rows without a parseable USD salary range are excluded. |
| `companyCategories` | array | Not set | Migration-compatible filter matching supplied terms against company name/slug, consistent with the head competitor's documented behavior. |
| `includeCompanies` | array | Not set | Optional allow-list of company names or slugs. |
| `since` | string | Not set | ISO date/timestamp applied to Wellfound liveStartAt for scheduled incremental runs. |
| `requestDelayMillis` | integer | 0 | Optional delay before each HTTP attempt. |
| `scanMaxItems` | integer | 2000 | Maximum candidate jobs scanned before change selection. maxItems remains the delivery cap. |
| `roles` | array | Not set | Additional source roles or URLs; ignored when startUrls is set. |
| `searchUrls` | array | Not set | Additional source roles or URLs; ignored when startUrls is set. |
| `remote` | boolean | Not set | Alias of remoteOnly; remoteOnly takes precedence. |
| `salaryMin` | integer | Not set | Drop jobs whose upper salary bound is below this value; missing upper bound is excluded. minSalary takes precedence and retains its lower-bound semantics. |
| `salaryMax` | integer | Not set | Drop jobs whose known lower salary bound exceeds this value. maxSalary takes precedence and retains its upper-bound semantics. |
| `visaSponsorship` | boolean | Not set | true requires explicit support. false excludes explicit refusal but keeps unknowns. Omit for no filter. |
| `descriptionFormat` | string | Not set | Select fields; source Markdown is retained verbatim. Missing source HTML stays null with a warning. |
| `query` | string | Not set | Free-text job search (e.g. "rust engineer", "ML ops", "solidity developer"). Maps to Wellfound's /jobs?q= endpoint. Combine with roles/searchUrls to union multiple searches. |
| `experienceLevel` | string | Not set | Filter by minimum required years of experience. Entry: 0-2, Mid: 2-5, Senior: 5-8, Staff+: 8+. Jobs without experience data are kept. |
| `equityMin` | number | 0 | Drop jobs whose max equity grant is below this percent. 0 = no minimum. Jobs without equity data are dropped when minimum is set. |
| `includeKeywords` | object | Not set | Require at least one of these keywords. Example: {"keywords":\["rust","golang"],"matchTitle":true,"matchDescription":true,"matchSkills":true}. Matches default to all three fields. |
| `compact` | boolean | false | Output only core fields (id, title, companyName, portalUrl, locationNames, remote, compensation, postedAt). For AI-agent/MCP workflows. |
| `descriptionMaxLength` | integer | 0 | Truncate job description to this many characters. 0 = no truncation. |
| `locationText` | string | Not set | Location filter. With role, it also builds Wellfound's public role/location route. |
| `skipReposts` | boolean | false | In incremental mode, suppress newly assigned job IDs whose content matches a previously delivered job confirmed expired by a complete scan. Incomplete scans never expire jobs. Applies to state first recorded with content hashes; older records acquire hashes on their next delivered update. |
| `originLocation` | string | Not set | Origin city for radius filtering, resolved from offline GeoNames city data. Unknown origins skip the filter with an OUTPUT warning. Unresolved job locations are excluded when the origin is known. Distances use city centres. |
| `radiusMiles` | integer | 0 | Drop jobs further than N miles from originLocation, using job coordinates where available. US-standard unit. |
| `radiusKm` | integer | 0 | Kilometer alternative to radiusMiles. If both set, radiusMiles wins. Use this for EU-based searches. |
| `cityName` | string | Not set | Origin city alias for radius filtering; takes precedence over originLocation and explicit coordinates. |
| `lat` | number | Not set | GPS latitude. Pair with lon. Ignored when cityName is set. |
| `lon` | number | Not set | GPS longitude. Pair with lat. |
| `telegramChatId` | string | Not set | Chat ID (from @userinfobot) or channel ID (starts with -100 for channels). Required when telegramToken is set. |
| `whatsappPhoneNumberId` | string | Not set | WhatsApp Business phone number ID from Meta Business Manager (the numeric ID next to your number — NOT the phone number itself). Free service-conversation messages within 24 hours of last user-initiated contact. |
| `whatsappTo` | string | Not set | Recipient phone number in E.164 format (e.g. +4512345678). The recipient must have messaged your business number within the last 24 hours. |
| `includeRunMetadata` | boolean | true | Prepend a one-line summary (search label + total count) to each notification batch. |
| `whatsappApiVersion` | string | "v23.0" | Graph API version used for the configured WhatsApp business sender. Set a version supported by your Meta application. |
| `enrichDetail` | boolean | false | Core job fields — title, full description, compensation, location, remote status, experience level, job type, and company name/logo/size — are delivered inline for every listing, no toggle needed. This option additionally attempts deep per-listing fields (structured salary currency/period, company website, industry, benefits, skills). Those deep fields are not currently available and will return null, so leave this off unless you specifically need them and accept they may be empty. |
| `enrichCompany` | boolean | false | Company basics — name, logo, slug, size — are delivered inline for every listing. This option additionally attempts deep firmographics (funding stage, total raised, investors, founders, social links, founded year, markets). Those deep fields are not currently available and will return null. |
| `includeDetails` | boolean | false | Fetch the detail page for each listing to retrieve the full description text and contact information. Increases run time and cost. Leave off for a fast, low-cost run. |
| `appConnector` | string | Not set | Optional Apify MCP connector supporting notion-create-pages or create\_issue. Writes one run summary after data delivery; other tools are not called. Configure mcpIssueTeam for issue trackers. |
| `mcpIssueTeam` | string | Not set | Team name or ID required when the selected connector supports create\_issue. |

#### Filters and ordering

| Field | Type | Default | Meaning |
|---|---|---|---|
| `remoteOnly` | boolean | Not set | Return only remote jobs. With role and no location, uses the public remote-role route. |
| `minSalary` | integer | Not set | Require the parsed lower bound of a USD salary range to be at least this value. |
| `excludeCompanies` | array | Not set | Optional block-list of company names or slugs. |
| `sort` | string | "none" | Sort accepted rows by Wellfound liveStartAt, or preserve source order. |
| `fromDate` | string | Not set | Alias of since; ISO date or timestamp. |
| `excludeEmptyFields` | boolean | Not set | Drop null, empty strings, arrays and objects; keep zero and false. |
| `excludeLocations` | array | Not set | Drop jobs whose locationNames match any of these (substring match, e.g. "India" drops "Bangalore, India"). Applied after fetch. |
| `toDate` | string | Not set | Only include jobs posted on or before this date. Accepts YYYY-MM-DD or ISO-8601. |
| `excludeKeywords` | object | Not set | Drop jobs containing any of these keywords. Same shape as includeKeywords. |
| `companyOnlyMode` | boolean | false | Aggregate accepted jobs into one record per company. maxResults bounds jobs scanned before aggregation. jobCount is the number of observed jobs in this run, not all openings. Incremental mode is not applied. |
| `notifyOnlyChanges` | boolean | true | When ON (default) AND incrementalMode is ON, send notifications only for NEW and UPDATED jobs. Without incrementalMode, this has no effect (all emitted jobs are notified). |

#### Result limits and cost

| Field | Type | Default | Meaning |
|---|---|---|---|
| `maxSalary` | integer | Not set | Require the parsed upper bound of a USD salary range to be at most this value. 0 means unlimited. |
| `maxItems` | integer | Not set | Maximum job rows saved after filtering. |
| `maxPages` | integer | 10 | Maximum public role-search pages to fetch. Wellfound currently exposes pageCount in SSR data. |
| `maxRequestRetries` | integer | 2 | Retries for transient 403/429/5xx and network failures. |
| `maxResults` | integer | Not set | Alias of maxItems; 0 means a bounded 5000-row ceiling, not unlimited. maxPages still applies. |
| `maxAgeMinutes` | integer | 0 | Only include jobs posted within the last N minutes. 0 = no limit. Pairs well with scheduled runs for real-time monitoring. |

#### Monitoring and state

| Field | Type | Default | Meaning |
|---|---|---|---|
| `incrementalMode` | boolean | false | Emit NEW/UPDATED jobs using state committed only after delivery. Run the same stateKey serially. |
| `stateKey` | string | Not set | Required in incremental mode. Changing search filters requires a new key. |
| `emitExpired` | boolean | false | Emit EXPIRED only after a complete, error-free scan; suppress expiration on partial snapshots. |
| `emitUnchanged` | boolean | false | In incremental mode, include unchanged jobs with changeType=UNCHANGED. Delivered rows still commit state only after successful delivery. |
| `slackWebhookUrl` | string | Not set | Slack incoming webhook URL (Slack App → Incoming Webhooks → Add New Webhook). Formats jobs as Slack blocks with company, salary, funding stage, and location. |
| `discordWebhookUrl` | string | Not set | Discord channel webhook URL (Server Settings → Integrations → Webhooks → New Webhook). Sends rich embeds colour-coded by change type (green=NEW, amber=UPDATED). |
| `webhookUrl` | string | Not set | Universal escape hatch for n8n / Make / Zapier / custom HTTP backends. Receives a JSON POST { metadata, items } per run. Single attempt, 15 s timeout. |
| `webhookHeaders` | object | Not set | Additional HTTP headers sent with the generic webhook POST (e.g. {"Authorization":"Bearer ..."}). |
| `notificationLimit` | integer | 5 | Maximum number of jobs to send per notification batch. Prevents spam on large result sets. |

#### Compatibility and transformations

| Field | Type | Default | Meaning |
|---|---|---|---|
| `customFilters` | array | Not set | Array of rules applied to any output field. Each rule: {"field":"","op":"includes|notIncludes|equals|notEquals|gt|gte|lt|lte","value":...}. Example: \[{"field":"companyFundingStage","op":"equals","value":"series-b"}, {"field":"description","op":"notIncludes","value":"wordpress"}]. |
| `compatibilityMode` | string | "auto" | auto detects Blackfalcon-specific input names or array location; ambiguous inputs preserve legacy URL plus client filters. blackfalcon direct URLs ignore search routing constraints; pasted searchUrls ignore location/remote per source. Explicit post-fetch filters remain active. |

#### Connection and advanced settings

| Field | Type | Default | Meaning |
|---|---|---|---|
| `proxyConfiguration` | object | Not set | Optional Apify or custom proxy used for listing, detail and company requests. Omit or disable for direct access. Configured proxy failures do not silently fall back to direct. |
| `telegramToken` | string | Not set | Telegram bot token from @BotFather. Sends notifications when new jobs are emitted. Pairs well with incrementalMode for real-time alerts. |
| `whatsappAccessToken` | string | Not set | Meta Cloud API access token with whatsapp\_business\_messaging scope. Get a permanent token from a system user in Meta Business Manager. |

#### Additional compatibility options

| Field | Type | Default | Meaning |
|---|---|---|---|
| `crawlerbrosInput` | object | Not set | Paste the complete competitor input JSON here to preserve its field types and defaults. Put all options inside this object. This is optional; normal native inputs remain available. |
| `blackfalconInput` | object | Not set | Paste the complete competitor input JSON here to preserve its field types and defaults. Put all options inside this object. This is optional; normal native inputs remain available. |

### Output reference

The table describes native fields; fields can be absent or null when the source does not provide them. Error rows and selected compatibility profiles have different shapes. Nested objects remain structured in JSON; CSV exports may serialize them.

| Field | Type | Meaning |
|---|---|---|
| `type` | string | Type |
| `jobId` | string | Job ID |
| `title` | string | Title |
| `jobUrl` | string | Job URL |
| `description` | \['string', 'null'] | Description |
| `jobType` | \['string', 'null'] | Job type |
| `primaryRoleTitle` | \['string', 'null'] | Primary role |
| `compensation` | \['string', 'null'] | Compensation |
| `salaryMinUsd` | \['number', 'null'] | Salary min USD |
| `salaryMaxUsd` | \['number', 'null'] | Salary max USD |
| `equityMinPercent` | \['number', 'null'] | Equity min % |
| `equityMaxPercent` | \['number', 'null'] | Equity max % |
| `remote` | \['boolean', 'null'] | Remote |
| `locations` | array | Locations |
| `acceptedRemoteLocations` | array | Accepted remote locations |
| `postedAt` | \['string', 'null'] | Posted at |
| `companyId` | \['string', 'null'] | Company ID |
| `companyName` | \['string', 'null'] | Company |
| `companyUrl` | \['string', 'null'] | Company URL |
| `companySize` | \['string', 'number', 'null'] | Company size |
| `companyBadges` | array | Company badges |
| `sourceUrl` | string | Source URL |
| `sourcePage` | integer | Source page |
| `coverage` | string | Coverage |
| `retrievalMethod` | string | Retrieval method |
| `warnings` | array | Warnings |
| `scrapedAt` | string | Scraped at |
| `errorCode` | string | Error code |
| `message` | string | Message |
| `id` | \['string', 'null'] | id |
| `portalUrl` | \['string', 'null'] | portalUrl |
| `detailUrl` | \['string', 'null'] | detailUrl |
| `companyTagline` | \['string', 'null'] | companyTagline |
| `descriptionMarkdown` | \['string', 'null'] | descriptionMarkdown |
| `salaryEquityMin` | \['string', 'null'] | salaryEquityMin |
| `salaryEquityMax` | \['string', 'null'] | salaryEquityMax |
| `source` | \['string', 'null'] | source |
| `visaSponsorshipSource` | \['string', 'null'] | visaSponsorshipSource |
| `salaryMin` | \['number', 'null'] | salaryMin |
| `salaryMax` | \['number', 'null'] | salaryMax |
| `yearsExperienceMin` | \['number', 'null'] | yearsExperienceMin |
| `yearsExperienceMax` | \['number', 'null'] | yearsExperienceMax |
| `visaSponsorship` | \['boolean', 'null'] | Visa sponsorship |
| `visaSponsorshipEvidence` | \['string', 'array', 'null'] | Visa statement evidence |
| `locationNames` | array | locationNames |
| `extractedUrls` | array | extractedUrls |

### API and automation

The **API** tab provides snippets for this Actor. For scheduled or larger collections, start a run asynchronously and poll its status instead of holding a synchronous request open. Keep your API token in an environment variable.

```python
import os, time, requests

base = "https://api.apify.com/v2"
headers = {"Authorization": "Bearer " + os.environ["APIFY_TOKEN"]}
actor_input = {'startUrls': ['https://wellfound.com/role/r/software-engineer'], 'maxItems': 20, 'maxPages': 1}
response = requests.post(
    base + "/acts/BiUgcZcjV20Fno1wV/runs",
    headers=headers, json=actor_input, timeout=30,
)
response.raise_for_status()
run = response.json()["data"]
for _ in range(720):
    if run["status"] not in ("READY", "RUNNING", "TIMING-OUT", "ABORTING"):
        break
    time.sleep(5)
    response = requests.get(base + "/actor-runs/" + run["id"], headers=headers, timeout=30)
    response.raise_for_status()
    run = response.json()["data"]
else:
    raise TimeoutError("Polling ended; inspect the existing run before starting another.")
if run["status"] != "SUCCEEDED":
    raise RuntimeError(run.get("statusMessage", run["status"]))
response = requests.get(
    base + "/datasets/" + run["defaultDatasetId"] + "/items",
    headers=headers, params={"format": "json", "limit": 1000, "offset": 0}, timeout=30,
)
response.raise_for_status()
rows = response.json()
print(rows)  # Paginate with offset for larger datasets; inspect per-row coverage.
```

#### Scheduled monitoring and integrations

1. Save a working input as an Apify Task.
2. Attach a Schedule in your timezone; keep runs sharing an incremental state key sequential.
3. Add a successful-run webhook or connect your downstream workflow to the dataset export/API.
4. Inspect missing/partial data before sending alerts or treating absence as removal.

Use the platform Integrations/API options to connect external workflows such as Make, n8n or a data warehouse. An external sentiment or summarization step is separate from this Actor and may have its own charges. The platform also supports Actor tools through [Apify MCP](https://docs.apify.com/platform/integrations/mcp); availability and client setup depend on your integration.

### FAQ

#### How should I run monitoring?

Save one input as a Task, enable incrementalMode and keep stateKey stable. Run jobs sharing a stateKey serially.

#### Why is a job not marked expired?

Expiry is suppressed when coverage is insufficient. Absence from a capped, failed or partial scan cannot establish that a job was removed.

#### Are salary and visa fields guaranteed?

No. Values are reported from source evidence and remain null or unknown when they cannot be established.

#### Can I export without writing code?

Yes. Run from the Input form, open the dataset and use the export controls for JSON or CSV. Select your preferred output profile before running when migrating an integration.

#### Does a successful run guarantee every field?

No. Inspect coverage, warnings and source-specific status fields. Optional enrichment may be missing even when usable rows were delivered.

### Troubleshooting

| Symptom | What to check |
|---|---|
| Empty dataset | Validate the target, source availability and filters; try the small quick-start input. |
| Fewer rows than requested | Check source limits, pagination and filtering. Increasing a cap cannot create unavailable source records. |
| Partial fields | Inspect coverage/warnings and the relevant source or enrichment status. |
| Access or throttling errors | Check the run log and documented proxy configuration; retry at a lower request volume. |
| Migration mismatch | Check the explicit input/output profile and technical field mapping below. |
| Unexpected bill | Separate result events from platform resources; inspect enrichment, result caps and the Pricing tab. |

### Technical reference

#### Why this Actor exists

Wellfound's role search pages expose paginated startup/job search results in `__NEXT_DATA__`. In the currently validated public surface, those rows include full job descriptions, job type, compensation, remote configuration, accepted remote locations, startup metadata, and page-count information. This allows multi-page collection without opening each DataDome-protected job-detail page.

#### Migration-compatible input

Existing Wellfound Actor users can keep common fields such as:

- `startUrls`
- `jobTitle`
- `keyword`
- `location`
- `remoteOnly`
- `jobType`
- `experience`
- `minSalary` / `maxSalary`
- `includeNoSalary`
- `companyCategories`
- `includeCompanies` / `excludeCompanies`
- `sort`
- `maxItems`
- `proxyConfiguration` (optional Apify or custom proxy, applied to listing, detail and company requests; omitted or disabled means direct)

This Actor also adds `role`, `since`, `maxPages`, request retry controls, and direct pagination of public role pages.

#### Recommended usage

For scalable results, specify a `role`, for example:

```json
{
  "role": "software engineer",
  "remoteOnly": true,
  "maxItems": 500,
  "maxPages": 20
}
```

`startUrls` is useful when migrating an existing workflow or targeting a known Wellfound role/location URL.

#### Output

Each job row can include job ID/title/URL, full description, job type, compensation, parsed USD salary range, equity range, remote status and locations, posting time, company metadata and explicit source/coverage telemetry.

The Actor marks missing source fields as partial instead of inventing values. Source failures are emitted as free error rows.

#### Source boundary

The launch path uses public Wellfound HTML and the embedded `__NEXT_DATA__` Apollo cache. It does not require login/session cookies or private APIs. Website behavior can change; the Actor exposes source diagnostics and retry counts so scheduled users can detect degradation.

#### Incremental delivery

Set `incrementalMode=true` with a unique `stateKey` for a stable search/filter scope. `scanMaxItems` controls scan depth independently of the delivery maxItems. Only successfully delivered NEW/UPDATED jobs are committed to the named store `wellfound-delivered-state`; undelivered changes are eligible next run. `emitExpired=true` emits EXPIRED identities only when source coverage passes the expiry gate described below. Traversing all pages alone does not establish complete coverage. Insufficient coverage suppresses both emitted EXPIRED records and internal expiry for `skipReposts`, while NEW/UPDATED delivery continues. Use serial schedules for the same stateKey: no atomic lock is available, and a crash between delivery and state commit can cause duplicates (at-least-once delivery). Source changes outside the scan are not detectable. Changing filters requires a new stateKey.

##### Additional output fields

The original job fields remain available. Each job also includes `id`,
`portalUrl`, `detailUrl`, `salaryMin`, `salaryMax`, `salaryEquityMin`,
`salaryEquityMax`, `locationNames`, `companyTagline`, and `descriptionMarkdown`.
Equity aliases are percentage strings; the original numeric fields remain numeric.
`yearsExperienceMin` and `yearsExperienceMax` come directly from public page data,
including zero. Missing source values remain null.

`visaSponsorship` uses a source boolean when available, otherwise a conservative
explicit statement in the description. `visaSponsorshipSource` and
`visaSponsorshipEvidence` show how the result was obtained. Ambiguous or conflicting
statements return null. This describes the listing, not immigration eligibility.
`extractedUrls` contains unique explicit HTTP(S) links in description order,
retaining source paths and trailing slashes. It does not fetch those links.
These additions are not a promise of full compatibility with every competitor field.
Existing incremental state may emit one UPDATED record after this schema upgrade
because new job fields participate in content fingerprints.

##### Additional migration inputs

`roles` and `searchUrls` union multiple public sources, deduplicating by job ID.
Explicit `startUrls` takes precedence. `remote`, `maxResults`, `salaryMin`,
`salaryMax`, and `fromDate` are compatibility inputs; explicitly supplied `remoteOnly`,
`maxItems`, `minSalary`, `maxSalary`, and `since` take precedence.
`maxResults=0` uses a bounded 5,000-row ceiling, with `maxPages` still applied.
`visaSponsorship=true` requires explicit support; false removes explicit refusal
and retains unknowns. Omit it to disable that filter.

`descriptionFormat` selects description fields without changing source text:
`text` keeps `description`, `markdown` keeps `descriptionMarkdown`, and `html`
keeps source HTML only (null with a warning when unavailable). `all` retains the
existing representation. `excludeEmptyFields` removes null/empty values while
retaining false and zero. Output formatting does not modify incremental state
fingerprints. Unsupported formats fail before requests or billable job output.
These output-format controls do not themselves change query routing, enrichment or notification settings.

##### Additional migration controls

`query` adds a free-text jobs URL alongside `roles` and `searchUrls` (explicit `startUrls` takes precedence). `excludeLocations`, `includeKeywords` / `excludeKeywords` with field selection, `experienceLevel`, `equityMin`, `toDate`, and `maxAgeMinutes` filter the fetched jobs. Unknown experience is retained; unknown equity is excluded when an equity minimum is requested. Date-only `toDate` includes the entire UTC day; the age cutoff is fixed once per run.

`compact` emits the eight core job fields; `descriptionMaxLength` truncates presentation fields only. Canonical incremental hashes remain unchanged. `emitUnchanged` includes unchanged jobs in incremental mode with `changeType: UNCHANGED`, and state still advances only for delivered jobs. New filters at their inactive defaults preserve existing state scopes; changing an active filter requires a new `stateKey`.

`location` now accepts the competitor's array of location slugs, generating each role × location combination and deduplicating jobs across sources. Use `locationText` for the earlier single-string form. `customFilters` accepts declarative includes/notIncludes/equals/notEquals/gt/gte/lt/lte rules, including dotted field paths; numeric comparisons exclude unknown data. No executable expressions are evaluated.

`companyOnlyMode` aggregates accepted jobs into one record per company, with `jobCount`, `sampleJobTitles` and `jobIds`. Counts describe observed jobs in this run, not all openings at the company. `maxResults` bounds jobs before aggregation; delivery accounting counts company records actually pushed. Incremental mode is ignored in this mode with an explicit warning, without reading or changing its saved state. Compact company output retains company identity and aggregates.

`skipReposts` uses content fingerprints of previously delivered jobs confirmed expired only after the source coverage gate passes. It suppresses a new ID with matching title, company, description, job type, locations and compensation. Insufficient coverage prevents both EXPIRED output and internal expiry. If notifications are disabled, eligible expirations can update internal state only. The fingerprint archive is bounded to 20,000 entries; legacy state is retained conservatively until refreshed through delivery.

Radius filtering supports `originLocation` / `cityName`, `lat` + `lon`, `radiusMiles` and `radiusKm` (positive miles take precedence). It uses the offline [GeoNames gazetteer via geonamescache](https://pypi.org/project/geonamescache/) without a paid geocoding API. Distances are between city centres, not exact office addresses. Unknown origins skip filtering with a warning; unresolved job locations are excluded when the origin is known, with the exclusion count in OUTPUT. `cityName` takes precedence over coordinates. For duplicate city names, the most populous match is selected; a country/state code suffix can narrow it, e.g. `Paris, FR`.

##### Optional notifications

Telegram, Slack, Discord, WhatsApp and generic JSON webhooks accept the competitor's notification input names. No notification request is made without a configured destination. Only successfully delivered dataset rows are eligible; `notificationLimit` defaults to 5 (maximum 20), and `notifyOnlyChanges` restricts incremental notifications to NEW/UPDATED records. Generic webhooks receive `{metadata, items}`.

Adapters make one attempt per destination with a 15-second timeout, matching the reference webhook contract. OUTPUT reports `api_accepted` or `failed` without credentials or endpoint URLs; acceptance is not proof the recipient read or received the message. A failure retains all scraped data in the dataset, and is not automatically retried. Redirects are refused so credentials are not forwarded to another endpoint. Slack/Discord payloads suppress unexpected mentions from scraped text. WhatsApp requires the customer's own eligible business account and messaging session; API version is configurable.

Notification adapters have been validated with injected transports; end-to-end customer-channel delivery is not established. An API acceptance status does not guarantee receipt.

`enrichDetail` / `includeDetails` attempts the public job detail page and reads identity-matched JobPosting metadata or SSR details. `enrichCompany` attempts public company SSR fields. Missing or blocked deep fields stay null, original listing content is retained, and status/warnings identify incomplete enrichment. Requests are cached per job/company within the run. Currency/period are taken only from explicit structured source fields, never guessed from a currency symbol.

`appConnector` accepts an Apify-managed MCP connector and writes one Notion page (`notion-create-pages`) or issue (`create_issue`, with `mcpIssueTeam`) summarizing the delivered results. The proxy uses the run token; upstream service credentials remain with Apify. Tool discovery is paginated, tool arguments are validated against the advertised schema, and no other tool names are called. Unsupported tools, missing teams and unavailable connector runtime are reported without losing dataset rows. See [Apify's connector development guide](https://docs.apify.com/integrations/mcp-connectors/use-in-actors).

Connector behavior has been validated with mocked sessions. End-to-end writes to a customer connector are not established and require the customer’s configured connection.

Configured proxies are used for every Wellfound request in the run. A proxy initialization failure stops the run instead of silently switching to direct access. OUTPUT reports `requestRouting=direct|proxy`; proxy URLs and credentials are never included. This does not guarantee source access from every proxy provider.

##### Company field coverage

Company output retains `companyUrl` and the explicit public `companyBadges`.
`companyFieldCoverage` and `companyMissingFields` report availability across the
11 deep company fields, independently of `coverage=observed_jobs_only` (the job
count scope). `parsed` means some enrichment data was parsed, not that all fields
are available. Company aggregation retains `companyEnrichmentStatuses` and
company enrichment warnings, including source failures and missing company URLs.

Company SSR matching accepts `Startup` and `StartupResult` entities, requires an
explicit ID or slug, rejects conflicting identities, and caches by URL plus
identity. Explicit deep fields in listing SSR are retained without an extra
request. Existing nonempty listing values survive company enrichment. Unknown
fields remain null/empty; unresolved Apollo references are not exported. Stage
badges and job descriptions are not used to guess funding, founders or industry.

Successful retrieval of every deep-company field is not established. Blocked or missing enrichment remains explicit; no account or challenge bypass is used.

##### Filter contracts and URL precedence

`salaryMin` compares the job's **upper** salary bound with the requested minimum;
`salaryMax` compares its **lower** bound with the requested maximum (range
intersection, equality included). A minimum requires a known upper bound. The
maximum alias only excludes a known lower bound above its ceiling; unknown
salary still obeys `includeNoSalary`. `minSalary` and `maxSalary` retain the older
lower/upper-bound containment semantics and override their respective aliases,
including explicit zero. Active alias semantics participate in incremental scope
hashing; reuse of an old scope with changed semantics requires a new state key.

`fulltime` and `parttime` normalize to `full-time` and `part-time`.
`maxPages` accepts 1–200. Traversal stops at the source last page or result cap, whichever comes first; the configured ceiling does not promise that many source pages.

`compatibilityMode` accepts `auto` (default), `blackfalcon`, or `crawlerbros`.
Auto selects Blackfalcon URL semantics when a Blackfalcon-specific input is
present (`remote`, `salaryMin`, `salaryMax`, `roles`, `query`, `searchUrls`, array
`location`, or an unhyphenated job type). Ambiguous inputs retain legacy behavior;
use an explicit mode for mixed-client inputs.

In Blackfalcon mode, nonempty `startUrls` overrides query/role/location/remote
search constraints, including `jobTitle` and `keyword`. Employment, salary,
visa, radius, date, exclude-location, include/exclude-keyword and custom
post-fetch filters remain active. This distinction follows the reference's
separate search routing and job/post-fetch filter sections. Pasted `searchUrls`
ignore location/remote only for those sources, including subsequent pages;
role-generated sources in the same run still apply them. Crawlerbros mode keeps
URL plus client-side filtering.

##### Source selection diagnostics

SSR role searches are selected by the requested page/role/remote scope instead
of the first Apollo cache entry. A conflicting or ambiguous search cache fails
explicitly. SOURCE\_DIAGNOSTICS now records per-page requested URL, HTML SHA-256,
selected search key, reported company page size/count, parsed job IDs and accepted
job IDs, plus fetch status/final URL. This adds no requests and does not expand
`maxPages` or mix a default remote source into a pasted URL.

`maxResults` is a filtered delivery budget, including in Blackfalcon compatibility mode. `maxPages` stays an explicit page ceiling; this Actor does not copy undocumented competitor page expansion. Set `maxPages=2` to request up to two pages. Unknown source booleans remain null rather than being inferred as false.

##### Source coverage and conservative expiry

Every run, including non-incremental collection, reports `OUTPUT.sourceCoverage`
(also exposed as `coverage`), `expirySuppressedReason`, and `emitExpiredRequested`.
`scanComplete` retains its traversal/budget meaning; it does **not** claim that
all source jobs were observed. `sourceCoverage.expiryEligible` requires traversal
success, raw unique job IDs matching each source's declared total, every page
from 1 through its declared last page, consistent per-page totals/page counts,
selected search metadata, complete company identities, and no repeated job or
company IDs within that source. Missing evidence fails closed. Counts are before
user filters and delivery limits. Sources are checked separately; overlap between
different sources does not by itself invalidate coverage.

`SOURCE_DIAGNOSTICS.sources[].pages[]` includes `reportedTotalJobCount`,
`reportedPageCount`, and `sourceCompanyIds`. Request `cacheHeaders` persist only
`date` and `age` response headers when present; cookies and other response headers
are not stored. Cache timing is explanatory, not proof of complete coverage.
A passing gate means these necessary checks passed, not an atomic server snapshot
or independent confirmation that an individual vacancy closed.

Legacy state without the required page evidence cannot authorize expiry. Natural job expiry has not been established through long-term live observation.

For a small incremental workflow, run this input serially with a unique `stateKey` shared only by that schedule. The first run seeds delivered state; later runs return eligible changes.

```json
{
  "startUrls": ["https://wellfound.com/role/r/software-engineer"],
  "maxItems": 5,
  "maxPages": 1,
  "scanMaxItems": 100,
  "incrementalMode": true,
  "stateKey": "well-expiry-smoke-REPLACE-WITH-UNIQUE-ID",
  "emitExpired": true,
  "skipReposts": true,
  "requestDelayMillis": 0
}
```

For this multi-page source a one-page scan is insufficient for expiry and must produce zero EXPIRED records. A later run can still return NEW records because only successfully delivered observations are committed. Keep each state key scoped to one stable search and filter set. This example does not force a disappearance or demonstrate natural job closure.

### Support

Open an issue from this Actor’s **Issues** tab with the run ID, expected result and a redacted input. Do not include tokens, cookies or private credentials. Include the output profile and affected field when reporting a migration mismatch.

### Presentation update

The current listing adds workflow examples, grouped input documentation, output-field explanations and plan-based result discounts. Source-specific limitations above still apply. This is an independent tool and is not affiliated with the source platform.

# Actor input Schema

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

Wellfound /jobs, /role, /role/r, or /role/l URLs. When omitted, role/location/remoteOnly can build a source URL automatically. In blackfalcon compatibility mode these replace query/roles/location/remote search constraints; job and post-fetch filters still apply.

## `role` (type: `string`):

Role name or slug, for example software engineer. Enables server-rendered role pagination.

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

Compatibility filter: title contains this phrase, case-insensitive.

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

Compatibility filter across title, company, locations and description.

## `location` (type: `array`):

One or more location slugs (e.g. \["new-york", "san-francisco", "berlin"]). Each location × role combination spawns a separate search thread. For /jobs?q= searches, use a human-readable city name. Ignored for pasted searchUrls (they carry their own location).

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

Compatibility filter for the jobType field exposed in Wellfound's public search payload.

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

Best-effort experience filter using title and description terms.

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

When false, rows without a parseable USD salary range are excluded.

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

Migration-compatible filter matching supplied terms against company name/slug, consistent with the head competitor's documented behavior.

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

Optional allow-list of company names or slugs.

## `since` (type: `string`):

ISO date/timestamp applied to Wellfound liveStartAt for scheduled incremental runs.

## `requestDelayMillis` (type: `integer`):

Optional delay before each HTTP attempt.

## `scanMaxItems` (type: `integer`):

Maximum candidate jobs scanned before change selection. maxItems remains the delivery cap.

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

Additional source roles or URLs; ignored when startUrls is set.

## `searchUrls` (type: `array`):

Additional source roles or URLs; ignored when startUrls is set.

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

Alias of remoteOnly; remoteOnly takes precedence.

## `salaryMin` (type: `integer`):

Drop jobs whose upper salary bound is below this value; missing upper bound is excluded. minSalary takes precedence and retains its lower-bound semantics.

## `salaryMax` (type: `integer`):

Drop jobs whose known lower salary bound exceeds this value. maxSalary takes precedence and retains its upper-bound semantics.

## `visaSponsorship` (type: `boolean`):

true requires explicit support. false excludes explicit refusal but keeps unknowns. Omit for no filter.

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

Select fields; source Markdown is retained verbatim. Missing source HTML stays null with a warning.

## `query` (type: `string`):

Free-text job search (e.g. "rust engineer", "ML ops", "solidity developer"). Maps to Wellfound's /jobs?q= endpoint. Combine with roles/searchUrls to union multiple searches.

## `experienceLevel` (type: `string`):

Filter by minimum required years of experience. Entry: 0-2, Mid: 2-5, Senior: 5-8, Staff+: 8+. Jobs without experience data are kept.

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

Drop jobs whose max equity grant is below this percent. 0 = no minimum. Jobs without equity data are dropped when minimum is set.

## `includeKeywords` (type: `object`):

Require at least one of these keywords. Example: {"keywords":\["rust","golang"],"matchTitle":true,"matchDescription":true,"matchSkills":true}. Matches default to all three fields.

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

Output only core fields (id, title, companyName, portalUrl, locationNames, remote, compensation, postedAt). For AI-agent/MCP workflows.

## `descriptionMaxLength` (type: `integer`):

Truncate job description to this many characters. 0 = no truncation.

## `locationText` (type: `string`):

Location filter. With role, it also builds Wellfound's public role/location route.

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

In incremental mode, suppress newly assigned job IDs whose content matches a previously delivered job confirmed expired by a complete scan. Incomplete scans never expire jobs. Applies to state first recorded with content hashes; older records acquire hashes on their next delivered update.

## `originLocation` (type: `string`):

Origin city for radius filtering, resolved from offline GeoNames city data. Unknown origins skip the filter with an OUTPUT warning. Unresolved job locations are excluded when the origin is known. Distances use city centres.

## `radiusMiles` (type: `integer`):

Drop jobs further than N miles from originLocation, using job coordinates where available. US-standard unit.

## `radiusKm` (type: `integer`):

Kilometer alternative to radiusMiles. If both set, radiusMiles wins. Use this for EU-based searches.

## `cityName` (type: `string`):

Origin city alias for radius filtering; takes precedence over originLocation and explicit coordinates.

## `lat` (type: `number`):

GPS latitude. Pair with lon. Ignored when cityName is set.

## `lon` (type: `number`):

GPS longitude. Pair with lat.

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

Chat ID (from @userinfobot) or channel ID (starts with -100 for channels). Required when telegramToken is set.

## `whatsappPhoneNumberId` (type: `string`):

WhatsApp Business phone number ID from Meta Business Manager (the numeric ID next to your number — NOT the phone number itself). Free service-conversation messages within 24 hours of last user-initiated contact.

## `whatsappTo` (type: `string`):

Recipient phone number in E.164 format (e.g. +4512345678). The recipient must have messaged your business number within the last 24 hours.

## `includeRunMetadata` (type: `boolean`):

Prepend a one-line summary (search label + total count) to each notification batch.

## `whatsappApiVersion` (type: `string`):

Graph API version used for the configured WhatsApp business sender. Set a version supported by your Meta application.

## `enrichDetail` (type: `boolean`):

Core job fields — title, full description, compensation, location, remote status, experience level, job type, and company name/logo/size — are delivered inline for every listing, no toggle needed. This option additionally attempts deep per-listing fields (structured salary currency/period, company website, industry, benefits, skills). Those deep fields are not currently available and will return null, so leave this off unless you specifically need them and accept they may be empty.

## `enrichCompany` (type: `boolean`):

Company basics — name, logo, slug, size — are delivered inline for every listing. This option additionally attempts deep firmographics (funding stage, total raised, investors, founders, social links, founded year, markets). Those deep fields are not currently available and will return null.

## `includeDetails` (type: `boolean`):

Fetch the detail page for each listing to retrieve the full description text and contact information. Increases run time and cost. Leave off for a fast, low-cost run.

## `appConnector` (type: `string`):

Optional Apify MCP connector supporting notion-create-pages or create\_issue. Writes one run summary after data delivery; other tools are not called. Configure mcpIssueTeam for issue trackers.

## `mcpIssueTeam` (type: `string`):

Team name or ID required when the selected connector supports create\_issue.

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

Return only remote jobs. With role and no location, uses the public remote-role route.

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

Require the parsed lower bound of a USD salary range to be at least this value.

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

Optional block-list of company names or slugs.

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

Sort accepted rows by Wellfound liveStartAt, or preserve source order.

## `fromDate` (type: `string`):

Alias of since; ISO date or timestamp.

## `excludeEmptyFields` (type: `boolean`):

Drop null, empty strings, arrays and objects; keep zero and false.

## `excludeLocations` (type: `array`):

Drop jobs whose locationNames match any of these (substring match, e.g. "India" drops "Bangalore, India"). Applied after fetch.

## `toDate` (type: `string`):

Only include jobs posted on or before this date. Accepts YYYY-MM-DD or ISO-8601.

## `excludeKeywords` (type: `object`):

Drop jobs containing any of these keywords. Same shape as includeKeywords.

## `companyOnlyMode` (type: `boolean`):

Aggregate accepted jobs into one record per company. maxResults bounds jobs scanned before aggregation. jobCount is the number of observed jobs in this run, not all openings. Incremental mode is not applied.

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

When ON (default) AND incrementalMode is ON, send notifications only for NEW and UPDATED jobs. Without incrementalMode, this has no effect (all emitted jobs are notified).

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

Require the parsed upper bound of a USD salary range to be at most this value. 0 means unlimited.

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

Maximum job rows saved after filtering.

## `maxPages` (type: `integer`):

Maximum public role-search pages to fetch. Wellfound currently exposes pageCount in SSR data.

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

Retries for transient 403/429/5xx and network failures.

## `maxResults` (type: `integer`):

Alias of maxItems; 0 means a bounded 5000-row ceiling, not unlimited. maxPages still applies.

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

Only include jobs posted within the last N minutes. 0 = no limit. Pairs well with scheduled runs for real-time monitoring.

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

Emit NEW/UPDATED jobs using state committed only after delivery. Run the same stateKey serially.

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

Required in incremental mode. Changing search filters requires a new key.

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

Emit EXPIRED only after a complete, error-free scan; suppress expiration on partial snapshots.

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

In incremental mode, include unchanged jobs with changeType=UNCHANGED. Delivered rows still commit state only after successful delivery.

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

Slack incoming webhook URL (Slack App → Incoming Webhooks → Add New Webhook). Formats jobs as Slack blocks with company, salary, funding stage, and location.

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

Discord channel webhook URL (Server Settings → Integrations → Webhooks → New Webhook). Sends rich embeds colour-coded by change type (green=NEW, amber=UPDATED).

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

Universal escape hatch for n8n / Make / Zapier / custom HTTP backends. Receives a JSON POST { metadata, items } per run. Single attempt, 15 s timeout.

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

Additional HTTP headers sent with the generic webhook POST (e.g. {"Authorization":"Bearer ..."}).

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

Maximum number of jobs to send per notification batch. Prevents spam on large result sets.

## `customFilters` (type: `array`):

Array of rules applied to any output field. Each rule: {"field":"<name>","op":"includes|notIncludes|equals|notEquals|gt|gte|lt|lte","value":...}. Example: \[{"field":"companyFundingStage","op":"equals","value":"series-b"}, {"field":"description","op":"notIncludes","value":"wordpress"}].

## `compatibilityMode` (type: `string`):

auto detects Blackfalcon-specific input names or array location; ambiguous inputs preserve legacy URL plus client filters. blackfalcon direct URLs ignore search routing constraints; pasted searchUrls ignore location/remote per source. Explicit post-fetch filters remain active.

## `proxyConfiguration` (type: `object`):

Optional Apify or custom proxy used for listing, detail and company requests. Omit or disable for direct access. Configured proxy failures do not silently fall back to direct.

## `telegramToken` (type: `string`):

Telegram bot token from @BotFather. Sends notifications when new jobs are emitted. Pairs well with incrementalMode for real-time alerts.

## `whatsappAccessToken` (type: `string`):

Meta Cloud API access token with whatsapp\_business\_messaging scope. Get a permanent token from a system user in Meta Business Manager.

## `crawlerbrosInput` (type: `object`):

Paste the complete competitor input JSON here to preserve its field types and defaults. Put all options inside this object. This is optional; normal native inputs remain available.

## `blackfalconInput` (type: `object`):

Paste the complete competitor input JSON here to preserve its field types and defaults. Put all options inside this object. This is optional; normal native inputs remain available.

## Actor input object example

```json
{
  "jobType": "any",
  "experience": "any",
  "includeNoSalary": true,
  "requestDelayMillis": 0,
  "scanMaxItems": 2000,
  "query": "software engineer",
  "equityMin": 0,
  "compact": false,
  "descriptionMaxLength": 0,
  "skipReposts": false,
  "radiusMiles": 0,
  "radiusKm": 0,
  "includeRunMetadata": true,
  "whatsappApiVersion": "v23.0",
  "enrichDetail": false,
  "enrichCompany": false,
  "includeDetails": false,
  "sort": "none",
  "companyOnlyMode": false,
  "notifyOnlyChanges": true,
  "maxItems": 20,
  "maxPages": 10,
  "maxRequestRetries": 2,
  "maxAgeMinutes": 0,
  "incrementalMode": false,
  "emitExpired": false,
  "emitUnchanged": false,
  "notificationLimit": 5,
  "compatibilityMode": "auto"
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

## `diagnostics` (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 = {
    "query": "software engineer",
    "enrichDetail": false,
    "enrichCompany": false,
    "includeDetails": false,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("peerless_columbine/wellfound-jobs-scraper-api").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 = {
    "query": "software engineer",
    "enrichDetail": False,
    "enrichCompany": False,
    "includeDetails": False,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("peerless_columbine/wellfound-jobs-scraper-api").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 '{
  "query": "software engineer",
  "enrichDetail": false,
  "enrichCompany": false,
  "includeDetails": false,
  "maxItems": 20
}' |
apify call peerless_columbine/wellfound-jobs-scraper-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,peerless_columbine/wellfound-jobs-scraper-api"
        }
    }
}

```

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/BiUgcZcjV20Fno1wV/builds/IDoODdnWNTvanone0/openapi.json
