Hacker News Who is Hiring Scraper & HN Threads to Markdown
Pricing
from $4.00 / 1,000 job parseds
Hacker News Who is Hiring Scraper & HN Threads to Markdown
Turn the monthly HN "Who is hiring?" thread into job records: company, role, seniority, remote region, salary in yearly USD. Watch mode returns only new posts since the last run. Also exports Hacker News threads as Markdown for LLMs. Official APIs, no proxy.
Pricing
from $4.00 / 1,000 job parseds
Rating
0.0
(0)
Developer
Vellum Kasane
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Security
Limited permissionsShare
Turn the monthly "Ask HN: Who is hiring?" thread into structured job records (company, role, location, remote region, seniority, salary converted to yearly USD, visa, links) and export any Hacker News story with its full comment thread as clean Markdown. Everything comes from the official Firebase HN API and the Algolia HN Search API: no HTML scraping, no proxy, no blocking. Turn on watch mode (stateKey) and a daily schedule returns only the job posts added since the last run, and you pay only for those.
| Input | A mode (who-is-hiring, front, new, best, ask, show, jobs, search, story), optional keyword / date window, story IDs or URLs. Job filters: keywords, location, remote only, posted within N days, seniority, employment type, minimum yearly pay |
| Output | Jobs: company, role, location, remote / onsite / hybrid, remote region, seniority, employment types, years of experience, salary (raw, yearly, USD, one per role in multi-role posts), visa, YC batch, URLs, full post as Markdown |
Stories: title, URL, points, author, comment count, comments with depth / parent_id, and the whole thread as one Markdown document | |
| Price | $0.004 per job post, $0.003 per story, $0.01 per run. Seen, filtered and non-job comments are free |
| Speed | A full monthly hiring thread (about 250 posts) in about 2 seconds: one API request for the whole thread. 20 front-page stories with comments in about 4 seconds |
{"type": "job","id": 49925063,"hn_url": "https://news.ycombinator.com/item?id=49925063","thread_month": "2026-10","headline": "Discord | Senior Software Engineer, Application Security | REMOTE (US West Coast) or San Francisco, CA | $196K-$245K base + equity","company": "Discord","role": "Senior Software Engineer, Application Security","location": "REMOTE (US West Coast) or San Francisco, CA","remote": true,"remote_detail": "US West Coast","remote_region": "US West Coast","remote_timezone": null,"seniority": "senior","salary": {"min": 196000,"max": 245000,"currency": "USD","interval": "year","annual_usd_min": 196000,"annual_usd_max": 245000},"salary_text": "$196K-$245K","employment_type": null,"employment_types": [],"visa": null,"urls": ["https://discord.com/jobs/8656854002"],"markdown": "Discord | Senior Software Engineer, Application Security | ...\n\nDiscord is where communities and friend groups spend time together...","created_at": "2026-10-01T18:09:56.000Z"}
Jump to the task you have:
- Get every job from this month's HN "Who is hiring?" thread as a table
- Get a daily feed of new HN job posts (job alerts)
- Find senior, remote or well-paid HN jobs only
- Export Hacker News threads as Markdown for an LLM, RAG or notes
- Monitor what Hacker News says about a product or topic
- What this Actor cannot do
1. Get every job from HN "Who is hiring?" as a table
{ "mode": "who-is-hiring", "maxItems": 500 }
The Actor finds the newest "Ask HN: Who is hiring?" thread (posted by the whoishiring account on the first weekday of the month), reads the whole comment tree in one request and turns each top-level post into one record. Download the dataset as CSV or Excel and you have the month's HN job market in one sheet.
- Older months:
sinceDays: 365processes every monthly thread of the last year (maxItemscaps the total). A specific month: paste its thread URL intostoryIds. - What gets parsed: HN posts follow the loose convention
Company | Role | Location | REMOTE | Full-time | $salary | url. The parser also reads em-dash headlines (Acme (Remote US) — Senior Engineer — TypeScript / Postgres), finds the role wherever it sits ("Multiple roles", "Forward Deployed Engineers"), keeps tech-stack segments and flags such asONSITEintags, and never loses text:headlineandmarkdownare always there. - Not job posts are skipped, not billed: complaints about the thread, job seekers posting in the wrong thread ("Location: … Willing to relocate: …") and replies to a role. In the October 2026 thread that was 6 of 247 top-level comments.
2. Get a daily feed of new HN job posts
{ "mode": "who-is-hiring", "maxItems": 500, "stateKey": "hn-hiring-watch" }
Schedule this once a day (Apify Schedules, or call it from Make, Zapier, n8n or the API). The first run exports the current thread. Every later run returns only posts that no earlier run with the same stateKey exported. When a new month's thread appears, the run also re-checks the previous thread the watch was already reading, so late posts are not lost; a thread the watch never read is not back-filled (a new watch does not buy last month's posts).
What to expect: posting is front-loaded. In October 2026, 100 posts arrived on day 1 and 77 on day 2 (73% in two days), then 3 to 25 a day. So a daily schedule costs about $0.4 and $0.3 on the first two days of the month at the new price, and a few cents a day after that. Send the dataset to Slack, email or a Google Sheet with Apify Integrations.
3. Find senior, remote or well-paid HN jobs only
{"mode": "who-is-hiring","maxItems": 500,"seniority": ["senior", "staff", "principal", "lead"],"minAnnualSalary": 150000,"stateKey": "hn-hiring-senior-150k"}
Contract or part-time work only (freelancers, side gigs):
{"mode": "who-is-hiring","employmentTypes": ["contract", "part-time"],"remoteOnly": true}
Remote Rust or TypeScript jobs posted this week (all filters are AND-ed; within a list, any word matches):
{"mode": "who-is-hiring","keywords": ["rust", "typescript"],"remoteOnly": true,"locationContains": ["europe", "worldwide"],"postedWithinDays": 7}
-
keywordssearches the company, the role and the whole post (case-insensitive substring:rustalso matches "trust"). -
locationContainssearches the parsed location, the remote region and the first line of the post, because HN posts often list several cities there. -
remoteOnlykeeps posts that say REMOTE, includingRemote (US)andRemote / Hybrid. -
postedWithinDaysuses the time the post was added. In the first N days of a month it also reads last month's thread, so a weekly run never misses the posts from the end of the previous month. -
seniorityis one ofintern,junior,mid,senior,staff,principal,lead,manager,director,vp,executive, read from the role only (rank words such as Director / VP / Chief win over level words; "Associate Director" is a director).nonematches posts without a level word. -
employmentTypesis any offull-time,part-time,contract,temporary(fixed-term),internship, read from the headline ("| Full-time |", "| FT |", "| Full Time or Contract |"), else the role ("Data Engineer (contract)"), else an explicit sentence ("This is a part-time position"). A post offering several types (employment_types: ["full-time", "contract"]) matches when any is wanted. Addunknownto keep posts that state no type: in the October 2026 thread 180 of 259 posts state one (full-time 168, contract 14, part-time 4, internship 1; 6 posts offer two or three). -
minAnnualSalarycompares the top of the published range, converted to a year (hourly x 2080, monthly x 12) and, withsalaryCurrency: "USD", to US dollars at the day's ECB rate. A post that lists several roles matches when any of its roles reaches the floor (seesalaries). Posts without a published salary never match. -
remote_regionsays where a remote job can be worked from, read from the remote qualifier only (REMOTE (US) or NYC->United States,Remote, USA only->United States,EU->Europe,Berlin / EU-remote->Europe,(UNITED STATES ONLY)->United States, a bareRemote->Worldwide). Since 0.13.9 it holds place names only, in one spelling (US/Can->United States, Canada,NYC->New York,Chicago, IL->Chicago,UT or ID residents only->United States); a qualifier that names no place (Remote (see below),all remote,Remote (async)) counts as a bareRemote. Offices named next to it are inlocation, not here. Filter it in your sheet. -
remote_timezone(0.13.8) is the working hours a remote post asks for, as written (REMOTE (US Timezones)->US Timezones,Remote (CET ±2h)->CET ±2h,Remote (UTC-1 to UTC+3 only)->UTC-1 to UTC+3), ornull. Hours never leak intoremote_regionany more; hours that name a region give that region (US time zones->United States,AMER or EMEA time zones->Americas, EMEA), zone names alone (CET,PT/ET, UTC offsets) leaveWorldwide, so filter on both columns if you need, say, European hours. -
These filters have the same names and meaning as in the ATS Jobs Feed, so one filter set works on both.
-
Filtered-out posts are not stored, billed or remembered, so loosening the filter later picks them up. Use one
stateKeyper filter set.
The same fields with the same meaning come from Vellum Labs' ATS Jobs Feed (Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio). Run both into one sheet to see HN posts next to the companies' own career pages; ATS Job Board Finder turns the company names from HN into board references for that feed.
4. Export Hacker News threads as Markdown for an LLM, RAG or notes
{"mode": "story","storyIds": ["https://news.ycombinator.com/item?id=49797323"],"maxCommentDepth": 5,"maxCommentsPerStory": 500}
Each story record has the story text and comments converted from HN's markup to Markdown, comments as a flat list with depth and parent_id, and markdown: the whole thread as one document with replies nested as blockquotes, ready to paste into Claude or ChatGPT, embed for RAG, or drop into Obsidian or Notion. Comment trees come from Algolia's items/<id> endpoint, which returns the full tree in one request, so a front page with comments is about 40 API requests, not thousands.
Other lists: front, new, best, ask (Ask HN), show (Show HN), jobs (YC job ads). maxCommentDepth: 0 keeps top-level comments only; includeComments: false exports the stories alone. AI agents can call the Actor through the Apify MCP server and read the dataset.
5. Monitor what Hacker News says about a product or topic
{ "mode": "search", "query": "postgres", "searchSort": "date", "sinceDays": 7, "stateKey": "hn-watch-postgres" }
search uses Algolia's syntax ("quoted phrase", -excluded words). With searchSort: date and a stateKey, a daily schedule returns only stories it has not exported before and stops paging as soon as it reaches already-seen stories, so you pay for new mentions only and need no deduplication downstream. Watch several topics with one stateKey each.
What this Actor cannot do
Read this before your first run. Coverage numbers are from the October 2026 thread (241 job posts after skipping 6 non-job comments).
| Limit | What happens | What to do |
|---|---|---|
| Free-form posts | Job fields are heuristics over free text. Found: company 241, location 226, role 178 (74%), remote region 127, seniority 82 (34%), parsed salary 88 of 259 (34%, 0.6). A post that is one paragraph gets company only. company is null rather than a guess when the headline starts with the role ("Senior Python Backend Engineer | REMOTE") and the opening of the post names no company ("join Reef Technologies", "Kaveronix is looking for…" are read; 0.12: 26 of 723 posts in Aug–Oct 2026 fixed, 13 of them now null instead of a role or a sentence) |
| Several roles in one post | One record per post, not per role. role is the role segment as written ("Multiple roles"); salaries lists every pay figure with the words before it (context, usually the role: "Staff Software Engineer - Infrastructure"). context is a hint, not a parsed title, and can carry location words. When the pay line names no role, the nearest role heading above it is used ("Engineering Manager, Data Platform -- ..." then "COMPENSATION: $180,000--$250,000"); it stays null when two role headings share one figure. The same role's pay in another currency on the same line ("45-70 USD or 180-280 PLN per hour", "$185K–$220K USD / $155K–$185K CAD") keeps the first figure's context and has alternative_to (index of that figure), so it is not counted as another role 23 of 259 October posts had 2-6 figures (129 figures in all) | Unwind salaries in your sheet for one row per role |
Remote region: what still reads Worldwide | remote_region is one value per post. Since 0.13.7 it also reads regions glued to the word ("EU-remote", "(US/Remote)", "Flexible/U.S. Remote"), regions per role ("Lead (UK/EU) & SDR (Canada) | Remote" -> United Kingdom, Europe, Canada) and the first region of a long note ("REMOTE (in USA, also in Europe but only for …)" -> United States). Still Worldwide: a bare Remote, an explicit worldwide / global / anywhere, hours that name no region ("REMOTE (CET)", "Remote PT/ET Hours", "UTC-1 to UTC+3": see remote_timezone), a link to a country list ("REMOTE in select countries (listed here …)") and sentences in the headline ("all roles fully remote, but some US-only as noted"). Since 0.13.9 also Worldwide: a remote qualifier that names no known place ("REMOTE (multiple countries)", "Remote (see below)"), and places outside the built-in list of about 60 countries / regions and 90 cities are dropped ("SEA" in "NYC / SEA" is ambiguous and dropped). Check headline before relying on Worldwide. | |
| Salary not published or ambiguous | salary is null (about 66% of posts). Ranges without a currency (145k-165k), signing bonuses and bare amounts that could be hourly or thousands ($90–$110 without "/hr") stay in salary_text only, and only when no real figure is parsed from the post. Company figures (raised $42M, $3.5B in GMV, over £50 billion) and small fees ($10) are dropped, so salary_text is null rather than misleading | Leave minAnnualSalary at 0 to keep them |
| "Up to $X", "$X+", one figure | Read as written: salary.bound is up_to (min null), from (max null), exact (min = max) or range. minAnnualSalary compares max, or min when there is no max | Filter on salary.bound to keep only ranges |
| Seniority only in the body | A role "Software Engineer" whose text says "senior" stays null | Filter markdown instead |
| Edited posts | Watch mode tracks post IDs, not edits: a post edited after it was exported is not exported again | Run without stateKey for a full refresh |
| Non-job comments | Skipped when they have no segmented headline and no way to apply (no link, e-mail, "apply", "hiring", "careers"). A bare one-line title with no contact is skipped too (1 in September 2026) | — |
| Deleted / flagged posts | Not returned (about 15% of a thread's top-level IDs are deleted or dead). Algolia can lag the live site by a few minutes | — |
| List sizes | Firebase lists return at most 500 (front, new, best) or 200 (ask, show, jobs) IDs | Use search with sinceDays (Algolia pages up to 1,000 hits per query) |
| Big threads | maxCommentsPerStory is depth-first, so a deep cap with a small limit keeps the first few branches only | Raise maxCommentsPerStory, or maxCommentDepth: 0 for top-level only |
| Rate limits | Algolia allows 10,000 requests per hour per IP. Concurrency is capped (default 5, max 10), with back-off on 429 / 5xx and Retry-After honoured | — |
Watch mode in front / best | Returns stories new to you, not newly submitted ones | Use new or search sorted by date |
Shared stateKey | Two schedules with one key "use up" items for each other. The store keeps the newest 50,000 IDs | One key per schedule / filter set; delete the store to reset |
| USD conversion unavailable | When the ECB rate service (frankfurter.dev, one request per run) is unreachable, annual_usd_* are null and a USD minAnnualSalary only matches USD posts | — |
| Budget cap | With Max total charge per run or maxItems, the run stops cleanly before exceeding it. In watch mode, posts not exported come in the next run | Raise the cap |
| Run aborted or migrated | Since 0.4 the watch state is saved on abort and migration too, so items already exported are not exported (or charged) again by the next run | — |
| Link posts and HN's own pages | text_markdown is empty for link posts; the linked article is not fetched. news.ycombinator.com HTML is never scraped | Fetch the article URLs with a page-to-Markdown Actor |
Input
| Field | Type | Default | Notes |
|---|---|---|---|
mode | string | front | front, new, best, ask, show, jobs, search, who-is-hiring, story. |
query | string | "" | Search terms for search mode (Algolia syntax). |
searchSort | string | date | date (newest first) or relevance. |
storyIds | string[] | [] | HN item IDs or item URLs for story and who-is-hiring modes. |
maxItems | integer | 100 | Cap on stories (or job posts in who-is-hiring). Caps cost. |
sinceDays | integer | 0 | Only items newer than N days (0 = no filter). In who-is-hiring mode, all hiring threads in the window. |
includeComments | boolean | true | Fetch comment trees and render thread Markdown. |
maxCommentDepth | integer | 3 | 0 = top-level comments only. |
maxCommentsPerStory | integer | 200 | Depth-first cap per story. |
maxConcurrency | integer | 5 | Parallel API requests (max 10). |
stateKey | string | "" | Watch mode. Name of a key-value store that remembers exported IDs; runs return only unseen items. |
keywords | string[] | [] | who-is-hiring only. Keep posts whose company, role or text contains one of these words. |
locationContains | string[] | [] | who-is-hiring only. Keep posts whose location, remote region or first line contains one of these. |
remoteOnly | boolean | false | who-is-hiring only. Keep posts that say REMOTE. |
postedWithinDays | integer | 0 | who-is-hiring only. Keep posts added in the last N days (also reads last month's thread when needed). |
seniority | string[] | [] | who-is-hiring only. Keep posts whose role maps to these levels (intern ... executive, none). |
employmentTypes | string[] | [] | who-is-hiring only. Keep posts offering one of these types (full-time ... internship, unknown). |
minAnnualSalary | integer | 0 | who-is-hiring only. Keep posts whose disclosed pay reaches this yearly amount; undisclosed pay is dropped. |
salaryCurrency | string | USD | Currency of minAnnualSalary. USD converts other currencies with the day's ECB rate. |
Output fields
One dataset item per job post or story, downloadable as JSON, CSV, Excel or HTML.
- Job records (
type: "job"): besides the example above,story_id,story_title,author,onsite,hybrid,remote_region,years_experience_min(first "N+ years of experience" in the body),employment_types(every type the post states;employment_typekeeps the first headline type for 0.9 integrations),yc_batch(W24...),tags(unclassified headline segments: work-mode flags, tech stack, "Visa sponsorship available"),fetched_at.salaryalso carriesbound(range/exact/up_to/from),summary,source,annual_min,annual_max.salariesis every pay figure in the post in order (same shape assalary, pluscontext, the words before the amount on its line);salarystays the first figure from the headline, so 0.6 integrations keep working.visaissponsored,not-sponsoredornull.urlslists the post's links, then links typed without a scheme ("acme.com/careers", returned ashttps://acme.com/careers). - Story records (
type: "story"):id,hn_url,title,url,domain,story_type(story,ask,show,job,poll),points,author,created_at,comment_count,text_markdown,comments[](id,parent_id,author,created_at,depth,text_markdown),comments_stored,markdown,fetched_at.
SUMMARY.json in the run's key-value store holds the counts: items stored, skipped (duplicates and non-job comments), skippedByFilter, skippedSeenInPreviousRuns, API requests and retries, charged events, duration.
Pricing
Pay-per-event. Platform usage (compute, storage) is included.
| Event | Price | When |
|---|---|---|
job-parsed | $0.004 per post | Once per Who-is-Hiring post written to the dataset. Seen, filtered-out and non-job comments are free |
story-exported | $0.003 per story | Once per story written to the dataset, comments included. Deleted, dead and seen stories are never charged |
| Actor start | $0.01 per run | Apify's standard start event |
Examples: a full monthly hiring thread (about 250 posts) costs about $1.01; a daily watch schedule about $1 a month in total plus $0.30 for the 30 run starts; the front page with comments (20 stories) $0.07. Set Max total charge per run to hard-cap spend; the Actor stops before exceeding it.
Data sources
- Hacker News API (Firebase): lists and items. "There is currently no rate limit."
- Algolia HN Search API: search, date search and whole comment trees in one request. 10,000 requests per hour per IP.
- frankfurter.dev: ECB reference rates for
annual_usd_*, one request perwho-is-hiringrun.
Only these public APIs are used; news.ycombinator.com pages are never scraped (its robots.txt asks for a 30-second crawl delay).
Verified
- 0.13.11 (2026-11-06): August to November 2026 threads (837 job posts): 12 fields changed in 11 posts, all checked by hand.
company6: three posts "Backed by Sequoia Capital, Peregrine is the operational AI platform …" ->Peregrine, "Company: esumry.com Location: West Austin TX …" ->esumry.com, "whitecircle.com/careers" ->whitecircle.com,null->Sumble("Sumble is the newco …").salary_text6 ->null, all company figures, not pay ("$70m" raised, "$42M" raised, "$30" trillion AUM, "$100"B cash flow, "$3.5"B GMV). Unit tests 53. - 0.13.9 (2026-11-04): August to November 2026 threads (837 job posts):
remote_regionchanged in 132, all checked by hand; no other field changed. Distinct place values 160+ -> 64 (non-places such as "VoiceAI", "YC 19", "Customer Success Engineer; structure", "Pre, seed", "and", "all" are gone; "US Canada" / "US & Canada" / "US/Can" all readUnited States, Canada).Worldwide86 -> 119 (33 posts whose qualifier named no place). Unit tests 47,eslintclean. - 0.13.8 (2026-11-03): August to November 2026 threads (837 job posts): 56 posts got
remote_timezone, all checked by hand;remote_regionchanged in 50 ("US Timezones" ->United States, "EU time zone" ->Europe, "North America, Europe, UTC, 8 to UTC+2" ->North America, Europe, "United States, Eastern Central Time Zones" ->United States, "CET ±2h" / "PT, ET Hours" / "UTC, 1 to UTC+3" ->Worldwide). No other field changed.Worldwide69 -> 86. Unit tests 45,eslintclean. - 0.13.7 (2026-11-02): August to October 2026 threads (718 job posts): 7 posts changed, all checked by hand, only
remote_region:Worldwide-> the region named in the headline (Fliplet "(UK/EU) & … (Canada)", Langfuse "EU-remote", FusionAuth ×2 "REMOTE (in USA, …", SentiLink "(UNITED STATES ONLY)", OneChronos "Flexible/U.S. Remote", 3Gimbals "(US/Remote)"). A time-zone preference ("Remote / EU/NA timezones") is not read as a region.Worldwide70 -> 63. Unit tests 41,eslintclean. - 0.13.6 (2026-10-31): August to October 2026 threads (718 job posts): 80 posts changed, all checked by hand.
remote_regionchanged in 78: 23 posts that saidWorldwidenow name the region from the remote qualifier or a remote segment ("REMOTE (US) or NYC", "Remote, USA only", "REMOTE (US/Canada/Brazil/Poland/UK/India)"), and offices or stray segments mixed into the region are gone ("United States, Santa Clara Brooklyn" -> "United States", "PMF; United States" -> "United States").locationchanged in 3 ("Denver, CO" -> "Denver, CO, USA"; a "Technical Trainer" role had been read as the location).Worldwide93 -> 70. Unit tests 41,eslintclean. - 0.13.5 (2026-10-30): August to October 2026 threads (718 job posts): 44 posts had a body-only
salary_textwith no parsed figure; 42 of them were company figures or fees and are nownull(all checked by hand against the post text), the other 2 are real pay ("160k-240k", "$1.5K USD/mo") and are unchanged. No other field changed. Unit tests 41,eslintclean. - 0.13.0 (2026-10-26): August to October 2026 threads (723 job posts, 295 with
salary_text): 19 posts changed, all checked by hand. Removed fromsalary_text: funding rounds (Thunder Compute $17.5M, Solace $207M, Tasklet $42M, XP Health $50M), "$1" / "$10" fees, a split range (OneChronos "$180,000 / $250,000 / $150,000" -> "$180,000--$250,000 / $150,000--$200,000"); Reef's PLN figure now shown. Headline hints unchanged. Unit tests 39,eslintclean. - 0.11.0 (2026-10-24): October and September 2026 threads (515 top-level comments, 267 figures): 4 posts state one role's pay in two or three currencies (Reef Technologies twice, Mozilla, vCluster Labs); their 5 extra figures now carry
alternative_toand the first figure'scontext(Reef's PLN figure hadcontext: "or"). No other output changed. Unit tests 38,eslintclean. - 0.10.0 (2026-10-23): October 2026 thread (259 job posts):
employment_typesset on 180, two or three types on 6 (all checked by hand: "Full time or part time or contract", "Full-Time, Internship", "Full-time / Contract" ...), 0 posts where it disagrees withemployment_type. Unit tests 37,eslintclean. - 0.9.0 (2026-10-21):
contextnames the role from a heading line above the pay line when the pay line has none (OneChronos-style posts: role paragraph, then "ESTIMATED COMPENSATION: ...");nullwhen two headings share one figure. A part-period figure restated as yearly ("$65k ($130k annualized)" for a 6-month contract) is read as the yearly $130k; "annualized revenue" is not pay. Measured on the October and September 2026 threads: 3 posts changed, no other output changed. - 0.8.0 (2026-10-20): October 2026 thread:
salaries129 -> 141 figures, posts with a salary 87 -> 89 (DuckDuckGo 7 roles written "Role - $178,500 USD + equity", Middesk 5 ranges that had been dropped because "(2x/week in office)" was read as a weekly rate). September thread: 112 -> 125 figures, and Attendi's€6,000 - €7,000is now monthly (was weekly). Every changed figure checked by hand, 0 new false positives; no figure that 0.7 parsed was lost. Unit tests 35,eslintclean. - 0.7.0 (2026-10-19): October 2026 thread (259 job posts): 23 posts list 2-6 pay figures, 129 figures in
salaries, every multi-figure post checked by hand (Baton 6 roles, Solace 5, Rebar 5, Vistulo 6 hourly PLN). RSU / PSU amounts ("up to $76,026.50 $HOST RSUs") are skipped.minAnnualSalary: 200000keeps 47 posts (44 when only the first figure counted).salarychanged on 1 post ($180,000--$250,000, was null); September thread: 1 change ($90–$110/hour, was$110/hour). Unit tests 34,eslintclean. - 0.6.0 (2026-10-18): October 2026 thread, 259 job posts: parsed salary 77 -> 88, 11 new all checked by hand (5 exact, 2 up_to, 4 hourly ranges), 0 false positives; funding (
$42M), signing bonus (€10k) and fixed-fee ($12K) amounts stay null. Unit tests 32,eslintclean. - 0.5.0 (2026-10-17): October 2026 thread (266 top-level comments) with
keywords: [typescript, rust],remoteOnly: true,postedWithinDays: 10: 9 posts stored, 250 filtered out (not billed), 3 API requests. Unit tests 31,eslintclean. - 0.4.0 (2026-10-09): parser measured on the September and October 2026 threads. October: 247 top-level comments, 6 non-job comments skipped, role found in 178 of 241 posts (was 165 of 247), wrong locations (tech stack, body text, "100–140k€") removed. September: role 185 of 248 (was 176 of 252). Unit tests 30,
eslintclean. - 0.3.0 (2026-10-01):
who-is-hiringwithmaxItems: 500: 3 API requests, 254 posts, 1.7 s. Withseniority: [senior, staff, lead, principal]andminAnnualSalary: 100000: 26 posts stored, 228 filtered out. - 0.1.0 (2026-09-22):
frontprefill (20 stories, comments depth 2, 50 per story): 41 API requests, 0 failed, 4.3 s. Pay-per-event path tested with a run budget: stops gracefully before the item that would exceed it.
Changelog
- 0.13.11 (2026-11-06):
companyis a name, not a label line, a careers URL or a sentence: "Company: X Location: Y" -> X, "acme.com/careers" -> "acme.com" (a bare domain such as "Fly.io" stays: it is the name), a long headline "Backed by …, Peregrine is the …" -> "Peregrine". Asalary_texttaken from a one-paragraph headline with no parsed pay now keeps only pieces that could be pay, as body text has since 0.13.5 ("raised roughly $70m" ->null). - 0.13.10 (2026-11-05):
urlsalso holds links an author typed withouthttps://("vvd.world/careers", "www.psi.ch"), which HN does not turn into links, ashttps://URLs after the real links. Only a domain with a path or awww.host counts; "Node.js/Express", a bare "example.com" in prose and e-mail domains do not. On the Aug–Nov 2026 threads (837 posts) 24 posts gained a link and 14 that had none now have one; no other field changed. - 0.13.9 (2026-11-04):
remote_regionis rebuilt from known place names only (countries, regions, US coasts, provinces and about 90 cities, one spelling each:US/USA/U.S.->United States,NYC->New York,SF->San Francisco,NL->Netherlands). Words that are not places are dropped; two-letter US state codes stand forUnited States(never for themselves: "CA" can be California or Canada); a qualifier with no place at all falls back to the headline's regions, elseWorldwide, like a bareRemote. Places are joined with,(was sometimes&orand). - 0.13.8 (2026-11-03): new
remote_timezone(working hours as written). Time-zone clauses ("US Timezones", "Pacific Time hours", "CET/CEST overlap", "UTC-8 to UTC+2", "2h overlap with US Pacific") are split out of the remote qualifier, location and tags beforeremote_regionis read; regions they name count ("Europe / US Eastern Time zone" ->Europe, United States), zone names and offsets do not. A qualifier that is only hours is the whole answer, so other segments ("+ Quarterly Meet-ups", the office "Berlin") no longer become the region. "Remote / EU/NA timezones" now readsEurope, North America(wasWorldwidein 0.13.7) to match "US time zones" ->United States. - 0.13.7 (2026-11-02): a remote post that would read
Worldwidetakes the regions its headline names next to "remote" ("EU-remote", "US/Remote", "Remote/US", "REMOTE (in USA …") or alone in parentheses ("(UK/EU)", "(Canada)", "(UNITED STATES ONLY)"). An explicit worldwide / global / anywhere keepsWorldwide; a match followed by "timezones" / "hours" is a preference and is skipped. - 0.13.6 (2026-10-31):
remote_regionis read from the remote qualifier ("REMOTE (US)", "Remote (EU time zone)") when there is one, so offices in other segments and stray segments no longer mix in; without a qualifier, a short remote segment kept intags("Remote, USA only") is read before falling back toWorldwide(a preference such as "PT/ET hours preferred" is no restriction and staysWorldwide). A place after "in" keeps up to two ", Region" parts ("Denver, CO, USA"). "Trainer" and "Instructor" are role words. - 0.13.5 (2026-10-30): a body-only
salary_textwith no parsed pay figure keeps only pieces that could be pay (thousands, not followed by M / B / T or "million", not after raised / funding / valuation / revenue / GMV / market / over). Funding rounds ("$42M / $60M"), company scale ("$3.5B in annual GMV" was shown as "$3.5", "£50 billion" as "£50") and fees ("$5") now givenull; "160k-240k" and "$1.5K USD/mo" stay. - 0.13.4 (2026-10-29): when no headline segment is a location, the place named in a headline phrase is used: "… at NLnet foundation in Amsterdam" ->
location"Amsterdam", "On-site (hybrid) in Stockholm, Sweden (Visa …)" -> "Stockholm, Sweden". A remote post that also names an office reads "Remote / Bremen, Germany" (remote_regionis still taken from the remote part only). Only after an on-site / hybrid / office / based / located word or "at - 0.13.3 (2026-10-28): a company name ending in a lowercase organisation word ("Rust developer at NLnet foundation in Amsterdam":
company"NLnet foundation",role"Rust developer") is read instead of the whole headline. Words: foundation, lab(s), inc, ltd, llc, gmbh, group, institute, collective, studio(s), ventures, technologies. August to October 2026 threads: 1 of 719 posts changed, nothing else. - 0.13.2 (2026-10-27): an amount followed by "+ 20% bonus", "+ 10% commission" or "+ benefits" is read as that fixed amount (
bound: "exact"), not as "or more" (from). "$250k+" is stillfrom. August to October 2026 threads: 1 of 719 posts changed ("Salary mid point: $165k + 20% bonus target"). - 0.13.0 (2026-10-26):
salary_textfor posts whose pay is only in the body is rebuilt from the parsedsalaries(up to 3 figures), so funding rounds ("raised $17.5M"), fees ("$10") and split ranges ("$180,000--$250,000" shown as "$180,000 / $250,000") no longer appear in it. A pay hint in the headline is kept as written. - 0.12.0 (2026-10-25):
companyis no longer a role name when the headline starts with the role ("Senior Python Backend Engineer | REMOTE", "Role: …"); the name comes from "join X" / "X is hiring" in the body or the headline sentence, elsenull. - 0.11.0 (2026-10-24):
salaries[].alternative_to: a figure that restates the previous one in another currency, joined only by "or", "/", "|" or "," on the same line, points at it and takes itscontext(Reef's "180-280 PLN" hadcontext: "or", Mozilla's and vCluster's CAD / EUR bands had none). Filters are unchanged:minAnnualSalarywithsalaryCurrency: "PLN"already matched the PLN figure. - 0.10.0 (2026-10-23):
employment_types(every employment type the post states, same values and rules as the ATS Jobs Feed, includingtemporary) and the filteremploymentTypes(same name as the feed;unknownkeeps posts without a type; filtered posts are not billed). - 0.9.0 (2026-10-21):
contextnames the role from a heading line above the pay line; "($130k annualized)" is read as the yearly figure. - 0.8.0 (2026-10-20): more salaries parsed: a lone amount after a spaced dash or a colon ("Senior Backend Engineer - $178,500 USD + equity", "Salary: $150,000"), yearly-sized or with an explicit rate, never after "bonus", "raised", "fee" and similar words. A pay interval is only read from the words that belong to the amount, so "($148K – $255K) — hybrid (2x/week in office)" is a yearly range (was dropped) and "€6,000 - €7,000 | 3 days/week" is monthly (was weekly).
contextdrops an intro sentence on the same line as the first role. - 0.7.0 (2026-10-19):
salaries: one entry per pay figure, withcontext(the role written before it), for posts that list several roles.minAnnualSalarymatches when any role reaches it. Also parsed: ranges written$180,000--$250,000, lone amounts in parentheses ("Junior SWE ($300k + equity)"); equity, RSU, PSU and bonus figures are never read as pay. - 0.6.0 (2026-10-18): salary bounds are explicit:
salary.boundisrange,exact,up_toorfrom. Newly parsed: "$180k+" (from), "up to ~ $750k", a lone amount in a headline segment ("| £120k |", "| $181K base + 15% bonus |"), and rates written "/hr" ("$23-$34 USD/hour", "$50/HR"). The README wrongly said "up to $X" was read as min = max; it has always beenmin: null. - 0.5.0 (2026-10-17): the ATS Jobs Feed's filters for
who-is-hiring:keywords,locationContains,remoteOnly,postedWithinDays(same names and meaning, filtered posts not billed). WithpostedWithinDays, the previous month's thread is also read while the window reaches into it. - 0.4.0 (2026-10-09): non-job top-level comments (complaints, job seekers, replies) are skipped and not billed; em-dash headlines are split; plural and multi-role segments ("Software Engineers", "Multiple roles") are read as the role wherever they sit; tech-stack segments go to
tagsinstead oflocation; body text glued to the headline is no longer taken as role or location;FT/PTand "100–140k€" are recognised. Watch mode fix: the second run of a new watch no longer exports the whole previous month's thread (it re-checks only threads the watch already read). Watch state is saved when a run is aborted or migrated. - 0.3.0 (2026-10-01): comparable job fields shared with the ATS Jobs Feed:
seniority,years_experience_min,remote_region, structuredsalarywith annualised and USD bounds; the raw hint moved tosalary_text. Filtersseniority,minAnnualSalary,salaryCurrency. - 0.2.0 (2026-09-24): watch mode (
stateKey);who-is-hiringre-checks the previous month's thread; date-sorted search stops at the first fully-seen page. - 0.1.0 (2026-09-22): initial release. Nine modes, Markdown threads, Who-is-Hiring parser.
Support
Open an issue on the Actor's Issues tab. Issues are answered within one business day; fixes ship as new builds without changing the input schema.
Made by Vellum Labs.