# RemoteOK Job Scraper — Salaries, Contacts & Monitoring (`corvuslab/remoteok-scraper`) Actor

Scrape remote jobs from RemoteOK: title, company, logo, tags, USD salary range, full description (text/HTML/Markdown), apply URL, external apply links and contact emails. Schedule incremental runs for a NEW/UPDATED/EXPIRED change feed with Telegram, Slack, Discord or webhook alerts.

- **URL**: https://apify.com/corvuslab/remoteok-scraper.md
- **Developed by:** [Corvuslab](https://apify.com/corvuslab) (community)
- **Categories:** Lead generation, Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## RemoteOK Job Scraper — Salaries, Contacts & Monitoring

### 🌍 What does the RemoteOK Job Scraper do?

> **Turn the entire RemoteOK board into clean, structured job data — and get pinged the moment anything changes.**

Scrape remote jobs from **RemoteOK (remoteok.com)** — the whole ~1,000-job catalogue or a filtered slice — into ready-to-use records with **title, company, company logo, location, tags, a USD salary range, the full description (as text, HTML and Markdown), the apply URL, external apply links, and extracted contact emails**. Schedule it with **incremental monitoring** and each run returns only what changed (NEW / UPDATED / UNCHANGED / EXPIRED), with instant alerts to **Telegram, Slack, Discord or any webhook**. No code required — export to JSON, CSV, Excel or the API, or wire it straight into an AI agent.

**Why this scraper**

- ⚡ **Fast & low-cost** — reads the public board in one direct pass, no proxy needed at typical volumes, so large runs stay cheap.
- 🧾 **Rich, typed records** — 25+ structured fields per job, not raw HTML.
- ♻️ **Cheap to monitor** — incremental mode returns only what changed since the last run (see below).
- 🔔 **Notifications built in** — Telegram, Slack, Discord or any webhook (n8n / Make / Zapier).
- 🤖 **AI- & API-ready** — compact + drop-empty output modes, MCP-friendly, one-click integrations.

***

### ✨ Key features

- 🔎 **Search or filter the whole board** — keyword search across title, company, tags and location, or pull every newest remote job with no filters at all.
- 🧾 **Structured records** — title, company, company logo, location, tags, salary min/max (USD, when published), apply URL, external apply links and posting date.
- 🎚️ **Rich filters** — search terms, 60+ role/skill tags, location, minimum salary and a max-results cap, all applied per job.
- 🧠 **Full descriptions, three ways** — every posting as plain text, HTML **and** Markdown, so it drops cleanly into a database, a page or an LLM prompt.
- 📧 **Contacts & apply links** — automatically extracts contact emails and external "apply" links from each description — outreach-ready.
- ♻️ **Incremental monitoring** — schedule it and get only what changed (NEW / UPDATED / UNCHANGED / EXPIRED); unchanged jobs are skipped so a daily watch costs a fraction of a full re-scrape.
- 🔁 **Repost detection** — flags jobs that are re-listings of an earlier posting, so your feed stays clean.
- 🔔 **Notifications** — Telegram, Slack, Discord or any webhook, with a configurable number of jobs per message.
- 🤖 **AI-ready** — compact mode and drop-empty-fields mode keep payloads small for LLMs and MCP clients.

***

### 📤 Example output

```json
{
  "id": "1135735",
  "url": "https://remoteok.com/remote-jobs/remote-genai-engineer-ai-builder-ai-agent-associate-specialist-magic-compass-limited-1135735",
  "applyUrl": "https://remoteok.com/remote-jobs/remote-genai-engineer-ai-builder-ai-agent-associate-specialist-magic-compass-limited-1135735",
  "title": "GenAI Engineer AI Builder AI Agent Associate Specialist",
  "company": "Magic Compass Limited",
  "companyLogo": "https://remoteok.com/resize-cgi/image/format=auto,fit=contain,width=100,height=100,quality=50/https://r2.remoteok.com/jobs/c74250b761dbc89146a63c5f05ca2d7f1785487608.jpg",
  "location": "Worldwide",
  "isRemote": true,
  "tags": ["AI", "Machine Learning", "Artificial Intelligence", "Prompt Engineering", "Crypto", "Web3"],
  "salaryMin": 10000,
  "salaryMax": 20000,
  "salaryCurrency": "USD",
  "salaryText": "10k - $20k",
  "publishedAt": "2026-07-31T08:46:48+00:00",
  "contactEmails": [],
  "applyLinks": ["https://rok.co/jc"],
  "description": "Magic Compass Limited is hiring a Remote GenAI Engineer / AI Builder / AI Agent Associate to design and build AI-powered workflows and AI Agents… (full text, HTML and Markdown all included)",
  "source": "remoteok.com",
  "scrapedAt": "2026-08-12T10:57:55+00:00"
}
```

The description is returned as **plain text, HTML and Markdown** (pick one or all three). `contactEmails` and `applyLinks` are populated whenever a posting exposes them.

### 📥 Example input

A few ready-to-run configurations — set these in the visual editor or pass them as JSON via the API:

```json
{ "maxResults": 100 }
```

```json
{ "searchTerms": ["python", "react"], "tags": ["senior"], "location": "Europe", "minSalary": 80000, "maxResults": 200 }
```

```json
{ "tags": ["ai", "machine learning"], "incrementalMode": true, "telegramChatId": "@myjobsfeed", "notificationLimit": 10 }
```

```json
{ "searchTerms": ["backend"], "includeDescription": true, "minSalary": 100000, "maxResults": 150 }
```

***

### 📚 What data can you extract?

- **Core job** — `title`, `company`, `companyLogo`, `location`, `isRemote`, `tags`, `url`, `applyUrl`, `publishedAt`.
- **Salary** — `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryText` (USD, present when the posting publishes it).
- **Description & contacts** (with full description on) — `description`, `descriptionHtml`, `descriptionMarkdown`, `contactEmails`, `applyLinks`.
- **Identifiers & provenance** — `id`, `sourceJobId`, `slug`, `source`, `scrapedAt`, `contentHash`.
- **Monitoring** (in incremental mode) — `changeType` (NEW / UPDATED / UNCHANGED / EXPIRED), `isRepost`, `repostOfId`, `repostDetectedAt`.

Every field is present in standard mode (missing values are `null`); **compact mode** returns the core fields only, for lean AI/MCP payloads.

***

### ⚙️ Input

Configure it in the visual editor — no code needed — or pass JSON via the API.

| Field | What it does |
|---|---|
| `searchTerms` | Keywords matched against title, company, tags or location (a job is kept if it matches any). |
| `tags` | Keep only jobs carrying at least one of these RemoteOK tags (60+ role/skill options). |
| `location` | Keep only jobs whose location contains this text (e.g. `Europe`, `US`, `Worldwide`). |
| `minSalary` | Keep only jobs with a published salary of at least this annual USD amount. |
| `maxResults` | Cap the number of jobs (0 = unlimited, bounded by the ~1,000-job board). |
| `includeDescription` | Fetch each job's full description plus contact emails and external apply links. |
| `incrementalMode` | Emit only what changed since the last run, tagged NEW / UPDATED / UNCHANGED / EXPIRED. |

…and **23 inputs** in total — the table shows the essentials; the rest cover start URLs, description format, compact/drop-empty output modes, notification channels (Telegram, Slack, Discord, webhook) and advanced tuning, all in the visual editor.

***

### 💡 Use cases

- **Recruiter & sourcing outreach** — pull fresh remote roles with company, salary, apply links and extracted contact emails, ready for your CRM.
- **Job boards & aggregators** — feed a clean, deduplicated remote-jobs catalogue into your own site or app.
- **Market & compensation research** — analyse remote salary ranges and in-demand tags across the whole board.
- **New-job alerts** — schedule incremental mode with notifications for a live feed of only the roles that just appeared.
- **Enrichment & pipelines** — drop structured job data straight into a sheet, warehouse or LLM workflow.
- **AI agents & MCP** — compact output plugs straight into LLM/MCP tooling.

***

### ♻️ Incremental monitoring — pay for changes, not repeats

Schedule the actor and turn on **incremental mode**: each run compares against the last and emits only **NEW / UPDATED / UNCHANGED / EXPIRED** records — unchanged jobs are skipped, so a daily watch costs a fraction of a full re-scrape. Pair it with a notification channel and every change lands in Telegram, Slack, Discord or your webhook automatically.

| Daily churn | of 1,000 tracked | billable records | you save |
|---|---|---|---|
| 5 % | 1,000 | 50 | **95 %** |
| 15 % | 1,000 | 150 | **85 %** |
| 30 % | 1,000 | 300 | **70 %** |

The first run seeds the baseline and bills in full; every run after that bills only the delta.

***

### 🚀 How to run it

1. Open the actor and add **search terms** and/or pick **tags**, a **location** or a **minimum salary** — or leave everything empty to pull the newest remote jobs across the whole board.
2. Set **Max results** and choose whether to include **full descriptions** (with contacts and apply links).
3. (Optional) Turn on **incremental mode** and a **notification** channel, then **Schedule** it.
4. Click **Start**.
5. Download the data as **JSON, CSV or Excel**, or pull it from the **API**.

New to Apify? Create a free account — it comes with monthly credit, no credit card required.

***

### 🔌 Integrations & export

Export to **JSON, CSV, Excel** or an HTML table, or pull from the **REST API** and the **JavaScript / Python** clients. Runs on a **schedule**, connects to **Google Sheets, Slack, Make, Zapier and n8n**, and works as an **MCP tool** for AI agents — compact mode keeps token usage small.

***

### ❓ FAQ

**Do I need a proxy or login?** No — it runs out of the box with no account or proxy; Apify Proxy is available under Advanced only if you hit occasional rate limits.

**Can I get only new jobs on a schedule?** Yes — turn on incremental mode and schedule it; each run emits only what changed and can notify your channel.

**Does it include salaries?** Yes, as `salaryMin` / `salaryMax` in USD plus the original salary text — whenever the posting publishes it (RemoteOK publishes salary on a minority of jobs).

**Can I get contact emails and apply links?** Yes — with full descriptions enabled, the actor extracts contact emails and external apply links from each posting.

**What formats can I export?** JSON, CSV, Excel, HTML table, or via the API.

**Is it good for AI agents?** Yes — enable compact mode; the output is MCP-friendly.

**How many jobs can I get?** Up to the full board (~1,000 jobs) — set `maxResults` (0 = unlimited).

**Is scraping this legal?** The actor collects only **publicly available** data. You are responsible for how you use it, including any personal data and GDPR-style obligations.

***

### ⚖️ Disclaimer

This actor accesses only publicly available data on RemoteOK. You are responsible for how you use the extracted data — in particular any personal information — and for complying with the site's terms and applicable law (including the GDPR where it applies). Not affiliated with, endorsed by, or sponsored by RemoteOK.

***

**Keywords:** remoteok scraper · remoteok.com scraper · remoteok api · remoteok job scraper · remote jobs scraper · remote work scraper · remote job board scraper · scrape remote jobs · remote developer jobs · remote job listings · job scraper · job feed api · salary data scraper · recruiting leads · new job alerts · job monitoring · export jobs to csv · no-code job scraper · mcp tool for ai agents

# Actor input Schema

## `searchTerms` (type: `array`):

Keywords to match against the job title, company, tags or location. A job is kept if it matches ANY term. Leave empty to match everything.

## `tags` (type: `array`):

Keep only jobs carrying at least one of these RemoteOK tags (case-insensitive). Any RemoteOK tag works — e.g. react, golang, kubernetes, machine learning, design, marketing, crypto.

## `location` (type: `string`):

Keep only jobs whose location text contains this value (e.g. "Europe", "US", "Worldwide").

## `minSalary` (type: `integer`):

Keep only jobs whose published salary reaches at least this annual USD amount. Jobs without a published salary are dropped when this is set.

## `maxResults` (type: `integer`):

Maximum number of jobs to return. Set 0 for unlimited (bounded by the size of the board, ~1,000 jobs).

## `startUrls` (type: `array`):

Optional. Paste RemoteOK tag pages (e.g. https://remoteok.com/remote-python-jobs) to add their tag as a filter.

## `includeDescription` (type: `boolean`):

Include each job's full description (plus extracted contact emails and external apply links). Turn off for the smallest, fastest output.

## `descriptionFormat` (type: `string`):

Which representation(s) of the description to include.

## `compact` (type: `boolean`):

Emit only the core fields (title, company, salary, tags, location, apply URL, date). Ideal for AI agents and MCP clients.

## `excludeEmptyFields` (type: `boolean`):

Remove null, empty-string and empty-array fields from each record.

## `incrementalMode` (type: `boolean`):

Track state between runs and tag every record with a changeType (NEW / UPDATED / UNCHANGED / EXPIRED).

## `stateKey` (type: `string`):

Stable name for the tracked search. Leave empty to derive one automatically from your filters.

## `emitUnchanged` (type: `boolean`):

Also emit jobs that have not changed since the previous run.

## `emitExpired` (type: `boolean`):

Emit records for jobs present last run but gone now (marked EXPIRED).

## `telegramToken` (type: `string`):

Bot token from @BotFather.

## `telegramChatId` (type: `string`):

Chat or channel ID, e.g. "-100123456789" or "@yourchannel".

## `slackWebhookUrl` (type: `string`):

Slack incoming-webhook URL.

## `discordWebhookUrl` (type: `string`):

Discord incoming-webhook URL.

## `webhookUrl` (type: `string`):

Any HTTPS endpoint. Receives a JSON POST with the matched jobs — works with n8n, Make and Zapier.

## `webhookHeaders` (type: `object`):

Extra headers for the webhook request, e.g. {"Authorization": "Bearer xyz"}.

## `notificationLimit` (type: `integer`):

How many jobs to include in each notification message.

## `proxyConfiguration` (type: `object`):

Optional. RemoteOK works without a proxy, so this is off by default. Enable Apify Proxy only if you hit occasional rate limits.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before giving up on it.

## Actor input object example

```json
{
  "searchTerms": [
    "python",
    "react"
  ],
  "tags": [
    "react",
    "golang"
  ],
  "maxResults": 100,
  "includeDescription": true,
  "descriptionFormat": "all",
  "compact": false,
  "excludeEmptyFields": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "notificationLimit": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxRequestRetries": 3
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `allItems` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [],
    "tags": [],
    "location": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("corvuslab/remoteok-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchTerms": [],
    "tags": [],
    "location": "",
}

# Run the Actor and wait for it to finish
run = client.actor("corvuslab/remoteok-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [],
  "tags": [],
  "location": ""
}' |
apify call corvuslab/remoteok-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,corvuslab/remoteok-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/V6iXWZqOPvVzT6hYt/builds/5WByCLGoquwDua5CA/openapi.json
