Remote Jobs API — RemoteOK, WWR, Himalayas & 4 More avatar

Remote Jobs API — RemoteOK, WWR, Himalayas & 4 More

Pricing

from $0.30 / 1,000 job returneds

Go to Apify Store
Remote Jobs API — RemoteOK, WWR, Himalayas & 4 More

Remote Jobs API — RemoteOK, WWR, Himalayas & 4 More

Seven remote job boards in one normalized feed: RemoteOK, Remotive, We Work Remotely, Himalayas, Jobicy, Working Nomads and Arbeitnow. Deduplicated across boards with an alsoOn list, full descriptions at no surcharge, filters applied before the charge, monitor mode for new jobs only. No API key.

Pricing

from $0.30 / 1,000 job returneds

Rating

0.0

(0)

Developer

Insight Solutions

Insight Solutions

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Seven public remote job boards in one schema, deduplicated across them: RemoteOK, Remotive, We Work Remotely, Himalayas, Jobicy, Working Nomads and Arbeitnow. Same columns, same run, same query.

Read straight from each board's own public feed. No API key, no login, no browser, no aggregator in the middle.

You are charged for the jobs you get, not the jobs we read. Every filter — keyword, location, salary, tag, timezone, date — is applied before the charge event. A run that reads 500 postings and returns 20 bills you for 20.

At a glance

Input — this is the Store prefill; paste it and run:

{
"boards": ["remoteok", "remotive", "himalayas"],
"keywords": ["developer", "engineer"],
"maxJobs": 25,
"remoteOnly": true,
"monitor": false
}

Output — one row per unique job posting; the fields you will use most are title, company, applyUrl, board and postedAt (full list under Output reference). Anything that could not be fetched comes back as a free diagnostic row (ok: false, errorType, error) instead of a charge.

Price$0.50 per 1,000 jobs on the FREE tier (+ $0.001 per run); filtered-out jobs, cross-board duplicates, full descriptions, summary rows, diagnostics and a monitor's baseline run are all free. Pay-per-event, no API key, no browser, limited permissions — works over the Apify MCP server (mcp.apify.com) and with agentic (x402) payments.

From codeclient.actor("insight.solutions/remote-jobs-api").call(run_input={…}) with apify-client, or

POST https://api.apify.com/v2/acts/insight.solutions~remote-jobs-api/run-sync-get-dataset-items
.


What you get

One row per unique posting. Same columns whichever board it came from:

{
"rowType": "job",
"jobId": "himalayas:biocatch/sales-account-manager-iberia",
"board": "himalayas",
"boardJobId": "biocatch/sales-account-manager-iberia",
"url": "https://himalayas.app/companies/biocatch/jobs/sales-account-manager-iberia",
"applyUrl": "https://himalayas.app/companies/biocatch/jobs/sales-account-manager-iberia",
"company": "BioCatch",
"companyLogo": "https://cdn-images.himalayas.app/wb53l4a8sm9f3f8mqkjnyapvsoga",
"companyUrl": "https://himalayas.app/companies/biocatch",
"title": "Sales Account Manager Iberia",
"category": "Sales",
"categories": ["Sales"],
"tags": ["Sales And Account Management", "B2B Sales", "Account Manager"],
"employmentType": "FullTime",
"seniority": "manager",
"locationRaw": "Spain",
"countries": ["Spain"],
"remoteRegions": null,
"timezones": [0, 1],
"isRemote": true,
"workplaceType": "remote",
"salaryMin": null,
"salaryMax": null,
"postedAt": "2026-09-15T19:29:00.000Z",
"expiresAt": "2026-11-14T19:28:59.000Z",
"alsoOn": null,
"alsoOnBoards": null,
"source": "himalayas.app"
}

…and the same row for a posting that turned up on three boards at once, with the other two folded into it:

