Anyone who has written a remote jobs scraper by hand knows the shape of the work. RemoteOK serves JSON from remoteok.com/api. We Work Remotely publishes an all-jobs RSS feed plus 13 category feeds, and no feed paginates. The Hacker News "Who is hiring?" thread is community-formatted posts (Company | Role | Location | Salary) that you parse yourself. Ten sources, ten shapes, and the same posting often appears on more than one of them.
This post walks through one way to skip that: remote-job-aggregator by flash_scraper, an Apify actor that sweeps 10 keyless remote job boards in one run (RemoteOK, We Work Remotely, Working Nomads, DevITjobs US + UK, The Muse, Remotive, Jobicy, Himalayas and the Hacker News "Who is hiring?" thread) and returns one deduplicated feed with no API key, no proxy and no login. One opt-in board, Arbeitnow, joins only when you add it to boards. Below: the columns, a real run receipt, the Python call, the price, and the limits the README lists about itself.
What you get
One row per remote job, the same columns on every row. The actor is a remote jobs aggregator rather than a single-board scraper: rows are merged across boards by canonical job URL and by (title, company), so the same role listed on two boards ships once. Eight of the columns, with titles and descriptions from the actor's dataset schema:
| Column | Schema title | What it holds |
|---|---|---|
location |
Location | Exactly what the board provides, often a country restriction like "USA Only" or "Anywhere in the World". |
salary_text |
Salary | Derived, always present: one readable string built from salary_min / salary_max / salary_currency / salary_interval, e.g. $90,000–$175,000 / year; "" when the board published no figure. |
posted_at |
Posted | Posting date, YYYY-MM-DD, when the board provides one. |
source_board |
Source | Which board this (first) copy of the job came from. |
url |
Link | Direct link to the posting (for Remote OK rows this is the job's Remote OK page, as their API terms require). |
seniority |
Seniority | Derived: entry, junior, mid, senior, lead, staff, principal, manager, director or unknown. |
also_on_boards |
Also on boards | The other board ids this same posting was found on and merged away (["remotive"]; [] when none). |
job_id |
Job ID | 16-hex fingerprint of the job's canonical URL; stable across runs and the same identity onlyNewJobs bills on, so use it as the upsert key. |
Hourly, daily, weekly, fortnightly and monthly salary figures are annualized (×2080 / ×260 / ×52 / ×26 / ×12); currency is left null when the board doesn't state it.
A real run
The receipt from a throwaway run on 2026-09-20 that ended up on the default input: all 10 boards, no keywords. Leaving searchTerms empty is valid and returns up to maxItems (default 100) of the current remote jobs across every board.
input: the schema defaults (all 10 boards, no keywords; the keys sent were not in the input schema and were ignored)
run: SUCCEEDED 6.648s
STATUS MESSAGE: Done — 100 remote job(s) delivered for all remote jobs (no keywords) from 10/10 boards (remoteok 10/99 · weworkremotely 10/100 · working_nomads 10/53 · devitjobs_us 10/72 · devitjobs_uk 10/45 · muse 10/100 · remotive 10/20 · jobicy 10/100 · himalayas 10/100 · hn_hiring 10/100); 11 cross-board duplicate(s) merged.
How to read remoteok 10/99: rows delivered from that board over the remote rows it offered before the keyword gate. Results are interleaved round-robin across boards, which is why each board contributed 10 rows to a 100-row order. The 11 cross-board duplicates were merged before the cap and before billing.
The Python call
The README's "Smallest useful call", with apify-client. The same JSON works in the Console, the REST API and n8n / Make / Zapier:
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("flash_scraper/remote-job-aggregator").call(run_input={"searchTerms": ["python"], "maxItems": 20, "boards": ["remoteok", "jobicy", "remotive", "working_nomads"]})
rows = client.dataset(run["defaultDatasetId"]).list_items().items
Two details before you change the input. boards here is restricted to the four single-request boards: the README measured 20 rows in 2 s on a local run on 2026-08-29, against 84 s across all ten boards that day, because Himalayas and The Muse are paginated feeds. And an API input that omits matchDescriptions matches titles only; send "matchDescriptions": true to also match tags and the first 500 characters of the description. searchTerms is capped at 10 terms per run; maxItems accepts 1–4000 and defaults to 100.
To turn the call into a pipeline, set onlyNewJobs: true and schedule it. Every later run delivers, and bills, only postings this exact search has never delivered before; a run where nothing is new finishes SUCCEEDED with zero rows and zero cost. Memory lives in a named key-value store in your own Apify account (remote-job-monitor), pruned after 90 days. A ready-made example task: Daily new remote data analyst jobs alert (only new postings).
What it costs
$0.003 per deduplicated job, $3 per 1,000, with no per-run start fee. That is the actor's live pricing record read on 2026-09-14; the Pricing tab on the Store page is always authoritative. So 500 jobs = $1.50, and $5 buys 1,666 jobs (1,666 × $0.003 = $4.998). Apify's free plan needs no card, and its monthly usage credit ($5 at the time of writing) covers about 1,666 deduplicated jobs from this actor if spent on it alone. Paid plans pay less per row: $0.0027 on Bronze down to $0.0015 on Diamond.
Duplicates merge before billing, filtered rows are never charged, and a nothing-new monitoring run or zero-match run charges nothing at all. The four-board call above is at most $0.06 (20 rows × $0.003).
Honest limits
Taken from the README's own limits section and FAQ:
- No LinkedIn, Indeed or Glassdoor. Those aren't remote-only boards, and this actor does not cover them.
- Feed-limited depth. These are public feeds, not archives: RemoteOK serves its ~100 newest rows, WeWorkRemotely ~100 per feed, Working Nomads ~50 curated rows, Jobicy 200 per call. The Muse and Himalayas paginate deeply (~1,980 and ~2,000 rows, measured 2026-08-19). A run cannot paginate years back; the ceiling is 4,000 deduplicated jobs per run.
-
Keyword matching is client-side. In title-only mode a Python job titled "Backend Engineer" won't match
"python". - Salary coverage varies by board: 35% of rows on the measured 2026-08-15 default run. DevITjobs ~100% of its rows, RemoteOK ~3%, The Muse and Working Nomads none.
- DevITjobs contributes remote rows only (~8% of its inventory). Boards change: a board that is down or blocked on a given run is named in that run's status message, never silently, its rows are never billed, and its failure never poisons the other nine.
- HN rows are parsed from community-formatted posts. Expect the occasional imperfect company/title split; only posts that explicitly say "remote" are included.
- Arbeitnow is opt-in for a reason. A German city board; only rows it flags remote are delivered (34 remote among its first 450 rows, measured 2026-08-25) and it publishes no salary field. Jobspresso is not offered (HTTP 403 to Apify IPs, measured 2026-08-27).
- Schedule at most hourly; daily is what the boards justify. Jobicy asks for at most one automated poll an hour and Remotive advises at most 4 requests a day.
-
The boards' data terms pass through to you. Remotive and Himalayas ask that listings not be resubmitted to third-party job sites or aggregators; Jobicy asks you to keep it as the source; Remote OK asks for a link back. Every row links to the board's own posting in
url.
Where to run it
Paste { "searchTerms": ["python"], "maxItems": 100 } or the four-board call above, and you have a deduplicated remote jobs feed without writing ten parsers.
Top comments (0)