RemoteOK Jobs Scraper - Remote Listings, Salaries & Details
Pricing
from $1.50 / 1,000 dataset items
RemoteOK Jobs Scraper - Remote Listings, Salaries & Details
Scrape RemoteOK jobs by keyword or job URL. Extract title, company, location, remote details, tags, salary when available, listing URL, description, and metadata into clean structured dataset rows.
Pricing
from $1.50 / 1,000 dataset items
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
13 days ago
Last modified
Categories
Share
RemoteOK Scraper: Remote Job Listings, Filters & Sorting
RemoteOK Scraper turns RemoteOK into clean, structured job rows. Search current listings by keyword or paste RemoteOK listing and tag URLs directly, then filter by salary, company, location, posting age and tags, and sort the saved rows client-side. Every row carries normalized fields such as salary midpoint and posting age alongside the original listing data, and you can export straight to JSON, CSV or Excel, or read it through the API.
Why This Scraper?
- Two ways to find jobs. Keyword search across current listings, or paste RemoteOK listing and tag URLs directly. No need to build search URLs by hand.
- Salary, company, location, age and tag filters. Narrow a run to exactly the roles you care about before you pay for a row.
- Normalized fields on every row. Salary midpoint, posting age in days, a cleaned plain-text description, and matched search tokens, in addition to the site's own fields.
- Client-side sorting. Order saved rows by posting time, salary, or company name without a second pass.
- Full-catalogue pulls across runs. Leave the page cap open and continue an interrupted or capped pull in a later run without re-saving (or re-billing for) jobs the previous run already collected.
- Optional raw fields. Keep the complete original listing object alongside the normalized ones, or drop it to save space.
- Optional MCP connector export. Pipe a summary of saved jobs into apps such as Notion, Linear, Airtable, or Apify.
Use Cases
- Recruiting and sourcing: pull current remote roles by keyword, salary band, or tag to build candidate outreach lists.
- Job board and job-alert apps: feed filtered, sorted listings into your own site, bot, or notification tool.
- Market and compensation research: track salary ranges, posting volume, and tag trends for remote roles over time.
- Lead generation: find companies actively hiring in a given location or stack, using the company and tag filters.
- Competitive and industry analysis: monitor which companies and roles a segment of the remote job market is posting.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
id / jobId | "1000001" (the key used for de-duplication and resume) |
slug | "remote-senior-backend-engineer-sample-labs-1000001" |
title | "Senior Backend Engineer" |
companyName | "Sample Labs" |
locationText | "Remote, Worldwide" |
postedAt | "2024-01-15T09:00:00+00:00" |
postedEpoch | 1705309200 |
postedAgeDays | 12 |
salary_min / salary_max | 90000 / 130000 |
salaryMid | 110000 |
hasSalary | true |
tags | ["python", "backend", "senior"] |
tagCount | 3 |
descriptionText | "We are a fully remote team looking for..." |
url | "https://remoteok.com/remote-jobs/remote-senior-backend-engineer-sample-labs-1000001" |
applyUrl | "https://remoteok.com/remote-jobs/remote-senior-backend-engineer-sample-labs-1000001" |
sourceUrl | the search or URL-mode source this row came from |
sourceType | "search-term" (also "url") |
searchTerm | "python" (null for all jobs, or in url mode with no query term) |
matchedSearchTokens | ["python"] |
matchedTagFilters | matched values from your tags filter, if set |
companyLogoUrl | "https://remoteok.com/assets/sample-logo.png" |
scrapedAt | "2024-01-27T10:00:00.000Z" |
When includeRawFields is on (the default), the complete original listing object is also kept in raw.
How to Use
- Pick a mode:
search(keyword) orurl(paste RemoteOK links). - Fill in the terms or URLs for that mode, and set the filters and sort order you want.
- Set Max items (per term or URL) and, optionally, Max pages, then click Start.
- Download the dataset as JSON, CSV or Excel, or read it through the API.
Broad keyword search:
{"mode": "search","searchTerms": ["all jobs"],"sortBy": "posted-desc","maxItems": 20}
Filtered keyword search:
{"mode": "search","searchTerms": ["python", "devops"],"tags": ["senior"],"minSalary": 80000,"requireSalary": true,"sortBy": "salary-desc","maxItems": 30}
Paste RemoteOK URLs:
{"mode": "url","startUrls": [{ "url": "https://remoteok.com/remote-dev-jobs" },{ "url": "https://remoteok.com/remote-design-jobs" }],"requireSalary": true,"maxItems": 15}
Full-catalogue pull with no page cap:
{"mode": "search","searchTerms": ["all jobs"],"maxItems": 5000,"maxPages": 0}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/remoteok-com-scraper").call(run_input={"mode": "search", "searchTerms": ["python"], "maxItems": 20})for job in client.dataset(run["defaultDatasetId"]).iterate_items():print(job["title"], job["companyName"], job["salaryMid"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/remoteok-com-scraper').call({ mode: 'search', searchTerms: ['python'], maxItems: 20 });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Caps and full-catalogue pulls
Max items and Max pages apply per search term or per URL, not to the whole run: a run with three search terms and maxItems: 20 can save up to 60 rows. Leave Max pages empty to walk every result page for a target; an explicit value stops after that many pages of that target regardless of maxItems.
For a full-catalogue pull (all current listings for a term or URL, not just the first page), leave Max pages empty and set Max items high enough (or paste multiple listing URLs, each with its own cap). Use Resume from a previous run to continue such a pull across separate runs: paste the prior run's ID (or its dataset ID) and this run skips every job id already saved there, appending only new jobs. It skips only the jobs in that one run's dataset, not in runs before it, so for a third pull point it at a dataset that holds everything collected so far, or de-duplicate by id on your side. A checkpoint is also saved automatically as the run progresses, so a run that was interrupted by a platform migration, or that failed and is then resumed with Resurrect, picks back up without re-saving (or re-billing for) jobs it had already pushed in that same run.
Known limitation: within a single run, a job can be marked as already handled before it is actually saved, and is then skipped for the rest of that run:
- If a search term or URL reaches its Max items cap partway through a page of results, every job scanned on that page is marked, not only the ones saved. A later search term or URL in the same run that would have matched some of those jobs skips them. Search terms all read the same listing feed, so overlapping terms (for example
all jobspluspython) are the typical trigger. - If a run is interrupted (migration, abort or failure) while it is still saving the pages of one search term or URL, the rest of that term's or URL's jobs are already marked, so the resumed run skips them.
The marks are part of the checkpoint, so every Resurrect of that same run keeps skipping those jobs; a new run starts clean. Resume from a previous run is not affected, since it only skips jobs actually present in the prior run's dataset. Workaround: avoid overlapping search terms or URLs with a tight maxItems in the same run, or give maxItems enough headroom that a target rarely stops mid-page. If a resumed run came back short, start a new run with Resume from a previous run pointing at the interrupted run to collect the jobs it missed.
Send results into your apps (MCP connectors)
Optionally pipe the saved jobs into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each job, not the full JSON. Each row becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where job pages should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Connector failures never change the dataset output. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (keyword) or url (paste RemoteOK links). |
searchTerms | array | ["all jobs"] | Job keywords or tag phrases, such as python or senior+devops (search mode). |
startUrls | array | sample RemoteOK URLs | RemoteOK listing or tag URLs to read directly (url mode). |
tags | array | (none) | Keep only jobs whose tag list contains at least one of these values. |
companies | array | (none) | Keep only jobs whose company name contains one of these values. |
locations | array | (none) | Keep only jobs whose location text contains one of these values. |
minSalary | integer | (none) | Keep only jobs whose salary range reaches at least this amount. |
maxSalary | integer | (none) | Keep only jobs whose salary range starts at or below this amount. |
postedWithinDays | integer | (none) | Keep only jobs posted within this many days. |
sortBy | string | posted-desc | Client-side sort order applied after filtering: posted-desc, posted-asc, salary-desc, salary-asc, or company-asc. |
requireSalary | boolean | false | Keep only jobs where a salary range is present. |
includeDescriptionText | boolean | true | Add a cleaned plain-text version of the listing description. |
includeRawFields | boolean | true | Keep the full original job object alongside normalized fields, in raw. |
maxItems | integer | 20 | Maximum matching rows to save for each search term or start URL. A cap, not a guarantee. |
maxPages | integer | (none, walks every page) | Result pages to walk per search term or start URL. An explicit value is honored with no upper limit. |
resumeFromRunId | string | (none) | A previous run ID or dataset ID to continue a full-catalogue walk across separate runs; jobs already collected there (matched by id) are skipped. |
proxy | object | Apify Proxy prefill | Connection settings. Accepted but not currently applied: requests are sent directly (see Plan Requirement). |
mcpConnectors | array | (none) | Optional: send a summary of each saved job to apps you authorized under Integrations. |
notionParentPageUrl | string | (none) | Notion connector only: page under which job pages are created. |
maxNotifyJobs | integer | 50 | Cap on jobs written to each connector per run. Does not affect the dataset. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"id": "1000001","jobId": "1000001","slug": "remote-senior-backend-engineer-sample-labs-1000001","title": "Senior Backend Engineer","position": "Senior Backend Engineer","company": "Sample Labs","companyName": "Sample Labs","location": "Remote, Worldwide","locationText": "Remote, Worldwide","descriptionText": "We are a fully remote team looking for a senior backend engineer to join our platform team.","tags": ["python", "backend", "senior"],"tagCount": 3,"url": "https://remoteok.com/remote-jobs/remote-senior-backend-engineer-sample-labs-1000001","applyUrl": "https://remoteok.com/remote-jobs/remote-senior-backend-engineer-sample-labs-1000001","sourceUrl": "https://remoteok.com/remote-python-jobs","sourceType": "search-term","searchTerm": "python","postedAt": "2024-01-15T09:00:00+00:00","postedEpoch": 1705309200,"postedAgeDays": 12,"salary_min": 90000,"salary_max": 130000,"salaryMid": 110000,"hasSalary": true,"companyLogoUrl": "https://remoteok.com/assets/sample-logo.png","scrapedAt": "2024-01-27T10:00:00.000Z","matchedSearchTokens": ["python"],"matchedTagFilters": [],"raw": {}}
Plan Requirement
The actor reads RemoteOK's public job feed directly and needs no special proxy group. The current version sends its requests directly and does not apply the Connection (proxy) setting, so changing that field does not change how requests are routed. You can leave it at the default.
FAQ
How much does it cost?
You pay per job row saved, plus a flat run start fee. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.
Is it legal to scrape RemoteOK?
This actor collects only publicly available job listing data. You are responsible for how you use it: follow RemoteOK's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.
Why did a search term or URL return fewer jobs than I expected within a mixed run?
If an earlier search term or URL in the same run hits its Max items cap partway through a results page, every job scanned on that page (not only the saved ones) is treated as already handled for the rest of that run. A later search term or URL that would have matched some of those same jobs will skip them without saving them, and a Resurrect of that run keeps skipping them. A run resumed after an interruption can also miss the rest of the search term or URL it was saving when it stopped. This does not affect separate runs: Resume from a previous run only skips jobs actually found in the prior run's dataset. To avoid it, keep maxItems generous relative to how many jobs a single page can match, or avoid combining overlapping search terms or URLs with a tight cap in one run.
Can I get only new job listings on a schedule?
There is no built-in change-detection mode. For a recurring pull, pass the previous run's ID (or dataset ID) into Resume from a previous run: this run then skips every job id saved in that one run and appends the rest. It does not remember runs before that one, so a job saved two runs ago can be saved again. For a clean recurring feed, de-duplicate by id on your side. You also have to pass the latest run ID into the next run's input yourself.
Why did my run fail instead of returning an empty dataset?
The run stops with a clear error instead of silently returning nothing when no search terms or URLs are supplied, when Resume from a previous run points to a run or dataset your account can't read, or when a pasted URL is not a RemoteOK link. It also stops if any search term's or URL's request fails (for example a RemoteOK URL that does not exist, or a temporary error from the site): jobs saved before that point stay in the dataset, but the remaining search terms or URLs are not processed. Check the URLs, then run it again.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push saved jobs into Notion, Linear or Airtable.
🔗 Want more jobs data?
Pair this actor with these related scrapers from the same team:
| 💼 We Work Remotely Scraper Extract current remote job listings from weworkremotely.com. Use keyword search or WWR... | 💼 Gupy Jobs Scraper Scrape public jobs from Gupy.io by keyword, state, city, workplace type, job type... |
| 💼 Cwjobs UK From $1/1k. Scrape tech and IT jobs from CWJobs.co.uk into clean, structured data. Search... | 💼 Jobs.cz Scrape Czech job listings from Jobs.cz. Search builder (keyword, location, field, salary... |
| 💼 France Travail Scraper Scrape job offers from France Travail with titles, companies, locations, salaries... | 💼 Wellfound Jobs Scraper Scrape startup jobs from Wellfound.com by keyword, location, role, or remote status... |
💬 Support & custom scrapers
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email abotapi@proton.me or message Telegram @abotapi.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.