{
"rowType": "job",
"jobId": "himalayas:proxify/senior-data-engineer-azure",
"board": "himalayas",
"company": "Proxify AB",
"title": "Senior Data Engineer (Azure)",
"salaryMin": 70200,
"salaryMax": 117000,
"salaryCurrency": "USD",
"salaryPeriod": "YEAR",
"alsoOn": [
{
"board": "weworkremotely",
"url": "https://weworkremotely.com/remote-jobs/proxify-ab-senior-data-engineer-azure",
"applyUrl": "https://weworkremotely.com/remote-jobs/proxify-ab-senior-data-engineer-azure",
"boardJobId": "proxify-ab-senior-data-engineer-azure",
"postedAt": "2026-09-15T12:04:11.000Z"
},
{
"board": "workingnomads",
"url": "https://www.workingnomads.com/job/go/1864102/",
"applyUrl": "https://www.workingnomads.com/job/go/1864102/",
"boardJobId": "1864102",
"postedAt": "2026-09-15T09:31:00-04:00"
}
],
"alsoOnBoards": ["weworkremotely", "workingnomads"]
}

That is one row and one charge, not three. The richest copy wins, its empty columns are filled in from the others, tags and countries are merged, and every place the job was found is on the row so you can still link to whichever board you like.


Quick start

Leave everything on its defaults and run it. You get the newest jobs from all seven boards, deduplicated, remote only.

Some things people actually ask:

You wantInput
Remote Python jobs, anywhere{ "keywords": ["python"], "tags": ["python"] }
Senior engineering roles paying over $120k{ "keywords": ["engineer"], "seniority": ["senior", "staff", "principal"], "salaryMin": 120000 }
Jobs open to Europe, posted this week{ "locations": ["Europe"], "postedSince": "7d" }
Jobs an employer will hire across UTC−5{ "timezones": [-5] } (Himalayas is the only board that publishes this)
Only jobs with a published salary{ "salaryOnly": true }
Full descriptions for an LLM pipeline{ "includeDescription": true, "descriptionFormat": "markdown" }
Everything one company is hiring for{ "companies": ["Proxify"] }
A daily alert of new jobs only{ "monitor": true } on a schedule
The deep board on its own{ "boards": ["himalayas"], "himalayasMaxPages": 50, "maxJobs": 0 }

What each board actually gives you

Measured 2026-09-15. These are not marketing numbers — they are what one run returned, and the Actor will not pretend otherwise.

