RemoteOK Jobs Scraper — Remote Job Feed avatar

RemoteOK Jobs Scraper — Remote Job Feed

Pricing

from $1.30 / 1,000 posting saveds

Go to Apify Store
RemoteOK Jobs Scraper — Remote Job Feed

RemoteOK Jobs Scraper — Remote Job Feed

The latest remote jobs RemoteOK publishes, as structured data: title, company, tags, location with country where stated, employment type, RemoteOK salary figures, posted date, full text on request. One request per run. Incremental mode charges only for changes. No personal data.

Pricing

from $1.30 / 1,000 posting saveds

Rating

0.0

(0)

Developer

Adderley Data

Adderley Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 hours ago

Last modified

Share

What does RemoteOK Jobs Scraper do?

RemoteOK Jobs Scraper reads RemoteOK's public API — the JSON feed RemoteOK publishes at remoteok.com/api for anyone to use — and returns the jobs in it as structured data you can load straight into a spreadsheet, a database or a model. RemoteOK lists remote jobs only.

One request returns the latest jobs RemoteOK publishes there: about a hundred, covering the last several weeks (on 26 September 2026 the feed held 99 jobs, posted between 1 August and 24 September). That is what the API offers; it is not RemoteOK's whole archive, and this Actor does not claim it is.

For each job you get the title, the company, RemoteOK's tags, the location as RemoteOK gives it with the country where the text names one, the employment type where a tag names one, the salary RemoteOK states (or the range the job's own text states), when it was posted, and the link to the job's page on RemoteOK — and, if you ask for it, the full text of the job.

What makes it different:

  • One request per run. The whole feed, every job's text included, comes back in a single call to RemoteOK's API. No HTML parsing, no page-by-page crawling, nothing to break when a page is redesigned.
  • Text as RemoteOK means it. RemoteOK's feed sends every accented letter, dash and emoji as the separate bytes that encode it — on 26 September 2026, 80 of the 99 job texts arrived that way, so "Mecánico" read as Mecánico — and a few job texts arrive as escaped HTML. The Actor reads both back before anything is filtered or saved, and leaves text that is already correct alone.
  • Incremental mode that tells the truth about a window. Put the Actor on a schedule and each run returns only jobs that are new or changed, and charges only for those. It never reports a job as expired just because it has dropped out of RemoteOK's latest hundred.
  • One stable schema. Every row has every field, every time. Unknown is null, never a missing key. The schema is versioned (job.v1) and every Adderley Data jobs Actor uses it, so RemoteOK sits in the same table as Greenhouse, Workable or SEEK.
  • No personal data. RemoteOK's feed has no recruiter field, and job.v1 has nowhere to put a person. Contact details inside job text are redacted by default. Logos are never returned.
  • Bounded cost. You set a maximum number of results; the run stops there. It also stops at the spending limit you set on the run in Apify.

What RemoteOK data can you extract?

FieldWhat it holds
idStable across runs: source:market:sourceJobId. Use it as your primary key.
titleJob title as listed.
company.nameThe hiring company, where the listing names one.
advertiser.nameThe business that placed the listing — often a recruitment agency. Never a person.
location.rawLocation text as listed. Several locations are joined with |.
location.suburbSuburb, when the listing states one.
location.cityCity or area, when the listing states one.
location.regionState or region, e.g. VIC.
location.postcodePostcode, when the source provides it.
location.countryISO 3166-1 alpha-2 country code.
workArrangementon_site, hybrid, remote or unknown.
employmentTypesNormalised: full_time, part_time, contract, casual, temporary, internship, volunteer.
salary.rawThe salary text exactly as shown, or null when the listing shows none.
salary.minLower bound as a number, when the text contains one.
salary.maxUpper bound as a number. Equal to min for a single figure.
salary.currencyISO 4217. Taken from the text, otherwise the market default.
salary.periodhour, day, week, month or year; null when the text does not say.
salary.includesSupertrue / false when the text says so ("plus super", "inc. super"); otherwise null.
classificationsThe source's category and subcategory pairs.
teaserThe short summary shown on the results page.
bulletPointsSelling points shown on the results page.
postedAtWhen the listing was posted, ISO 8601 UTC.
updatedAtThe source's own last-modified time, ISO 8601 UTC. Published by ATS and API sources; null where the site does not show one.
expiresAtExpiry, ISO 8601 UTC, where the source states one.
isPromotedtrue for paid placements. A listing shown both promoted and organic is returned once.
urlLink to the listing.
descriptionNull unless requested. text, optional sanitised html, and contactsRedacted.
changeTypeIncremental runs: NEW, UPDATED, REAPPEARED, EXPIRED (or UNCHANGED if you ask for those). Otherwise null.
firstSeenAtIncremental runs: when this monitor first saw the listing.
contentHashSHA-256 over the fields that define a change. Compare it to detect edits yourself.
scrapedAtWhen this row was produced, ISO 8601 UTC.
sourceSource key, e.g. seek.
marketMarket key, e.g. au, nz.
sourceJobIdThe source's own identifier for the listing.
company.sourceCompanyIdThe source's identifier for the company, when exposed.
company.urlThe company's page on the source site, when exposed.
advertiser.sourceAdvertiserIdThe source's identifier for the advertiser.
schemaVersionAlways job.v1. Breaking changes ship as job.v2 in a new Actor version, never silently.

