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

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

Pricing

from $0.60 / 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.60 / 1,000 job returneds

Rating

0.0

(0)

Developer

Insight Solutions

Insight Solutions

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

3 days 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": 100,
"remoteokMaxRequests": 6,
"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 — $1.00 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 code — client.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 per request, and ~90 more for each category you spend a request on — remoteokMaxRequestsnewest 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.

How RemoteOK gets past its newest 100

RemoteOK's public API has no paging parameter. ?offset= looks like one and is not: ?offset=200 comes back byte-for-byte identical to the plain feed, and the ?offset=100 response that does hold older jobs is a stale cached copy, ten days older by its own last_updated. So there is no page two to ask for.

What the API does honour is ?tag=, and a tag returns a genuinely different hundred: the captured ?tag=python feed shares 7 of 100 rows with the untagged one. So remoteokMaxRequests spends requests on RemoteOK's own categories — exec, medical, finance, education, ops, marketing, design, dev, the sections least like the newest-100 feed first — and each one is worth roughly 90 postings the newest-100 feed never mentions.

  • The API is always the first source. Request one is exactly the untagged newest-100 feed (or ?tag= when you set tags). remoteokMaxRequests: 1 is the old behaviour to the byte.
  • Requests go one at a time, a second apart, on this run's own address first and through the proxy only if that address is refused. RemoteOK's terms are the strictest of the seven boards here and this is the only one asked more than once.
  • A posting that appears in two of RemoteOK's categories is one job, charged once, and does not spend your maxJobs.
  • A run that reaches maxJobs on request one stops there and spends none of the rest, so raising this costs nothing when your filters are narrow.

There is also remoteokBoardHtml, which reads RemoteOK's public board pages alongside the API. It is off by default and new: the page reader is written against RemoteOK's documented markup and has not yet been confirmed against a live page. With it on and the markup moved, those requests return nothing, you get a free diagnostic row, and the API feeds deliver as they always do.


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. They are also what RemoteOK is asked for as ?tag=, in order, within remoteokMaxRequests
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-selectnoneintern … executive
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
remoteokMaxRequestsinteger4Requests to spend on RemoteOK. 1 = its newest ~100 only; each one after that is a RemoteOK category worth ~90 more postings
remoteokBoardHtmlbooleanfalseAlso read RemoteOK's HTML board pages. New — first live run pending; see above
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.

jobsFetched is every posting the board handed over, so on a board read from more than one of its own feeds — RemoteOK with remoteokMaxRequests above 1 — the gap down to jobsAfterFilters is the rows your filters excluded plus the ones that were the same posting arriving from a second feed. Both are free, and the run summary's duplicatesCollapsed counts every collapse of either kind: one board against itself, and one posting found on two different boards.

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. A run whose boards were all usable but produced nothing — a board that refused us, an empty feed, filters that excluded every job — finishes SUCCEEDED with zero results, a status message that says so, and free diagnostic and summary rows telling you why; nothing is charged, start fee included. A run finishes FAILED only when there was nothing usable to attempt, or the Actor itself hit an error.
  • 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.001$0.001$0.0008$0.0006

That is $1.00 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.040 = $0.041
All seven boards, no keyword filter, maxJobs: 0~600 unique jobs$0.001 + $0.600 = $0.601
Daily monitor after the baseline, seven boards~40 new jobs$0.001 + $0.040 = $0.041
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: 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. It is the one run that bills the start fee without returning a job row. 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.


Building an app with Lovable? Apify's Lovable connector runs any Actor from inside a Lovable project with your Apify token. Connect Apify in Lovable once, then ask it: "Use Apify to run insight.solutions/remote-jobs-api with my keywords and build a remote job board around the results." At the default settings a run finishes well inside Lovable's 300-second synchronous limit, and every result is one JSON object per row, so the generated app needs no extra parsing. Usage bills to the Apify account whose token is connected.

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 and the other boards still deliver. If no board returned a job, the run finishes SUCCEEDED with zero results and a status message pointing at those rows, and nothing is charged.


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 per request and stops, and ?offset= is ignored — it looks like paging only until you try a second value. A ?tag= query returns a different hundred, which is the only way to go deeper, and remoteokMaxRequests is how many of those you buy.
  • 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