# Remote OK Jobs Feed - Remote Job Postings, Salaries & Tags (`jpopendata/remoteok-jobs`) Actor

Latest remote job postings from the public Remote OK API: title, company, tags, location, USD salary range, posted date, Remote OK job URL and apply link. Keyword, tag, location, salary and date filters. No job descriptions or contact data. Unofficial; not affiliated with Remote OK.

- **URL**: https://apify.com/jpopendata/remoteok-jobs.md
- **Developed by:** [JP Open Data](https://apify.com/jpopendata) (community)
- **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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Remote OK Jobs Feed — Remote Job Postings, Salaries & Tags

Get the latest remote job postings from the **public Remote OK API** as clean, flat rows: job title, company, tags, location, USD salary range, posted date, the job's Remote OK URL and the apply link. Filter by keyword, tags, location, minimum salary and posting age. One run takes a few seconds.

> **If you publish or display this data, link back to Remote OK.** Remote OK's API terms ask you to link to each job's URL on Remote OK with a normal, followed link (no `rel="nofollow"`) and to name **Remote OK** as the source; otherwise they may suspend API access. Every row gives you the job's Remote OK URL (`url`) and the source name (`source`) for this. Do not use the Remote OK logo without written permission.

**Unofficial tool — not affiliated with, endorsed or sponsored by Remote OK.** Job descriptions and contact details are not included (see below).

### What you get

- **One row per job posting**, newest first, with the fields buyers ask for: `title`, `company`, `location`, `tags`, `salaryMinUsd` / `salaryMaxUsd`, `postedAt`, `url` (the job on Remote OK), `applyUrl`, `companyLogoUrl`.
- **Clean text**: text that the feed delivers garbled (for example "Ciudad de MÃ©xico" or "fÃ¼r") is repaired to "Ciudad de México" / "für"; HTML entities and stray spaces are removed; `0` salaries become `null`.
- **Filters done for you**: keyword (title, company and tags), tags (any of), location text, minimum yearly salary, posted within N days.
- **Attribution in every row**: `source` = "Remote OK", `url` / `sourceUrl` = the job's Remote OK page, and `license` = Remote OK's API terms word for word, so every export carries them.
- **No job descriptions, no e-mail addresses, no phone numbers** (privacy and copyright — see "Legal and privacy").

### Quick start — verified input

```json
{
  "maxItems": 50
}
```

Returns the 50 newest postings, in about 5 seconds. An empty input `{}` does the same.

More examples:

```json
{ "keyword": "engineer", "maxItems": 0 }
```

Every current posting with "engineer" in the title, company or tags.

```json
{ "tags": ["dev", "golang", "python"], "postedWithinDays": 7 }
```

Developer postings tagged dev, golang or python from the last 7 days.

```json
{ "location": "United States", "minSalaryUsd": 100000 }
```

Postings located in the United States that state a salary range reaching 100,000 USD a year.

### Input

| Field | What it does |
|---|---|
| `keyword` | Words that must all appear in the title, company name or tags, e.g. `react`, `senior designer`. Case, accents and hyphens are ignored; a word also matches inside longer words. |
| `tags` | Keep postings with at least one of these Remote OK tags, e.g. `dev`, `design`, `marketing`, `python`, `golang`, `customer support`. Whole tags only. |
| `location` | Text that must appear in the location the employer gave, e.g. `United States`, `Germany`, `London`. |
| `minSalaryUsd` | Keep postings whose salary range reaches at least this yearly amount in US dollars, e.g. `100000`. `0` = no filter. |
| `postedWithinDays` | Keep postings published in the last N days. `0` = no limit. |
| `maxItems` | Most rows to return, newest first (default `50`; `0` = every matching posting). |

All fields are optional and combine with AND.

### Output example (one row)

Illustrative values (not a real posting):

```json
{
  "id": "1000001",
  "title": "Senior Backend Engineer",
  "company": "Example Co",
  "location": "Remote",
  "tags": ["backend", "senior", "engineer"],
  "salaryMinUsd": 90000,
  "salaryMaxUsd": 130000,
  "postedAt": "2026-10-13T08:00:00.000Z",
  "url": "https://remoteok.com/remote-jobs/remote-senior-backend-engineer-example-co-1000001",
  "applyUrl": "https://remoteok.com/remote-jobs/remote-senior-backend-engineer-example-co-1000001",
  "companyLogoUrl": null,
  "contactRemoved": false,
  "source": "Remote OK",
  "sourceUrl": "https://remoteok.com/remote-jobs/remote-senior-backend-engineer-example-co-1000001",
  "license": "Source: Remote OK (https://remoteok.com/). Remote OK API terms, as sent with the data: \"API Terms of Service: Please link back (with follow, and without nofollow!) to the URL on Remote OK and mention Remote OK as a source, ...\" ...",
  "retrievedAt": "2026-10-14T11:00:00.000Z"
}
```

Field notes:

- `url` is the job's page on Remote OK — the page the API terms ask you to link to. `sourceUrl` is the same link.
- `applyUrl` is the apply link from the feed (only `http`/`https` links). In practice the feed currently gives the Remote OK job page here too.
- `salaryMinUsd` / `salaryMaxUsd` are as the employer entered them (yearly USD in most cases; a few employers enter an hourly rate such as `40`–`48`). About 1 posting in 5 states a salary.
- `location` is the employer's own text (a country, a city, "Remote - US" ...). About 4 in 10 postings give none (`null`).
- `companyLogoUrl` is a link to the hiring company's logo image when the feed has one (often `null`). The logos belong to the companies; only the link is passed on. The Remote OK logo is never output.
- `contactRemoved` is `true` when an e-mail address or phone number was cut out of the row (shown as "\[contact removed]"), or a link was left out because it was one.
- `postedAt` is UTC (ISO 8601).

### Common input mistakes

| You wrote | Write instead |
|---|---|
| `"location": "Remote"` | Leave `location` empty — every Remote OK job is remote. `"Remote"` only keeps postings whose location text says "Remote". |
| `"tags": ["go"]` | `"tags": ["golang"]` (whole tags only) or `"keyword": "golang"` |
| `"minSalaryUsd": 100` (meaning 100k) | `"minSalaryUsd": 100000` |
| `"location": ["US", "Canada"]` | One location per run: `"location": "United States"`, then `"location": "Canada"` |
| `"keyword": "react, node"` (meaning either) | `"tags": ["react", "node"]` — keyword words must all match |

### Empty results?

A run that finds nothing **succeeds with 0 rows** and a warning. Typical causes:

- the feed holds only the **latest ~100 postings** (no history), so a narrow keyword may simply have no current posting;
- a location written differently from the postings (try the country name, e.g. `Germany`; many postings give no location at all);
- a high `minSalaryUsd` (most postings do not state a salary);
- a short `postedWithinDays`.

### How it works, speed and limits

- One run sends **two requests** to remoteok.com: `robots.txt` (checked before anything else) and the public API `https://remoteok.com/api`, at least 1.5 seconds apart (robots.txt asks for a 1-second crawl delay). The API returns the latest ~100 postings in one response; there is no paging and no history.
- **Want a history?** Schedule the Actor (for example every few hours) and keep the rows; `id` identifies a posting across runs.
- If Remote OK answers "too many requests" (HTTP 429), the Actor waits as asked (or 30 seconds) and tries **once** more. If the site refuses the connection (HTTP 403), the run stops with a clear message — the Actor does not try to get around it.
- Each run stores `RUN_SUMMARY` (postings in the feed, matches, rows, the date range the feed covers, the robots.txt check) and `LICENSE` (Remote OK's API terms as sent with the data) in its key-value store.

### Data source, attribution and terms

- **Source**: the public Remote OK API, `https://remoteok.com/api`. Remote OK's `llms.txt` points automated clients to it: "Structured, machine-readable job data is available at the public API".

- **Remote OK's API terms**, sent with every API response and copied into every row's `license` field:

  > API Terms of Service: 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.
  >
  > Please don't use the Remote OK logo without written permission as it's a registered trademark, please DO use our name Remote OK though.

- **Remote OK Terms of Service** (https://remoteok.com/legal): "You agree to link back with a web hyperlink or in-app hyperlink to our site on the page or app screen where you use the data from our APIs or site."

- **What you need to do:** If you publish or display this data, link to each job's Remote OK URL (the url field) with a normal, followed link (no rel="nofollow") and name Remote OK as the source. Do not use the Remote OK logo without written permission.

- If the API's terms text ever changes, the rows carry the new text and the run log and `RUN_SUMMARY.apiTermsChanged` say so.

### Legal and privacy

- **Public data only.** The Actor reads Remote OK's public API, which Remote OK offers for this purpose, and follows robots.txt. No login, no web pages scraped.
- **Unofficial / not affiliated** with Remote OK. "Remote OK" names the data source only.
- **You are responsible** for using the data in line with Remote OK's terms (above) and the laws that apply to you.
- **GDPR / personal data**: job postings are business information published by the hiring companies. The Actor does **not** output the job description (it is written by the employer, and it often contains recruiters' personal e-mail addresses and phone numbers). E-mail addresses, phone numbers and contact links are also cut out of the title, company, location and tags, and an apply link that is an e-mail address or a messaging link is left out. There are no fields for people's names. A posting whose own Remote OK URL contains a phone number is left out.
- Remote OK provides its data "as is" and states that it takes no responsibility for the accuracy of postings; check important details on the job's Remote OK page.

# Actor input Schema

## `keyword` (type: `string`):

Optional. Words that must all appear in the job title, the company name or the tags, e.g. "react", "senior designer" or "python django". Case, accents and hyphens are ignored; a word also matches inside longer words ("design" finds "designer").

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

Optional. Keep postings that have at least one of these Remote OK tags, e.g. "dev", "design", "marketing", "python", "golang", "customer support", "sales", "finance". Whole tags only (case and hyphens ignored).

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

Optional. Text that must appear in the location the employer gave, e.g. "United States", "Germany", "London" or "Mexico". About 4 in 10 postings give no location; they are left out when this is set. Every Remote OK job is remote, so leave it empty for all postings.

## `minSalaryUsd` (type: `integer`):

Optional. Keep postings whose salary range reaches at least this yearly amount in US dollars, e.g. 100000. 0 = no filter. Only about 1 posting in 5 states a salary; the others are left out when this is above 0.

## `postedWithinDays` (type: `integer`):

Optional. Keep postings published in the last N days, e.g. 7. 0 = no limit (the feed usually reaches back several weeks).

## `maxItems` (type: `integer`):

Most rows to return, newest first. Default 50. 0 = every matching posting (the feed has about 100).

## Actor input object example

```json
{
  "keyword": "",
  "tags": [],
  "location": "",
  "minSalaryUsd": 0,
  "postedWithinDays": 0,
  "maxItems": 50
}
```

# Actor output Schema

## `postings` (type: `string`):

One row per Remote OK job posting (no job description), each with source "Remote OK", the job's Remote OK URL and the API terms (source, sourceUrl, license, retrievedAt).

# 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 = {
    "keyword": "",
    "tags": [],
    "location": "",
    "minSalaryUsd": 0,
    "postedWithinDays": 0,
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("jpopendata/remoteok-jobs").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 = {
    "keyword": "",
    "tags": [],
    "location": "",
    "minSalaryUsd": 0,
    "postedWithinDays": 0,
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("jpopendata/remoteok-jobs").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 '{
  "keyword": "",
  "tags": [],
  "location": "",
  "minSalaryUsd": 0,
  "postedWithinDays": 0,
  "maxItems": 50
}' |
apify call jpopendata/remoteok-jobs --silent --output-dataset

```

## MCP server setup

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

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/mRCPR1gl5uWXDvgnc/builds/jMDt9x9dpjp97yuYG/openapi.json