How RemoteOK's fields fill the schema:

  • url is the job's own page on RemoteOK, https://remoteok.com/remote-jobs/<slug>. The feed writes the address with capitals (remoteOK.com); the Actor writes it the ordinary way. sourceJobId is RemoteOK's job number, and id is remoteok:global:<number>.
  • company.name and advertiser.name are the company name RemoteOK gives. RemoteOK publishes no company identifier or company page, so company.sourceCompanyId, company.url and advertiser.sourceAdvertiserId are null.
  • location.raw is the location text as RemoteOK gives it, with the stray comma RemoteOK sometimes leaves at the end dropped. It is empty for many jobs (40 of 99 on 26 September 2026). location.country is filled only when the text names a country — "Remote - US", "Germany", "Remote UK", "London, London, England, United Kingdom" — and city and region only when the text's shape says which part is which ("Vancouver, BC, Canada"). A city on its own ("Melbourne", "Boston") could be in more than one country and gets no country. "Remote", "Remoto" and "Worldwide" name no place.
  • classifications holds RemoteOK's tags, one per tag, in lower case and in RemoteOK's order, each once ({"category": "golang", "subcategory": null}).
  • employmentTypes comes from a tag that names one: "full time" gives full_time, "part time" part_time, "contract", "contractor" and "freelance" contract, "internship" or "intern" internship, "temporary" temporary, "volunteer" volunteer. Most jobs carry no such tag, and their list is empty — nothing is guessed from the title.
  • salary holds RemoteOK's own figures, in US dollars a year by RemoteOK's convention, or else the range the job's text states — the questions below explain both. salary.includesSuper is always null.
  • workArrangement is remote for every job: RemoteOK lists only remote jobs.
  • postedAt is RemoteOK's date for the job, to the second, in UTC. updatedAt and expiresAt are null: RemoteOK's feed publishes neither.
  • market is always global, and isPromoted is always false: the feed does not mark paid placements.

How much does it cost to scrape RemoteOK?

You pay per job saved to your dataset — $1.50 per 1,000 jobs on Apify's Starter plan — plus $0.005 each time a run starts. There is no monthly rental.

Apify planPricePer listing
Free$1.50 per 1,000 listings$0.00150
Starter (Bronze)$1.50 per 1,000 listings$0.00150
Scale (Silver)$1.40 per 1,000 listings$0.00140
Business (Gold)$1.30 per 1,000 listings$0.00130

Plus $0.005 per run start. Compute and proxy are included in these prices.

What you runCost (USD, Starter plan)
100 listings, one run$0.15
1,000 listings, one run$1.50
10,000 listings, one run$15.01
50,000 listings, one run$75.00
A daily incremental monitor finding about 150 new or changed listings a day, for a month$6.90

A run saves at most about a hundred jobs, the size of RemoteOK's feed, so the first line of the table is close to the cost of the largest run there is, and the larger lines apply only to many runs added together. Descriptions cost nothing extra: they arrive in the same request as the listing. Use incremental mode for anything you run more than once — after the first run you pay only for what is new or changed. As an estimate from one reading: the 99 jobs in the feed on 26 September 2026 had been posted over eight weeks, about two a day, so a daily monitor of the whole feed would save a few jobs a run, not the 150 a day in the table.

How to scrape RemoteOK jobs

  1. Open the Actor in Apify Console and go to the Input tab.
  2. Leave every filter empty for the whole feed, or narrow it: Title keywords, Tags, Locations, Posted within (days), Minimum salary (US dollars a year).
  3. Set Maximum results. This is also your cost cap.
  4. Press Start. The run makes one request, so it is short. When it finishes, open the Output tab and export as JSON, CSV, Excel, XML or HTML, or read the dataset through the Apify API.

