Search Wellfound (formerly AngelList Talent) for startup job postings — supporting the full filter surface (role, location, remote policy, experience level, job type, salary + equity ranges with currency, company size + stage, markets, skills, visa sponsorship, recency, sort, pagination) — and return structured JSON jobs with full company, recruiter, salary/equity, and description data. Read-only.
Works with
AI-first code editor with Composer
Before installing skills in Cursor, ensure your development environment meets these requirements:
node --versionsearch-startup-jobsExecute the skills CLI command in your project's root directory to begin installation:
Fetches search-startup-jobs from wellfound.com/search-startup-jobs-5ek1ik and configures it for Cursor.
The CLI shows a list of agents. Use arrow keys and space to select Cursor:
Confirm successful installation by checking the skill directory location:
Restart Cursor to activate search-startup-jobs. Access via /search-startup-jobs in your agent's command palette.
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your environment. Always review source, verify the publisher, and test in isolation before production.
Submit your Claude Code skill and start earning
Automate repetitive workflows and reduce manual effort
Example
Generate reports, summarize documents, draft communications
Save 3-5 hours per week on routine tasks
Learn new skills, understand complex topics, get expert guidance
Example
Explain concepts, provide examples, suggest learning resources
Accelerate learning and skill development by 2x
Enhance output quality through reviews, suggestions, and refinements
Example
Review drafts, suggest improvements, catch errors
Improve work quality by 30-40% with less effort
0
total installs
0
this week
0
upvotes
Run in your terminal
0
installs
0
this week
—
stars
| name | search-startup-jobs |
| title | Wellfound Startup Job Search |
| description | >- Search Wellfound (formerly AngelList Talent) for startup job postings — supporting the full filter surface (role, location, remote policy, experience level, job type, salary + equity ranges with currency, company size + stage, markets, skills, visa sponsorship, recency, sort, pagination) — and return structured JSON jobs with full company, recruiter, salary/equity, and description data. Read-only. |
| website | wellfound.com |
| category | jobs |
| tags | - jobs - startups - wellfound - angellist - datadome - graphql |
| source | 'browserbase: agent-runtime 2026-05-16' |
| updated | '2026-05-16' |
| recommended_method | browser |
| alternative_methods | - method: api rationale: >- Wellfound has no public developer API — apitracker.io/a/wellfound confirms every developer-docs field is empty. The internal Apollo /graphql endpoint is callable only from inside a page-warmed browser context (CSRF + datadome + _wellfound cookies). Cookieless GraphQL POSTs return 401/403. Treat the page's __NEXT_DATA__ Apollo state as the API surface. - method: url-param rationale: >- SEO landing pages (/role/<role>, /role/l/<role>/<loc>, /location/<loc>, /company/<slug>/jobs) accept only ?page=N — they don't expose salary/equity/stage/size/skills filters. The dynamic /jobs filter app uses non-stable URL-param encoding (robots.txt confirms /*?role=*, /*?jobId=*, /*?jobSlug=* are all dynamic). URL-only construction is reliable for role+location queries; the full filter surface requires driving the UI. |
| verified | false |
| proxies | false |
Given a Wellfound (formerly AngelList Talent) job-search intent — a free-form role+location, a full /jobs?… URL, a /company/<slug>/jobs URL, or a single job-slug URL — return matching startup job postings as structured JSON. For each posting: job id + canonical URL, title, company (name, slug, logoUrl, Wellfound URL, short pitch, stage, size, total funding when surfaced), location(s) and remote policy, posted timestamp, employment type, experience level, base salary range and equity range (with currency), required skills, full long-body description, recruiter / hiring-manager reference when surfaced, and the application URL (Wellfound's apply route or the company's ATS). Read-only — never clicks Apply, Save, Message Recruiter, Follow Company, or any mutation control.
/company/openai/jobs.apitracker.io/a/wellfound shows every developer-docs field empty); the data is only available through the Wellfound web app.The Wellfound web app is a Next.js + Apollo GraphQL client. Every search/listing/company page ships a complete Apollo graph in <script id="__NEXT_DATA__"> — that's the optimal extraction surface (no DOM scraping, no per-field selector brittleness). Two architectural facts dominate:
/graphql endpoint is reachable only through the live web app with a valid CSRF token, datadome cookie, and _wellfound session cookie — replaying it cookieless or out-of-context returns 403/401.browse cloud fetch --proxies https://wellfound.com/ returned 200 OK with the static landing HTML; every job-search path (/jobs, /jobs?role=…, /role/<role>, /role/l/<role>/<loc>, /location/<loc>, /company/<slug>, /company/<slug>/jobs, /sitemap.xml) returned a 403 with x-datadome: protected and a captcha-delivery interstitial. The Browserbase Fetch API (no Verified fingerprint) cannot get past DataDome — a full browse cloud browse session with --verified --proxies is mandatory.The result: lead with scripted browsing through Browserbase. If, mid-session, you observe an XHR/fetch to /graphql carrying a JobSearchResults-shaped operation that succeeds with the page-warmed cookies, capture the operation hash + variables and replay it within the same browser context for pagination — but never as a cookieless out-of-band request.
SID=$(browse cloud sessions create --keep-alive --verified --proxies | jq -r '.id')
browse cloud browse --connect "$SID" open "https://wellfound.com/"
browse cloud browse --connect "$SID" wait load
browse cloud browse --connect "$SID" wait timeout 2500 # let DataDome's JS challenge finish
--verified AND --proxies are both required. A bare session or a browse cloud fetch --proxies (no Verified) returns DataDome's 403 + x-datadome: protected HTML on every job route — verified across 9 URL probes during skill generation.
Wellfound gates most of the high-value surface behind login:
/jobs?… filter app redirects unauthenticated visitors to /jobs/login for any non-trivial filter combination (anything beyond a bare role/location SEO landing page).Use the cookie-sync skill (/tmp/bb-skills/skills/cookie-sync/SKILL.md) to import a logged-in _wellfound session cookie from a real authenticated browser into your Browserbase session. After cookie injection, hard-refresh https://wellfound.com/jobs and confirm the top-right nav shows the user avatar (not the "Sign in" button) before issuing any filtered search.
If no logged-in context is available, the skill degrades gracefully to guest mode — usable only for unauthenticated SEO landing pages (/role/<role>, /role/l/<role>/<loc>, /location/<loc>, /company/<slug>). Document auth_state: "guest" in the output so downstream consumers know salary/equity fields will be null.
| Input shape | Action |
|---|---|
Full https://wellfound.com/jobs?… URL | Use as-is. |
/company/<slug> or /company/<slug>/jobs URL | Use as-is — single-company extraction. |
/jobs/<id>-<slug> single-job URL | Use as-is — single-page extraction (skip search). |
| Free-form role+location | First try the SEO landing path — it's lighter and renders without login. Slug the role (lowercase, hyphenated, must match one of Wellfound's ~50 curated role slugs — see Gotchas) and the location (city slug, e.g. san-francisco, new-york, london). Combine: /role/l/<role>/<loc>. If only a role is given: /role/<role>. If only a location: /location/<loc>. |
| Free-form intent with filters beyond role+location (salary, equity, stage, size, skills, market tags, visa, recently-active, sort, remote-policy, distributed-only) | Navigate /jobs, then drive the filter UI (step 4) — the SEO landing pages do not expose this surface. Requires login. |
Wellfound's SEO landing pages (/role/…, /location/…) accept only ?page=<N> as a query param — they are not the dynamic filter app. The dynamic filter app lives at /jobs?… and is what you need for the full filter surface described in the task spec.
/jobs filter UI (logged-in path) — full filter surfacebrowse cloud browse --connect "$SID" open "https://wellfound.com/jobs"
browse cloud browse --connect "$SID" wait load
browse cloud browse --connect "$SID" wait timeout 3000
browse cloud browse --connect "$SID" snapshot
The filter rail lives on the right side of the page. Each control is a button that opens a popover/menu; you click options inside, then click outside to close. The filter surface (per Wellfound's own help docs, help.wellfound.com/article/777):
| Filter | UI control | Notes |
|---|---|---|
| Role | Multi-select dropdown ("Role") | ~50 curated roles. Free-text role title NOT supported in this control — for arbitrary role-text matching, use the Keywords filter. |
| Location | Multi-select dropdown ("Location") | Cities / metros / countries. Multi-select. Includes a special "Remote" entry. |
| Remote policy | Oval button inside the location field (defaults to "Worldwide") | Three values: None (on-site only), Some (remote OK + on-site), Only remote. |
| HQ-from (companies based in) | Required sub-control when remote ≠ None | "Show remote jobs of companies based in" — accepts countries/regions. Filters by company HQ, not your location. |
| Distributed teams only | Toggle in the remote popover | Limits results to companies self-identifying as primarily/entirely remote. |
| Salary | Min + Max number inputs, currency dropdown | Default behavior: jobs without listed salary are filtered out when min/max set. Toggle "Include jobs with no salary listed" to include unlisted. |
| Equity | Dual-handle range slider | % equity, 0 to ~5%+. Surfaces on most listings even when salary is hidden. |
| Job type | Multi-select checkboxes | full-time, contract, internship, cofounder. |
| Experience level | Multi-select checkboxes | intern, junior, mid, senior, principal/exec. (Map task input verbatim — Wellfound's UI labels are Entry-Level, Mid-Level, Senior, etc.) |
| Investment stage | Multi-select | bootstrapped, seed, series_a, series_b, series_c, series_d_plus, public, acquired. |
| Company size | Multi-select | Enum: SIZE_1_10, SIZE_11_50, SIZE_51_200, SIZE_201_500, SIZE_501_1000, SIZE_1001_5000, SIZE_5000_PLUS. |
| Industries / markets | Multi-select autocomplete | Wellfound's market tags (AI, FinTech, B2B SaaS, Climate, …). Free-typed values must autocomplete to a known tag. |
| Tech stack / skills | Multi-select autocomplete | Skill tags (Python, React, Postgres, …). Same autocomplete behavior. |
| Visa sponsorship | Checkbox ("Will sponsor visa") when surfaced | Not surfaced on all variants; check snapshot for presence. |
| Recently active | Dropdown ("Last active") | "Within last 24 hours", "Within last week", "Within last month". |
| Keywords | Free-text input | Runs across job listing + company text. Use this for free-text role titles not in the 50-role taxonomy. |
| Sort order | Dropdown ("Sort by") | Relevance (default), Newest. |
For each filter the caller passes:
browse cloud browse --connect "$SID" snapshot to find the trigger button's ref.click the trigger → wait timeout 800 for the popover.type <value>, wait timeout 1000 for the autocomplete dropdown, click the matching option: ref. Do not use fill — fill synthesizes an Enter keypress that submits the filter before the autocomplete dropdown surfaces (same gotcha as OpenTable's typeahead).checkbox: <label> ref inside the popover.browse cloud browse drag <x1> <y1> <x2> <y2> on the handle; values are approximate — snap to the nearest visible tick label.After all filters are applied, the URL updates with a serialized filter state but the encoded form is not stable — do not try to construct /jobs?role=…&location=… URLs directly. Drive the UI and let the app build the URL. (Wellfound's robots.txt confirms the URL params are dynamic — Disallow: /*?role=*, Disallow: /*?jobId=*, Disallow: /*?jobSlug=* etc.)
After the results grid is rendered, extract __NEXT_DATA__ in a single CDP call:
browse cloud browse --connect "$SID" eval \
"JSON.stringify(JSON.parse(document.getElementById('__NEXT_DATA__').textContent).props.pageProps.apolloState.data)" \
> /tmp/page1-graph.json
The graph is a flat key/value map. Iterate keys and pick out:
StartupResult:<id> — one per matched company on the search page. Fields include id, name, slug, logoUrl, highConcept (company one-liner), companySize (SIZE_* enum), badges (e.g. ACTIVELY_HIRING), highlightedJobListings (an array of JobListingSearchResult refs).JobListingSearchResult:<id> — search-grid job entries. Fields: id, title, slug, primaryRoleTitle, jobType (full_time / contract / internship / cofounder), remote (bool), locationNames ({type: "json", json: ["Bengaluru", ...]} — note the nested wrapper), liveStartAt (epoch seconds, the posted timestamp), compensation (a short human-readable string like $120k – $180k • 0.1% – 0.5% — already pre-formatted; salary and equity ranges are baked into this string and must be regex-extracted), descriptionSnippet (short HTML excerpt — NOT the full body).Startup:<id> — full company profile (only present on /company/<slug> pages, not on /jobs search pages). Fields: name, slug, logoUrl, highConcept, companySize, totalRaisedAmount, companyUrl, twitterUrl, linkedInUrl, productHuntUrl, jobPreamble, plus the cursor-paginated jobListingsConnection({...}) key (see step 7).seoLandingPageJobSearchResults:… — search meta. Read pageCount and pageSize to know how many pages to fetch.User:<id> / Recruiter:<id> — recruiter / hiring-manager refs, surfaced on full job pages when present.Unpack references. Apollo serializes nested objects as {type: "id", id: "<key>"} pointers. Resolve them by looking up the key in the same data map. The canonical flattener is:
function unpack(node, graph) {
if (node && typeof node === 'object' && node.type === 'id' && node.id) {
return unpack(graph[node.id], graph);
}
if (Array.isArray(node)) return node.map(v => unpack(v, graph));
if (node && typeof node === 'object') {
const out = {};
for (const k of Object.keys(node)) out[k] = unpack(node[k], graph);
return out;
}
return node;
}
The JobListingSearchResult graph node only carries descriptionSnippet. To get the full long-body description, the visible skill tag list, the application URL (Wellfound's apply route vs. the company's external ATS), and the recruiter reference, you have to open the job's own page. The canonical URL is:
https://wellfound.com/jobs/<id>-<slug>
where <id> and <slug> come from the JobListingSearchResult node. Open each detail page in the same session (sequentially — Wellfound rate-limits parallel navigations on the same session), and extract __NEXT_DATA__ again. The job-detail graph contains a JobListing:<id> node with the full description (HTML/Markdown body), skills: [{id, name}], applyUrl or atsSource/atsUrl, and the recruiter ref.
Pace at ~1 detail page / 1.5s. If you need 50+ details, consider extracting only the IDs first and short-circuiting to a "summary only" output mode for clients that don't need full bodies.
Two pagination modes coexist:
SEO landing pages (/role/<role>, /role/l/<role>/<loc>, /location/<loc>): query-param pagination. Open <base>?page=<N> for N in 2..pageCount. Read pageCount from seoLandingPageJobSearchResults:*.pageCount. Each page is its own browse cloud browse open (DataDome resets the JS challenge state; the cookie persists within the session).
/jobs filter app + /company/<slug>/jobs: cursor pagination, no query-param form. The Apollo key is jobListingsConnection({"after":"<cursor>","filters":{...},"first":20}). The first page's cursor is MA== (base64 for 0). Scroll the results grid to the bottom — Wellfound infinite-scrolls and the next jobListingsConnection(…) graph node appears in __NEXT_DATA__ after each scroll-triggered fetch. To collect all pages:
for i in $(seq 1 20); do
browse cloud browse --connect "$SID" scroll 0 0 0 2000
browse cloud browse --connect "$SID" wait timeout 1500
browse cloud browse --connect "$SID" eval \
"JSON.stringify(JSON.parse(document.getElementById('__NEXT_DATA__').textContent).props.pageProps.apolloState.data)" \
> "/tmp/scroll-$i.json"
# exit when the most-recent jobListingsConnection has no more `edges` beyond what you've already seen
done
If the input is a /jobs/<id>-<slug> URL or a /company/<slug>/jobs/<id>-<slug> URL, skip search entirely:
browse cloud browse --connect "$SID" open "$INPUT_URL"
browse cloud browse --connect "$SID" wait load
browse cloud browse --connect "$SID" wait timeout 2500
browse cloud browse --connect "$SID" eval \
"JSON.stringify(JSON.parse(document.getElementById('__NEXT_DATA__').textContent).props.pageProps.apolloState.data)" \
> /tmp/job.json
Parse the JobListing:<id> node + linked Startup:<id> node from the graph and emit a single-job result.
browse cloud sessions update "$SID" --status REQUEST_RELEASE
Apply, Apply now, Save, Message recruiter, Follow company, Share, or Easy Apply (Wellfound's one-click apply that POSTs immediately on click). Do not submit any form. Do not interact with the chat widget.browse cloud fetch (with or without --proxies) returns 403 + x-datadome: protected on EVERY job-related route. Verified during skill generation: /, /discover/blog, /landing-page-assets/* work; /jobs, /role/*, /location/*, /company/*, /sitemap.xml, even /jobs/123 all 403. A full browse cloud browse session with --verified --proxies is the only path that gets through. A bare browserbase session (no Verified, or no proxies) also fails — both flags are required, not one or the other./jobs/login; salary and equity fields render as "Sign in to see"; descriptions are truncated to a snippet; pagination beyond page 1 of the /jobs app drops to the login wall. Cookie-sync from a logged-in account is required for the full filter+detail surface. Without it the skill must report auth_state: "guest" and leave salary/equity as null.apitracker.io/a/wellfound. Every developer-docs field (API Reference, SDKs, OAuth playground, GraphQL playground, OpenAPI spec, pagination style, rate limits, status page) is empty. The internal /graphql endpoint is callable only from within a page-warmed browser context (CSRF + cookies). Do not attempt cookieless GraphQL POSTs — they return 401/403.__NEXT_DATA__ is the optimal extraction surface. Path: props.pageProps.apolloState.data. Don't bother writing DOM selectors against the rendered job cards — they're React-managed, class names are content-hashed (_card_a1b2c3), and they re-render. Pull the JSON.highlightedJobListings, recruiter, markets, skills, locationNames all serialize as {type: "id", id: "<key>"} pointers (or arrays of them). Always look up against the same data map. The unpack-references function in step 5 of Workflow is canonical.compensation field is pre-formatted, not structured. JobListingSearchResult.compensation is a string like "$120k – $180k • 0.1% – 0.5%" or "₹50,000 – ₹1L" (Indian companies use INR formatting with L/Cr suffixes). To get structured salary_min / salary_max / equity_min / equity_max / currency, regex it: /(?<cur>[\$₹€£])(?<smin>[\d.,]+[kKmMLCr]?)\s*[–-]\s*(?<cur2>[\$₹€£]?)(?<smax>[\d.,]+[kKmMLCr]?)/. Multiply by k/L/Cr suffix multipliers (k=1e3, L=1e5, Cr=1e7). On the job-detail page (JobListing:<id> node), the structured compensationStructured field is sometimes present — prefer that when available.locationNames is double-wrapped. It's {type: "json", json: ["Bengaluru", "Remote"]} — the actual array is at .json, not at the top level. Easy to miss.liveStartAt is epoch seconds, not ms. Multiply by 1000 if you need a JS Date.companySize is an enum, not a range. Values: SIZE_1_10, SIZE_11_50, SIZE_51_200, SIZE_201_500, SIZE_501_1000, SIZE_1001_5000, SIZE_5000_PLUS. Map to human-readable in your output.jobType enum uses snake_case in the graph (full_time, part_time, contract, internship, cofounder) but the UI shows kebab-case ("full-time"). Match accordingly.badges is a wrapped enum. Top-level entries are {type: "id", id: "Badge:ACTIVELY_HIRING"} — unpack against Badge:ACTIVELY_HIRING in the same graph to get {id, name, label, tooltip, avatarUrl}. The most useful badge ID is ACTIVELY_HIRING (company is processing applications today).python-developer, software-engineer, front-end-developer, back-end-developer, full-stack-developer, data-scientist, data-engineer, devops-engineer, product-designer, ui-ux-designer, product-manager, marketing-manager, growth-marketer, sales-development-representative, account-executive, customer-success-manager, recruiter, operations-manager, etc. A misspelled or out-of-taxonomy role slug returns a 404 on /role/<role> — don't fabricate slugs; if the caller's role doesn't normalize cleanly, fall back to the /jobs filter UI's Keywords field (free-text)./role/l/<role>/<loc> order matters. Role first, then location, with the literal l/ separator. /role/<role>/l/<loc> is a 404./role/… and /location/… paginate by ?page=N only — no salary/equity/stage/size/skills filters. For the full surface you MUST drive the /jobs filter UI, which requires login.?page=N SEO pagination tops out around 30-50 pages depending on role popularity. Beyond pageCount the page renders an empty grid (no error). Always check seoLandingPageJobSearchResults.pageCount and stop at that value.fill on autocomplete filters triggers premature submission. Wellfound's filter typeaheads use a custom React combobox that listens for Enter to commit. browse cloud browse fill <ref> <value> synthesizes Enter and submits before the autocomplete dropdown renders. Use the click-then-type-then-click pattern (snapshot → click input → type → wait 1000 → click option). Same gotcha as OpenTable's location picker.browse cloud browse drag to set the equity range — there's no number input. Snap target to the nearest tick label in the snapshot. If the caller's equity bounds don't align to a tick, document equity_range_approximate: true in the output.Prerequisites
Time Estimate
15-45 minutes depending on use case complexity
Steps
Common Pitfalls
✓ Do
✗ Don't
💡 Pro Tips
✓ Use when
Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.
✗ Avoid when
Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.
calcareers.ca.gov/search-california-state-jobs-dgl84s
indeed.com/search-jobs-8yxl6y
recruiterroles.com/recruiting-jobs-search-sf3f31
jeffallan/claude-skills
wshobson/agents
wondelai/skills
search-startup-jobs has been reliable in day-to-day use. Documentation quality is above average for community skills.
I recommend search-startup-jobs for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
Keeps context tight: search-startup-jobs is the kind of skill you can hand to a new teammate without a long onboarding doc.
Keeps context tight: search-startup-jobs is the kind of skill you can hand to a new teammate without a long onboarding doc.
We added search-startup-jobs from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
Useful defaults in search-startup-jobs — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
Solid pick for teams standardizing on skills: search-startup-jobs is focused, and the summary matches what you get after install.
Registry listing for search-startup-jobs matched our evaluation — installs cleanly and behaves as described in the markdown.
Registry listing for search-startup-jobs matched our evaluation — installs cleanly and behaves as described in the markdown.
search-startup-jobs reduced setup friction for our internal harness; good balance of opinion and flexibility.
showing 1-10 of 48