# Remotive Jobs Scraper: Remote Job Listings from the Website (`mrbridge/remotive-jobs-scraper`) Actor

Scrape remote job listings from the Remotive website by title words, category, location, job type and date. One row per job with company, salary, listing type (curated or aggregated), expiry date, canonical URL and description with links kept. Export to JSON, CSV or Excel.

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

## Pricing

from $0.70 / 1,000 job listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Remotive Jobs Scraper

Remotive Jobs Scraper collects remote job listings from the public website https://remotive.com. It reads the job sitemaps and one page per offer, so you get one clean row per job, ready for JSON, CSV or Excel. Filter by words of the title, category, location, job type and age.

### What is Remotive Jobs Scraper?

Remotive is a remote job board. This Actor reads the public pages of the site (the sitemaps that list the offers, then the page of each offer), not the Remotive API, which now serves only a fixed sample of 10 offers. No API key and no login are needed.

Each run reads the sitemaps, keeps the newest offers that pass the filters on their URL, then opens the page of each offer, newest first, until `maxItems` jobs are returned. The pages are fetched slowly (about one request per second), so a large run takes time: expect roughly 50 minutes for 3,000 jobs.

#### What data can I extract with it?

One row per job: source, job id, title, company, company logo, candidate location, job type, category, salary when the page publishes one (amounts, currency and period), publication time, canonical Remotive URL, description as text and as HTML, scrape time. Four fields come from the site itself:

- `listingType`: `curated` for an offer that Remotive publishes, `aggregated` for an offer that Remotive takes from a third party.
- `expiresAt`: the date the offer expires (`validThrough`). The site keeps serving some offers after that date, and such an offer is still returned. The `pastValidThrough` counter of the `OUTPUT` record tells how many.
- `applicantCountries`: the ISO country codes the offer accepts applicants from.
- `companyWebsite`: the website of the hiring company when the page gives it.

Good to know:

- The pages carry no application link and no tags, so `tags` is always empty. There is no seniority level either, so `level` is empty.
- Offers that no longer exist (the site answers 404 or 410) are skipped. They are counted in `expired`, never returned and never billed.
- Descriptions are included by default. E-mail addresses and phone numbers are masked, links are kept. A link that carries an e-mail address is masked too, and `mailto:` and `tel:` links are replaced by their text. Set `includeDescription` to `false` to leave the descriptions out.

### Input

| Field | Meaning | Default |
|---|---|---|
| `searchTerms` | Up to 10 terms, 50 bytes at most. A job is kept when one term appears in its title | empty (all) |
| `categories` | Remotive categories to keep (30 categories), compared with the category of the offer URL | empty (all) |
| `locations` | Keep jobs whose location contains one of these texts, or whose applicant countries include one of these ISO codes. Aggregated offers only list codes, so type `de`, not `germany` | empty (all) |
| `tags` | The site publishes no tags, so any entry keeps no job | empty |
| `jobTypes` | Keep jobs with one of these types, for example `full-time` (case does not matter) | empty (all) |
| `postedWithinDays` | Keep jobs published in the last N days (1 to 365) | empty |
| `maxItems` | Hard cap on jobs returned and billed, 1 to 3000 | 100 |
| `includeDescription` | Add the description as text and HTML | `true` |

The Console starts with one category and 10 items so that a first run is short.

Example input:

```json
{
  "categories": [
    "software-development"
  ],
  "maxItems": 10
}
```

Invalid input (an empty or too long term, too many terms, an unknown category) ends the run successfully with one `BAD_INPUT` row in the `errors` dataset and no job.

### Output

Jobs go to the default dataset. Problems go to a separate `errors` dataset and are never billed. The `OUTPUT` record of the key-value store summarizes the run: requests, sitemaps read, candidates, jobs returned and billed, errors by kind, why the run stopped, a coverage note and the attribution to give to Remotive.

#### Partial runs

A run can end before it has seen everything. Read these fields of `OUTPUT`:

- `stoppedBy` tells why a run stopped with work left. It is `null` when the run covered its input. Other values: `maxItems`, `chargeLimit`, `deadline`, `circuitOpen` (three blocked answers in a row), `badInput` and `fetchFailed`.
- `fetchFailed` means that no sitemap could be read because the fetch failed, so the run found no offer. A sitemap that failed while others were read does not set it.
- `walksFailed` counts the sitemaps (the sitemap index included) that could not be fetched, and `failedWalks` lists the first 20 as `{ walk, kind, httpStatus }`, where `walk` is the URL of the sitemap and `httpStatus` is present only for an unexpected answer. The run goes on with the next sitemap, and the `coverageNote` says how many planned sitemaps could not be read.
- A job page that fails to load gives one row in the `errors` dataset and the run goes on. It is not a failed walk.
- A non-empty `errorsByKind` does not always mean that the run stopped. A `LISTING_INVALID` row skips one page without a readable offer and stops nothing.
- The run keeps the newest `maxItems x 2 + 50` offer URLs that pass the URL filters. When they are all used before `maxItems` is reached, the `coverageNote` warns that older matching offers may exist.