Every filter is applied to the one response, so filters never add requests. RemoteOK's own ?tag= parameter is not used: it did not filter reliably when this Actor was built.

Input

FieldTypeDefaultWhat it does
keywordsarray—Keep jobs whose title contains every word of any keyword, in any order — "engineer data" matches "Senior Data Engineer". Leave empty for all titles.
tagsarray—Keep jobs carrying any of these RemoteOK tags, e.g. "dev", "golang", "customer support", "full time". A whole tag is matched, without regard to case, and "full-time" is the same as "full time". Leave empty for all tags.
locationsarray—Keep jobs whose location names this country ("Germany", "UK", "United States") or has this text as a whole word or phrase ("Texas", "London", "Europe", "LATAM"). "Worldwide", "Anywhere", "Remote" or "Global" keep jobs whose location names no place — empty, or only a word such as "Remote". Leave empty for all locations.
postedWithinDaysinteger—Keep jobs RemoteOK dates within the last N days, counted back from the start of the run. Leave empty for any time — the feed holds the last several weeks.
salaryMinUsdinteger—Keep jobs whose RemoteOK salary reaches this many US dollars a year: the top of the range, or the one figure given. Jobs RemoteOK gives no salary for are left out when this is set. Leave empty for any pay.
maxResultsinteger100The run stops once this many jobs are saved, newest first. You are charged per job saved, so this is also your cost cap. RemoteOK's feed holds about a hundred jobs.
includeDescriptionbooleanfalseOn: the full job text comes back with each row, read from the same request as the listing, so it costs no extra requests. Off: listing fields only.
descriptionFormattext, text_and_html"text"Plain text, or plain text plus sanitised HTML.
redactContactsbooleantrueOn by default: email addresses and phone numbers inside description text are replaced with [redacted]. This Actor never outputs recruiter names or contact fields.
incrementalbooleanfalseRemember what earlier runs saw and save only jobs that are new or changed. Unchanged jobs are skipped and not charged. Put the Actor on a schedule with this on.
stateKeystring—Optional name for this monitor, e.g. "remote-engineering". Runs with the same key share memory. Left empty, a key is derived from the filters themselves.
emitExpiredbooleanfalseKept so one input works across Adderley Data Actors; it has no effect here. RemoteOK's feed holds only its latest jobs, so a job that leaves it may still be open, and this Actor never saves an EXPIRED row.
emitUnchangedbooleanfalseIncremental mode only. Saves (and charges for) every job, labelled UNCHANGED where nothing moved.
proxyConfigurationobject{"useApifyProxy":true}Apify Proxy, automatic group, is the default and is what this Actor is tested with.
maxConcurrencyinteger4Parallel requests. A run makes one request, so this changes nothing in practice; the default is the modest one every Adderley Data Actor uses.
maxRequestsPerMinuteinteger90An upper bound on request rate across the whole run. If RemoteOK answers 429 (too many requests), the Actor waits as long as RemoteOK asks, or 30 seconds, and asks again from the same address, at most twice.

A typical input:

{
"tags": [
"dev"
],
"maxResults": 100
}

How the filters decide, so the results are predictable:

  • Title keywords keep a job when its title contains every word of any one keyword, in any order.
  • Tags keep a job carrying any of the tags given. A whole tag is matched, without regard to case, and "full-time", "full_time" and "full time" are the same tag; "engine" does not match "engineer".
  • Locations keep a job when the location names the country given, by name or code ("Germany", "UK", "USA"), or has the text given as a whole word or phrase ("Texas", "London", "Europe", "LATAM" — "US" is not found inside "Austin"). "Worldwide", "Anywhere", "Remote" and "Global" keep jobs whose location names no place: empty, or only a word such as "Remote" or "Remoto". Every RemoteOK job is remote, so "Remote" does not mean every job. A region is matched by its words, not worked out from a country: "Europe" does not keep a job in "Germany".
  • Posted within (days) counts back from the start of the run. A job with no date is kept.
  • Minimum salary reads RemoteOK's own salary figures: the top of the range, or the one figure given. A job whose pay appears only in its text is not kept by this filter.

Output

One row per job. This is a synthetic example in the exact shape the Actor returns:

