Scrape Y Combinator's Work at a Startup board: every YC-backed company currently hiring, with role, company, YC batch, location, salary and detected tech stack. A small, high-signal board - every row is a funded startup. No browser, no proxy, no login. Schedule it daily to track new postings.
2026-09-08 — a short result set now says whose limit it was
Measured against the live Store listing: asking for the advertised maximum of 50 returned
20 (40%), and the count reproduced exactly across separate runs — so this is the board's real
inventory, not a flaky scrape. The status message said only Returned 20 YCombinator postings., which a
buyer who asked for 50 cannot tell apart from a broken Actor.
finishRun() now receives requested: maxJobs and appends the reason:
Returned 20 YCombinator postings. That is every posting on the board matching your
filters — asking for50 will not return more today.
RUN_STATS also carries requested and boardExhausted. Logic and tests live in
apify-actor-kit (inventory.ts); src/kit/ here is generated.
Every filter here was previously a bare continue, so a run that returned nothing
gave no clue which filter emptied it. Rejections are now counted per filter and
surfaced in the message and in RUN_STATS.filteredOut. A refused listing page is
counted too, so blocked is distinguishable from "no postings found".
failOnZeroResults (default true).
Fixed — two inputs advertised more than they deliver
The breakdown made both measurable in a single run, on 2026-08-27:
input
measured
now
equityOnly: true
rejected 20 of 20
titled rarely matches; description states that YC's list page does not state equity for most postings, so the filter nearly always empties the result
techStackFilter (devops/kubernetes/aws)
rejected 19 of 20
description states the stack is inferred from a very short title and blurb, and suggests filtering client-side for recall
Neither is silently broken any more: an emptied run now fails and names the filter
that did it. A filter that silently does not apply is a refund request.
Fixed — maxJobs advertised a ceiling it cannot reach
The schema offered up to 1,000. YCombinator's jobs page is a single
un-paginated list — measured 2026-08-27 it carried 20 postings, and this actor
does not paginate. Maximum corrected to 50.
Changed
One batched Dataset.pushData() per run instead of one call per row.
Unreleased — 2026-08-13
Documentation only. No behavior changes; build 0.0.3 is unmodified.
Added
Full documentation suite: docs/ARCHITECTURE.md, docs/DEVELOPMENT.md, docs/YC-DOM.md,
docs/IMPROVEMENT-PLAN.md, and CLAUDE.md.
.actor/dataset_schema.json — the output surface was previously undeclared.
README rewritten as a buyer-facing listing with an explicit known-limitations table.
Verified against live YC
Two platform runs on 2026-08-13:
Input
Rows
Cost
Duration
maxJobs: 10
10
$0.0010
5 s
maxJobs: 100
20
$0.0052
43 s
20 rows is source exhaustion: one server-rendered page, no pagination. Runs on CheerioCrawler over
plain HTTP with no browser, which is why it is among the cheapest actors in the portfolio.
Data quality is the best of the TypeScript actors — ycBatch, role, companyDescription and a
genuine multi-currency salary parser (USD/GBP/EUR/INR) all populate reliably.
Findings recorded (not fixed)
A zero-row run exits SUCCEEDED — total scrape failure is indistinguishable from success.
equityOnly: true returns zero rows, always.hasEquity is derived from title + company
blurb + role, which never mention equity, so it is false on every row.
Every selector is a Tailwind utility class (li.my-2.flex.h-auto.w-full, …). Any YC restyle
breaks extraction silently. Most fragile actor in the portfolio.
Detail chips (jobType, department, role, salary) are read by array index — a missing chip
shifts every later field into the wrong chip.
maxJobs advertises a maximum of 1,000; ~20 is achievable.
postedAt is relative prose ("2 days ago"), not a timestamp.
techStack is matched against title + company blurb + role, never a job description — empty on
~8 of 10 rows.
An unparseable salary is dropped entirely rather than kept as raw text.
The K/M multiplier is applied string-wide rather than per number.
The roles filter is bypassed for jobs with no detected role.
The jobId fallback embeds Date.now().
department is extracted into a local variable and never emitted.
All are itemized and prioritized in docs/IMPROVEMENT-PLAN.md, which flags one investigation to run
before any of the DOM work: check whether YC ships its listings as embedded JSON, since that
would replace the fragile selectors and solve pagination in a single change.
0.0.3 and earlier
No changelog was kept. Build 0.0.3 is the deployed build as of 2026-08-13; the last source commit
was 2025-11-16.