A request that gets a 429 answer is not repeated, and the run waits for the `Retry-After` before the next request. A network error, a timeout, a truncated answer or a server error (5xx) is retried once.

### Billing

One event is billed per delivered job, plus a start event per run. Errors and expired offers are never billed. The Pricing tab of the Actor shows the current prices.

When the platform moves a run to another server or restarts it while it works, the run picks up where it was: a job already returned is neither returned nor billed again, and the `OUTPUT` counters cover the whole run. The run checks the rows already in its dataset when it starts again, so this also holds after a restart without warning.

### Data source and terms

The data comes from Remotive (https://remotive.com), read from its public website: the sitemaps and the offer pages, not the API. Each row keeps the canonical Remotive URL of the job. Link back to that page and name Remotive as the source when you republish the data.

Please read this before you rely on the output:

- Some offers are `aggregated`: Remotive took them from third parties. The content belongs to the original publishers.
- Remotive has terms of use. They ask not to submit Remotive jobs to third party job sites and not to use the jobs to collect sign-ups or e-mail addresses. They also point to a paid API for full access. The Actor reads the public website, which is not what that paid offer provides.
- Remotive has not given its authorization for this Actor. We did not obtain it.
- You remain responsible for how you use the results and for complying with the source's terms and with the laws that apply to you, in particular for commercial use.

### Frequently asked questions

#### Can I use Remotive Jobs Scraper via MCP?

Yes. Any Apify Actor can be called from an MCP client through the Apify MCP server. See the [Apify MCP documentation](https://docs.apify.com/platform/integrations/mcp) for the setup. For a ready-made MCP server over several remote job boards, see [mrbridge/remote-jobs-mcp](https://apify.com/mrbridge/remote-jobs-mcp).

#### Your feedback

We're always working on improving the performance of our Actors. So if you've got any technical feedback for Remotive Jobs Scraper or simply found a bug, please create an issue on the Actor's [Issues tab](https://apify.com/mrbridge/remotive-jobs-scraper/issues).

More free tools, scrapers, MCP servers and data studies from the same team: [mr-bridge.com](https://mr-bridge.com).

`OUTPUT` has a field `pagesRereadAfterResume`: the job pages fetched a second time because the run was moved or restarted while it was applying them, usually 0 and a few pages at most per restart. These pages are also counted in `pagesFetched`.

Changes in 0.1.5: `OUTPUT` has the new field `pagesRereadAfterResume`. Input and rows are unchanged.

Changes in 0.1.2: the source is now the public website instead of the job feed. `OUTPUT` has the new fields `walksFailed` and `failedWalks` and the new `stoppedBy` value `fetchFailed`. The dataset has the four fields `listingType`, `expiresAt`, `applicantCountries` and `companyWebsite`. Links in descriptions are no longer masked.

# Changelog

This Actor's version history is a separate document: https://apify.com/mrbridge/remotive-jobs-scraper/changelog.md

# Actor input Schema

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

Up to 10 terms, 50 bytes at most each. Lowercased and deduplicated. A job is kept when one term appears in its title (the job description is not searched). Leave empty to keep every job.

## `categories` (type: `array`):

Remotive categories to keep, matched on the category of the job page address. Leave empty for every category.

## `locations` (type: `array`):

Keep only jobs that match one of these entries (case-insensitive). A place name such as usa or europe is searched in the location text of the offers Remotive publishes itself. A two-letter country code such as de or us is matched against the countries an offer accepts, which is the only location the offers Remotive aggregates from other sites give (a Worldwide offer accepts them all). Leave empty to keep all.

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

Keep only jobs with one of these tags (exact match, case-insensitive). The job pages of remotive.com publish no tags, so any entry here keeps no job: leave it empty and use search terms instead.

## `jobTypes` (type: `array`):

Keep only jobs with one of these types: full-time, part-time, contract, freelance or internship (case-insensitive, full_time and full-time are the same). Leave empty to keep all.

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

Keep only jobs published in the last N days (1 to 365).

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

Hard cap on the number of jobs returned (and billed) in one run, 3000 at most. Each job costs one page request, so a large cap makes a long run.

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

Add the job description as text and as HTML. E-mail addresses and phone numbers are masked, links are kept.

## Actor input object example

```json
{
  "searchTerms": [],
  "categories": [
    "software-development"
  ],
  "locations": [],
  "tags": [],
  "jobTypes": [],
  "maxItems": 10,
  "includeDescription": true
}
```

# Actor output Schema

## `jobs` (type: `string`):

Direct link to all dataset items produced by the run.

## `summary` (type: `string`):

Direct link to the OUTPUT record: requests made, jobs read, filtered out, returned and billed, errors by kind, the reason of an early stop and the Remotive attribution.

# 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 = {
    "categories": [
        "software-development"
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("mrbridge/remotive-jobs-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 = {
    "categories": ["software-development"],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("mrbridge/remotive-jobs-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 '{
  "categories": [
    "software-development"
  ],
  "maxItems": 10
}' |
apify call mrbridge/remotive-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mrbridge/remotive-jobs-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/A6YBU9TCJYeY8bHTc/builds/mX51vUtneUAzako5t/openapi.json