{
"schemaVersion": "job.v1",
"id": "remoteok:global:9100012",
"source": "remoteok",
"market": "global",
"sourceJobId": "9100012",
"url": "https://remoteok.com/remote-jobs/remote-senior-backend-engineer-go-example-robotics-co-9100012",
"title": "Senior Backend Engineer (Go)",
"company": {
"name": "Example Robotics Co",
"sourceCompanyId": null,
"url": null
},
"advertiser": {
"name": "Example Robotics Co",
"sourceAdvertiserId": null
},
"location": {
"raw": "Austin, Austin, Texas, United States",
"suburb": null,
"city": "Austin",
"region": "Texas",
"postcode": null,
"country": "US"
},
"workArrangement": "remote",
"employmentTypes": [
"full_time"
],
"salary": {
"raw": "USD 150,000 - 185,000 a year",
"min": 150000,
"max": 185000,
"currency": "USD",
"period": "year",
"includesSuper": null
},
"classifications": [
{
"category": "dev",
"subcategory": null
},
{
"category": "engineer",
"subcategory": null
},
{
"category": "golang",
"subcategory": null
},
{
"category": "senior",
"subcategory": null
},
{
"category": "backend",
"subcategory": null
},
{
"category": "full time",
"subcategory": null
}
],
"teaser": null,
"bulletPoints": [],
"postedAt": "2026-09-24T16:00:06.000Z",
"updatedAt": null,
"expiresAt": null,
"isPromoted": false,
"description": null,
"changeType": "NEW",
"firstSeenAt": "2026-09-25T19:30:12.000Z",
"contentHash": "86e23927f8cf8bff14a35d94f5e6787402e3b7048390b9a0b6887633a3e800ca",
"scrapedAt": "2026-09-25T19:30:12.000Z"
}

The Output tab has two table views: Overview (the fields most people want, flattened) and Changes (for incremental runs).

Incremental mode: new and changed RemoteOK jobs on a schedule

Turn on Incremental mode and run the same input on a schedule — hourly or daily. The Actor keeps a small record of what it has seen and every row tells you what happened:

changeTypeMeaning
NEWFirst time this monitor has seen the job
UPDATEDSeen before, and the title, company name, location, tags, salary or any word of the job's text has changed
UNCHANGEDOnly if you turn on Also save unchanged jobs

How it behaves, so there are no surprises:

  • EXPIRED is never reported. RemoteOK's feed is a window onto its latest jobs, not the whole of them. A job leaves the window when newer jobs push it out, whether or not it is still open on RemoteOK, so its absence proves nothing. Other Adderley Data Actors, which read whole job boards, report EXPIRED; this one never does, and never reports REAPPEARED either. Report expired jobs has no effect here.
  • The first run returns everything as NEW. From the second run you pay only for the difference.
  • A job that leaves the window and comes back unchanged is not saved or charged again.
  • A change is found by comparing the job itself. An edit to its text counts: a corrected typo is reported as UPDATED and charged like any other change. A change of formatting alone does not count, and neither do the order of the jobs or of a job's tags, nor a new date on a job that is otherwise the same, nor RemoteOK's note to applicants at the end of every text (see below), whose tag changes with the address the feed is requested from.
  • The feed covers several weeks, so a daily schedule sees every job that stays in it for a day or more.
  • Runs share memory when they share a State key. Leave it empty and the key is derived from the filters themselves, so the same input always continues the same monitor. Name it (remote-engineering) if you want to change filters later without starting again.

Descriptions and contact details

Full descriptions are off by default. Turn on Include full descriptions and each row carries description.text (and sanitised description.html if you choose that format): the job's text as RemoteOK publishes it. Because RemoteOK returns the text in the same response as the listing, this costs no extra requests and no extra time.

RemoteOK ends every job's text with a note to applicants: "Please mention the word … and tag … when applying to show you read the job post completely." The word belongs to the job, and the tag encodes the network address the feed was requested from — on the Apify platform, the proxy's. The note is part of what RemoteOK publishes, so it stays in the text; the Actor leaves it out only when comparing runs.

Job text sometimes contains a recruiter's email address or phone number. With Redact contact details on — the default — those are replaced with [redacted] and description.contactsRedacted is true; so is a personal profile address (linkedin.com/in/…). Neither description.text nor the optional HTML carries the address behind a link: the HTML keeps each link's words and drops its address, and drops images. The Actor never returns recruiter names or contact details as fields, under any setting, and never returns a company logo. If your use case is contacting individuals, this is the wrong tool.