BoardWhat one run returnsFreshnessStructured salary
RemoteOKnewest ~100 (the public API's cap; there is no paging)newest firstbare numbers on ~12% of rows, labelled USD/year by board convention
Remotivethe whole public feed — 15 jobs today, job-count = total-job-countdelayed 24 hours by Remotive's own termsfree text only, 9 of 15 rows
We Work Remotely88 items — the newest ~10 per category, not the newest 88 on the boardnewest first per categorynone
Himalayas104,000+, paged 20 at a timeliveyes — min, max, currency and period on ~55% of rows
Jobicyup to 200 per requestupdated hourlyyes, ~50% of rows
Working Nomads50newest firstnone
Arbeitnow250 per page, of which ~4% are flagged remoteupdated hourlynone

Himalayas is the board with depth. Remotive and We Work Remotely are small today and are in the default list because they carry jobs the others do not, not because they carry volume. Arbeitnow is a European aggregator: with remoteOnly on it contributes about ten rows per page, and the 240 it does not contribute are read and discarded for free — the board-summary row shows you exactly that.


Input

FieldTypeDefaultWhat it does
boardsmulti-selectall sevenWhich boards to read
keywordsarraynoneTitle, tags and category must contain one of these
excludeKeywordsarraynoneDrop jobs containing one of these, description included
keywordsMatchDescriptionbooleanfalseExtend keywords to the posting body
categoriesarraynoneMatch the board's own category names. Also picks which We Work Remotely category feed is fetched
tagsarraynoneMatch the board's tags. The first entry is sent to RemoteOK as ?tag=
locationsarraynoneFree text over the location line, countries and remote regions. The first entry is sent to Jobicy as geo when it maps
timezonesarray of intsnoneUTC offsets the employer will hire across. Himalayas only
employmentTypesmulti-selectnoneFullTime, PartTime, Contract, Temporary, Internship, Volunteer, Other
senioritymulti-selectnoneinternexecutive
companies / excludeCompaniesarraynoneInclude or exclude employers by name
salaryMininteger0USD-per-year floor, compared to the top of the published range
salaryOnlybooleanfalseDrop jobs with no published pay
postedSincestringnoneISO date or a relative window: 48h, 7d, 4w
remoteOnlybooleantrueOnly bites on Arbeitnow; the other six are remote-only by construction
maxJobsinteger100Per board, counted in matching rows. 0 = no cap
maxJobsTotalinteger0Cap for the whole run, applied after the dedupe
includeDescriptionbooleanfalseAdd the full posting body. No surcharge
descriptionFormatselecttexttext, markdown or sanitized html
deduplicatebooleantrueCollapse the same posting across boards into one charged row
monitorbooleanfalseReturn only postings never returned before
stateStoreNamestringremote-jobs-api-stateThe named key-value store the monitor remembers in
firstRunBehaviorselectbaseline-onlyWhat the first monitor run does
stateRetentionDaysinteger45How long the monitor remembers a posting
arbeitnowMaxPagesinteger2Arbeitnow pages to read (2 MB each, ~10 remote rows each)
himalayasMaxPagesinteger10Himalayas pages to read (20 rows each)
maxConcurrencyinteger4Boards in flight
maxRunSecsinteger240Wall-clock budget for the run
requestTimeoutSecsinteger20Per-request timeout
proxyConfigurationobjectApify datacenterEvery board answered through it, and directly

Output reference

Four row types, all in one dataset with the same columns:

rowTypeWhat it isCharged
jobOne unique posting that passed every filteryes
board-summaryOne per board: what it read, what survived, what you paid forno
run-summaryThe same for the whole run, plus duplicatesCollapsedno
diagnosticOne thing that could not be read, and whyno

Identity and links: jobId, board, boardJobId, url, applyUrl, alsoOn, alsoOnBoards. Employer: company, companyLogo, companyUrl, companyDomain. Role: title, category, categories, tags, employmentType, seniority, excerpt, descriptionText, descriptionHtml. Where: locationRaw, locations, countries, remoteRegions, timezones, isRemote, workplaceType. Money: salaryMin, salaryMax, salaryCurrency, salaryPeriod, salaryRaw. Time: postedAt, expiresAt, isNew, firstSeenAt.

The board summary is your receipt

Every run writes one free row per board:

{
"rowType": "board-summary",
"board": "arbeitnow",
"pagesFetched": 2,
"jobsFetched": 500,
"jobsAfterFilters": 20,
"jobsDeduped": 18,
"jobsNew": 18,
"jobsCharged": 18,
"boardNotice": "This is a free public API for jobs, please do not abuse…"
}

Five hundred postings read, twenty matched, eighteen billed. boardNotice carries that board's own terms, in its own words, so the attribution obligation travels with the data.

Salary

Salary is reported only where the board publishes it, and never inferred from description text. Three of the seven publish structured pay; Remotive publishes free text, which is parsed into numbers where it can be and kept verbatim in salaryRaw either way. RemoteOK publishes bare numbers with no currency and no period — it quotes annual USD on its own board, so that is what is reported, and this sentence is the disclosure that it is a board convention rather than something the feed stated. salaryMin compares against the top of a range and converts an hourly or monthly rate for the comparison only; the columns always hold exactly what the board published, in the currency it published.


What you are never charged for

  • Every job your filters excluded. The filters run before the charge, and an excluded job does not even count toward maxJobs.
  • Every duplicate collapsed across boards. One posting on three boards is one row and one charge.
  • Every board-summary and run-summary row.
  • Every diagnostic: blocked, rate-limited, not-found, no-results, timeout, unavailable, parse-error, invalid-input, locked, budget-reached.
  • Every job already in the monitor's state.
  • Descriptions. All seven feeds embed the full posting in their list response, so there is no second request to bill for and there is no description, salary or "detail" surcharge — not now and not later.
  • A run that returns no job at all. It finishes FAILED, says why, and bills nothing, start fee included.
  • A monitor's baseline run, and a monitor on a day with nothing new. See below.

Pricing

EventFREEBRONZESILVERGOLD
Run started$0.001$0.001$0.001$0.001
Job returned$0.0005$0.0005$0.0004$0.0003

That is $0.50 per 1,000 jobs, descriptions included. There is one paid event and there will never be a second.

RunRowsCost
The prefill — 3 boards, keywords: ["developer","engineer"], maxJobs: 25~40 jobs$0.001 + $0.020 = $0.021
All seven boards, no keyword filter, maxJobs: 0~600 unique jobs$0.001 + $0.300 = $0.301
Daily monitor after the baseline, seven boards~40 new jobs$0.001 + $0.020 = $0.021
Daily monitor, keywords: ["rust"], on a quiet day0 jobs$0.00 — SUCCEEDED, free

The run honours ACTOR_MAX_TOTAL_CHARGE_USD: when the budget runs out it stops delivering rather than handing over rows it cannot bill for, finishes SUCCEEDED and says so in the run status.


Running it every day: monitor mode

Set monitor: true and schedule it. Every run after the first returns only postings this Actor has never returned before.

The first run is a baseline. It reads every board, records what is out there, returns no job rows and charges for none of them, and finishes SUCCEEDED with a status message saying so. That is deliberate and it is the one documented exception to "a run with no job rows fails": a schedule's first day is not a failure, and charging for a list of everything that already existed would be charging for the wrong thing. Set firstRunBehavior: "emit-all" if you would rather have the backfill and pay for it.

A later run on a day when nothing new was posted also finishes SUCCEEDED and costs nothing at all — not even the start fee. A scheduled job should not look broken because hiring was quiet.

isNew and firstSeenAt are on every row in both modes, so a non-monitor run is still useful for your own diffing.

Where the state lives

An Actor's default key-value store is created fresh every run, so it cannot carry yesterday's state. This Actor keeps state in a named store — remote-jobs-api-state by default — which works within its limited permissions: such an Actor may create storage, write to it, and re-open storages it created in previous runs. Nothing else in your account is touched, and limited permissions are a requirement for x402 agentic payments.

The one tradeoff: a limited-permissions Actor cannot read a store you created by hand. If stateStoreName points at one you made in Console, the Actor may not be able to open it — and it then logs a warning, treats the run as a first run, and returns no job rows, so a permissions problem never becomes a wave of jobs re-announced as new and re-charged. Let the Actor create its own store and this never comes up.

One record holds every job the monitor has seen: a dedupe key, a date and a short board list, about 70 bytes each. Entries older than stateRetentionDays are pruned on every write and the record is capped at 50,000 jobs, oldest first. Two overlapping scheduled runs would each diff against the same state and double-report every job, so the second one takes one look at the run lock, writes a free locked diagnostic and stops without fetching anything.


Proxy

The default is Apify's datacenter proxy, and it is the right default: every one of the seven boards answered HTTP 200 through it, and HTTP 200 with no proxy at all, in the capture probe. There is no residential leg anywhere in this Actor. Switch proxyConfiguration to residential only if a board starts refusing your runs; a board that refuses one exit IP is retried once from a new one and then reported as a free blocked diagnostic rather than hammered.

Boards are read in parallel, never more than one request at a time against any one of them, with a pause between successive pages.


Use it from an AI agent, or from code

One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with limited permissions, uses pay-per-event pricing and never enters Standby, so it works over the Apify MCP server and with x402 agentic payments. The Integrations tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.

curl -X POST "https://api.apify.com/v2/acts/insight.solutions~remote-jobs-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"boards":["himalayas","remoteok"],"keywords":["rust"],"salaryMin":120000,"maxJobs":50}'
# pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/remote-jobs-api").call(run_input={
"keywords": ["machine learning", "ml engineer"],
"locations": ["Worldwide", "Europe"],
"salaryOnly": True,
"postedSince": "7d",
"includeDescription": True,
"descriptionFormat": "markdown",
"maxJobs": 100,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row.get("ok") and row["rowType"] == "job":
print(row["title"], row["company"], row["board"],
row["salaryMin"], row["salaryMax"], row["applyUrl"], sep=" | ")

Set monitor: true when you want an alert rather than a backfill: the run then returns only postings it has not returned before.


FAQ

Do I need an API key for any of these boards? No. All seven publish a public feed, and this Actor reads nothing else. No login, no cookie that identifies anyone, no scraping behind a paywall.

Why is Remotive only fifteen jobs? Because that is what Remotive's public API returns today — job-count and total-job-count both say fifteen. It is not a page of a larger feed. Treat it as a bonus board.

Why does Arbeitnow return so little? Because it is a European job aggregator, not a remote board: roughly 4% of its postings are flagged remote. Those 4% are real remote jobs the other six boards do not have. The rest are read and dropped for free, and the board-summary row shows you the ratio.

How does the cross-board dedupe decide two postings are the same job? Company plus title, both normalised — legal suffixes dropped (OpenBase LLC and OpenBase are one employer), bracketed qualifiers dropped ((m/w/d), (Remote), (IR-471)), and a leading or trailing "remote" removed. It is applied across boards only. Two postings on the same board that share a company and a title are two requisitions — companies really do post "Senior Software Engineer" twice for two teams — and collapsing those would lose a real job.

Why does timezones only return Himalayas jobs? Because Himalayas is the only one of the seven that publishes timezone restrictions. A posting that says nothing about timezones is not "open to every timezone", so it cannot satisfy a timezone filter.

Why is salaryMin null on so many rows? Because most boards do not publish pay. Nothing here reads a number out of a description and calls it a salary. Use salaryOnly: true if you only want rows that have one.

Can I get the full job description? Yes, includeDescription: true, in text, Markdown or sanitized HTML, at no extra cost. Recruiter names, email addresses and personal profile links are removed from it.

What happens if a board changes its format? The feeds are public APIs, and their shape can change without notice. A board that answers in a shape this Actor does not recognise produces a free parse-error diagnostic row naming the URL, the other boards still deliver, and the run only finishes successfully if it returned at least one real job.


Limitations

  • The feeds are what the boards publish. Two of them are small today — Remotive 15 jobs, We Work Remotely 88 — and that is everything their public APIs have, not a page of something larger.
  • RemoteOK has no paging. Its API serves the newest ~100 and stops. A ?tag= query returns a different hundred, which is the only way to go deeper.
  • We Work Remotely's all-jobs feed is the newest ~10 per category, so it is not in date order across the feed. Set categories to read a category feed instead.
  • Arbeitnow is a European ATS aggregator where roughly 4% of postings are remote, and its pages are ~2 MB each. That is why arbeitnowMaxPages defaults to 2.
  • Working Nomads returns a flat 50 with no paging parameter that we have found.
  • Salary is only present where the board publishes it, and is never inferred from description text.
  • Seniority is derived from the job title, except on Himalayas and Jobicy, which publish their own band.
  • This Actor deliberately does no employer-email enrichment — contact lines are stripped from descriptionText — and does no scraping around any board's feed cap. Where a board publishes only its newest N, that is what you get, and the table above says which.
  • The upstream formats may change without notice. See the FAQ.

Attribution — please honour it

Every board here publishes its feed on terms. They are short, they are reasonable, and they are reproduced below in each board's own words. Every row carries the board it came from and that board's own job URL, so linking back is a copy of one field. You are responsible for honouring each board's terms in whatever you build.

  • Remote OK"Please link back (with follow, and without nofollow!) to the URL on Remote OK and mention Remote OK as a source, so we get traffic back from your site. If you do not we'll have to suspend API access." Also: do not use the Remote OK logo — it is a registered trademark — but the name is fine.
  • Remotive"Please link back to the URL found on Remotive AND mention Remotive as a source in order to Remotive to get traffic from your listing." Do not resubmit Remotive jobs to third-party boards (their notice names Jooble, Neuvoo, Google Jobs and LinkedIn Jobs). Using their listings to collect signups or email addresses breaches their terms. Their jobs are delayed 24 hours, and they ask for at most about four requests a day — this Actor makes exactly one per run.
  • We Work Remotely — credit We Work Remotely and link to the job's own URL. Their robots.txt permits the RSS paths this Actor reads.
  • Himalayas — link back to the original job URL and name Himalayas as the source.
  • Jobicy"Please ensure Jobicy is clearly credited with a direct link to the source, and all application buttons redirect to the original job URL provided in this feed." Their fair-use rule is at most one request per hour; this Actor makes one per run.
  • Working Nomads — credit Working Nomads and link to the job URL. Their robots.txt disallows nothing.
  • Arbeitnow"This is a free public API for jobs, please do not abuse. I would appreciate linking back to the site. By using the API, you agree to the terms of service present on Arbeitnow.com"

Each board's notice is also written into the dataset, on that board's board-summary row, in the boardNotice column.


Our other Actors

Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.

Video, audio & social

News, documents & the web

Business, finance & jobs

Apps & games