What people use it for

  • Remote job boards and alerts. A clean feed of new remote jobs, deduplicated, labelled by change and linked back to RemoteOK.
  • Hiring signals. Which companies are hiring remotely, for which skills, at which pay — a daily monitor is one scheduled run.
  • Pay research. RemoteOK's salary figures as numbers, beside the ranges job texts state themselves.
  • Skills and tag trends. RemoteOK's tags over time, as a table.
  • Research and teaching. A clean, repeatable dataset with a documented schema.

Using the API

Run it from code with the Apify client, using your own API token:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('adderleydata/remoteok-jobs-scraper').call({"tags":["dev"],"maxResults":100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]?.url);
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("adderleydata/remoteok-jobs-scraper").call(run_input={"tags":["dev"],"maxResults":100})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["title"], item["company"]["name"], item["url"])

Schedules, webhooks and the Make, Zapier, n8n and Google Sheets integrations all work the way they do for any Apify Actor. The Actor runs with limited permissions and is priced per event, so AI agents can call it through Apify's MCP server as well.

The Actor reads RemoteOK's public API — the feed RemoteOK publishes for others to use, with no login and no key — once per run, and returns facts about job postings. It does not log in, does not solve CAPTCHAs, does not get round any limit RemoteOK sets and does not collect personal information. If RemoteOK answers that it is getting too many requests, the Actor waits as long as RemoteOK asks and asks again from the same address, at most twice; it never switches address to get round a limit.

RemoteOK asks everyone who uses data from its API or its site to link back to RemoteOK where the data is used, and to mention Remote OK as the source. Every row carries url, the job's page on RemoteOK, for that link. RemoteOK also asks that its logo not be used without its permission; the Actor never returns logos.

What you do with the data is your responsibility. Each job's text is its advertiser's copyright — analyse it, do not republish it. If your project touches personal information, privacy law applies to you wherever you are. This is general information, not legal advice.

Questions

Why does a run return about a hundred jobs at most? That is what RemoteOK's API holds: its latest jobs, covering the last several weeks. A larger Maximum results does not change it.

Why is a job that disappeared not reported as expired? Because the feed is a window. A job drops out of it when newer jobs push it out, whether or not it has been filled. The incremental mode section above says what is reported instead.

Why is the salary in US dollars a year? RemoteOK gives each job two salary figures and no currency or period, and shows them on its pages as US dollars a year. That is RemoteOK's convention, not something each job states: where a job's own text gives its pay in another currency or by the hour, RemoteOK's figures are a yearly US-dollar equivalent (on 26 September 2026, a job whose text gave a yearly range in euros, and another paid by the hour, both carried yearly dollar figures). The Actor returns RemoteOK's figures as given — min, max, currency USD, period year — with raw saying them in words ("USD 70,000 - 80,000 a year"; "From USD 70,000 a year" when only a minimum is given). Figures that cannot be a year's pay (one job carried 30 and 36) are not returned as a salary. Where RemoteOK gives no figures, a range the job's text states is read from that sentence, in the currency and period the text states, and the sentence is kept in salary.raw. Otherwise salary is empty; nothing is estimated.

Why is the location empty, or the country missing? Many RemoteOK jobs state no location (40 of 99 on 26 September 2026). Where there is one, the country is filled only when the text names it; a city alone does not say which country it is in.

Why does the Actor save fewer jobs than the feed holds? Filters aside, a job the feed gives without a link to its own page is left out, because every row links to the job on RemoteOK (one of 99 on 26 September 2026, whose title RemoteOK had cut short). Nothing else is dropped.

Why does every description end with a note about mentioning a word? RemoteOK adds it to every job's text, so the Actor keeps it. The descriptions section above says what it holds.

Does it need a RemoteOK login or API key? No. The API is public.

Can I get recruiter emails or phone numbers? No, by design.

How current is the data? It is read from RemoteOK while your run is in progress. postedAt is RemoteOK's date for the job; scrapedAt records when the row was produced.

The field I need is not there. Open an issue on the Issues tab. Fields are added to the schema without breaking existing ones.

Support

Use the Issues tab on this page. We read it every day. Include the run ID and what you expected to see.

Other Adderley Data Actors

Every Actor in a vertical returns the same fields, so adding a source needs no new code on your side.

About

Made by Adderley Data, Melbourne — https://adderleydata.com. Not affiliated with, endorsed by or sponsored by Remote OK. Remote OK is a name and trade mark of its owner, used here only to describe what this Actor reads; its logo is not used.