# Remote Jobs Scraper: Jobicy & We Work Remotely API (`adastra-connect/remote-jobs-aggregator`) Actor

Aggregate remote jobs from Jobicy and We Work Remotely with salary data, source attribution, deduplication and alerts for new jobs.

- **URL**: https://apify.com/adastra-connect/remote-jobs-aggregator.md
- **Developed by:** [Ad Astra Connect](https://apify.com/adastra-connect) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 jobs

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

## Remote Jobs Scraper API

Collect remote job postings from Jobicy and We Work Remotely in one normalized dataset. This remote jobs scraper API combines public JSON and RSS feeds, removes matching employer/title duplicates across boards, keeps source attribution and original apply links, and supports scheduled new job alerts. Export jobs for a career dashboard, research workflow or remote job discovery service.

### Remote job boards and coverage

| Source | Public interface | Refresh policy |
| --- | --- | --- |
| Jobicy | Official public jobs API | At most one synchronization pass per hour; results are delayed by approximately three hours and limited to a seven-day publication window |
| We Work Remotely | Official public RSS feed | Successful results cached for one hour; failed passes recover after five minutes; recent feed items only |

Himalayas, Remotive and Arbeitnow are excluded because their current terms or robots rules restrict this use. They cannot be enabled through input. No API keys or accounts are needed for the supported feeds.

### What data can you get from the remote jobs API?

| Field | Contents |
| --- | --- |
| `id`, `source` | Source-prefixed posting identifier and board name |
| `title`, `company`, `companyUrl` | Role, employer and public company homepage when supplied |
| `category`, `tags`, `jobType` | Source categories, tags and employment type |
| `remoteRegion`, `locationRestrictions` | Published remote eligibility, without assuming every remote role is worldwide |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval` | Posted salary only; missing or ambiguous values are null |
| `postedAt` | Publication date in UTC when available |
| `applyUrl`, `sourceUrl`, `attribution` | Original board posting links and source credit |
| `firstSeenAt` | First emitted time for a new posting in monitor mode |
| `description` | Optional plain text, privacy-redacted and truncated to 3,000 characters |

Recruiter, author and applicant fields are excluded. Descriptions are off by default. Automated name redaction checks titles, categories, locations, descriptions and URL paths, but can miss unfamiliar names or remove legitimate words. Public employer names are preserved; email addresses and phone numbers are still scrubbed from employer labels.

### How to use the remote jobs scraper

1. Keep the two verified sources selected for a quick test.
2. Set `maxResults` to 30, or use the small prefilled input.
3. Add keywords, categories or a remote region if needed.
4. Run the Actor and open the dataset's Remote jobs overview.
5. Download JSON, CSV or Excel, or read the dataset through the Apify API. Check `RUN_SUMMARY` for cache age, skipped sources and coverage limits.

```json
{
  "sources": ["jobicy", "weworkremotely"],
  "keywords": [],
  "excludeKeywords": [],
  "categories": [],
  "remoteRegion": "",
  "minSalary": 0,
  "postedWithinDays": 0,
  "maxResults": 30,
  "monitorMode": false,
  "includeDescription": false
}
```

`maxResults` defaults to 100. Include keywords use OR within the list; exclusions take priority. Keywords search titles, employer labels, categories and tags, not descriptions. Category matching is case insensitive and uses source labels or tags.

A `remoteRegion` of `worldwide` requires explicit worldwide eligibility. `usa` and `europe` match recognized eligibility labels, and explicitly worldwide roles also match country searches. Geography is a text filter, not a work authorization determination.

`minSalary` compares the posted lower bound in its original currency and interval. It does not convert hourly pay to annual pay or currencies. Missing lower bounds fail an enabled salary filter. `postedWithinDays` excludes unknown dates when enabled.

### Remote job sample output

These are selected fields from a real posting. Full rows also retain the original posting URLs and required attribution.

| Source | Title | Company | Remote region | Job type |
| --- | --- | --- | --- | --- |
| Jobicy | Adoption Strategist - APAC | Elevenlabs | Singapore | Full-Time |

```json
{
  "id": "jobicy:156604",
  "source": "jobicy",
  "title": "Adoption Strategist - APAC",
  "company": "Elevenlabs",
  "companyUrl": null,
  "category": ["Customer Support & Success"],
  "tags": [],
  "jobType": "Full-Time",
  "remoteRegion": "Singapore",
  "locationRestrictions": ["Singapore"],
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryInterval": null,
  "postedAt": "2026-10-23T13:48:16.000Z"
}
```

### Pricing

Planned price: **$1.00 per 1,000 jobs**. The final price is shown on the Store page. Each dataset row uses one `job` billing event, plus the platform `apify-actor-start` event per run where configured. The startup charge counts toward the total spending limit. No pricing is configured by this source project. Cross-board duplicates removed before output and monitor rows suppressed as already seen do not create billable rows. Ordinary repeated exports can emit and bill the same jobs again. The Actor stops gracefully when the spending limit is reached.

### Incremental runs / monitoring for new job alerts

Enable `monitorMode` and reuse the same `monitorKey` with the same filters. The first run emits current matching jobs up to `maxResults`. Later runs emit only matching employer/title combinations not previously emitted in that monitor. Rows include `firstSeenAt`. When a cap prevents output, remaining jobs can appear on a later run; they are not silently marked seen.

State lives in the named key-value store `remote-jobs-aggregator-state`, separate from the default dataset. A new `monitorKey`, a filter change or deleting this named store starts a fresh monitor. Changing caps, source selection or description options preserves the existing monitor scope. Do not purge the named store between scheduled runs.

Cross-board deduplication uses normalized employer and title. The first source wins and retains its own attribution. A changed source identifier or edited description does not trigger an alert for the same employer/title. Roles with the same employer/title can be merged even when their locations differ. This detects newly discovered roles, not edits, closures or individual requisitions with identical titles. State is retained until you reset it.

### Scheduling and integrations

Use Apify schedules for hourly or daily checks. Run sequentially and prevent overlapping runs that share the named cache or monitor store. Connect datasets and webhooks to Make, Zapier or n8n, use the Apify API from your application, or connect MCP tools for AI agents. This Actor produces job rows; it does not send messages itself. Build alert delivery in your chosen integration.

### Is it legal?

This is not legal advice.

The Actor uses official public feeds without login, cookies, proxies or browsers, checks robots.txt, and preserves source credit. Users are responsible for their use and any downstream publication. Keep visible source attribution and a link to the original board posting whenever you display a result. Apply buttons must use the returned `applyUrl`, including Jobicy's original board link. No personal profiles or application data are collected. Do not use job descriptions as a contact directory.

### Remote jobs API limitations

- Results are a bounded recent window, not every remote vacancy. Jobicy defaults to one page; We Work Remotely has one RSS feed. `windowCapped` in `RUN_SUMMARY` indicates additional items may exist.
- `maxPagesPerSource` can increase a Jobicy pass to five sequential cursor pages. Default one keeps startup quick. Host requests are spaced by at least 750 ms or the robots crawl delay, whichever is longer.
- An hourly source cache is shared across filters and monitor names. Changing `maxResults` or page depth cannot expand a cached window until its next refresh. A failed refresh uses a five-minute cooldown. Jobicy also retains its hourly limit on starting a new pass, including after a failure or crash. WWR reservations abandoned by a crashed run expire after five minutes; legacy reservations retain their original expiry. Runs finding a parallel refresh wait up to 10 seconds for its result, then report that source as `cooldown`.
- Each source gets at most one retry total across robots checks and feed pages for network errors, timeouts, HTTP 429 or HTTP 5xx, with a three-second backoff or Retry-After capped at 10 seconds. HTTPS requests use a fixed source host allowlist, 20-second request timeouts, 8 MiB feed bodies and 512 KiB policy bodies. A 45-second collection budget also bounds normal runs.
- Shared KV reservations provide best effort coordination, without atomic locking. After reserving, a run waits a random 200 to 600 ms and re-reads ownership before fetching; a run that loses ownership waits for the other result. Configure overlapping schedules and tasks to run sequentially to avoid duplicate source polling.
- The cache contains bounded, normalized, redacted rows, including descriptions. Raw API bodies are not persisted. `includeDescription` only controls dataset output.
- Sources can delay, remove or change listings. Hourly polling can miss jobs that appear and disappear between checks. Missing salary, homepage, tags or publication dates remain empty or null.
- The Actor divides the output budget across the sources in input order. Filters, duplicates or sparse feeds can produce fewer rows than the cap.
- Redaction is best effort and may produce false positives in prose. Employer names intentionally skip person-name detection; contacts are still scrubbed. Keep descriptions disabled for the smallest output and review any prose before publishing it.

### FAQ

When all sources are unavailable, the run returns 0 rows with no job charge; only the configured Actor start charge may apply. Check the status message and `RUN_SUMMARY` for the next refresh time. Jobicy can require waiting until the next hour. Parsing and normalization errors are highlighted in the status message and can require a code fix.

#### Can I scrape Himalayas, Remotive or Arbeitnow too?

No. Those candidates were dropped during terms and robots verification. Only Jobicy and We Work Remotely are accepted.

#### Why did a repeat run return zero jobs?

In monitor mode, all current matching postings may already be seen. Filters can also remove every candidate. Zero matches and temporarily unavailable sources return a successful run. Inspect `RUN_SUMMARY`.

#### Why are some salaries null?

Many listings do not disclose pay. A currency symbol alone may be ambiguous, and the Actor does not infer salary or exchange rates. Intervals reflect the source posting.

#### Can I get the full description or a direct employer apply link?

Set `includeDescription` for a redacted excerpt up to 3,000 characters. Apply links stay on the original job board to preserve source requirements. The Actor does not bypass those links or request authenticated APIs.

#### Can I reset the source polling limit by changing my monitor name?

No. The polling cache is shared across monitor names. Its next refresh time is recorded in each source summary. Keep scheduled runs sequential.

### Support

Report reproducible problems through this Actor's Issues tab on Apify. Include the input and run ID without personal information.

Jobicy, We Work Remotely, Himalayas, Remotive, Arbeitnow, ElevenLabs, Excel, Apify, Make, Zapier and n8n are trademarks of their respective owners. This Actor is not affiliated with, endorsed or sponsored by any of these companies or projects.

# Actor input Schema

## `sources` (type: `array`):

Verified sources only. Other candidate boards are excluded because of their terms or robots policy.

## `keywords` (type: `array`):

Match any phrase in title, employer, categories or tags. Case insensitive; descriptions are not searched.

## `excludeKeywords` (type: `array`):

Exclude rows matching any phrase in title, employer, categories or tags.

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

Match any category or tag substring. Taxonomies vary between boards.

## `remoteRegion` (type: `string`):

Explicit eligibility matching, for example worldwide, usa or europe. Worldwide roles match country searches. Unknown locations do not count as worldwide.

## `minSalary` (type: `number`):

Minimum lower salary bound, in its original currency and interval. No exchange-rate or annual conversion. Jobs without a lower bound are excluded when set.

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

Exclude jobs older than this many days. Zero disables. Missing dates are excluded when enabled.

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

Hard output cap. Each source gets a share of the remaining budget. A bounded feed window may return fewer matching jobs.

## `monitorMode` (type: `boolean`):

Emit first-discovered matching postings once per monitor and filter scope. The first run emits current matching jobs up to the cap.

## `monitorKey` (type: `string`):

Persistent monitor identity. Reuse with the same filters for scheduled runs; use a new name to reset. Run sequentially.

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

Include HTML-stripped, privacy-redacted descriptions up to 3000 characters. Off by default. Automated name detection is best effort.

## `requestDelayMs` (type: `integer`):

Minimum host spacing. Values below 750 ms still use 750 ms; robots crawl delays can increase this.

## `maxPagesPerSource` (type: `integer`):

Jobicy cursor pages per hourly synchronization pass, spaced and sequential. Default one. WWR uses one RSS feed. Cached depth cannot expand until the next refresh.

## Actor input object example

```json
{
  "sources": [
    "jobicy",
    "weworkremotely"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "categories": [],
  "remoteRegion": "",
  "minSalary": 0,
  "postedWithinDays": 0,
  "maxResults": 30,
  "monitorMode": false,
  "monitorKey": "default",
  "includeDescription": false,
  "requestDelayMs": 750,
  "maxPagesPerSource": 1
}
```

# Actor output Schema

## `results` (type: `string`):

Normalized job dataset.

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

Dataset table view.

## `runSummary` (type: `string`):

Source status, window, cache and row counts.

# 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 = {
    "sources": [
        "jobicy",
        "weworkremotely"
    ],
    "keywords": [],
    "excludeKeywords": [],
    "categories": [],
    "remoteRegion": "",
    "minSalary": 0,
    "postedWithinDays": 0,
    "maxResults": 30,
    "monitorMode": false,
    "monitorKey": "default",
    "includeDescription": false,
    "requestDelayMs": 750,
    "maxPagesPerSource": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("adastra-connect/remote-jobs-aggregator").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 = {
    "sources": [
        "jobicy",
        "weworkremotely",
    ],
    "keywords": [],
    "excludeKeywords": [],
    "categories": [],
    "remoteRegion": "",
    "minSalary": 0,
    "postedWithinDays": 0,
    "maxResults": 30,
    "monitorMode": False,
    "monitorKey": "default",
    "includeDescription": False,
    "requestDelayMs": 750,
    "maxPagesPerSource": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("adastra-connect/remote-jobs-aggregator").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 '{
  "sources": [
    "jobicy",
    "weworkremotely"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "categories": [],
  "remoteRegion": "",
  "minSalary": 0,
  "postedWithinDays": 0,
  "maxResults": 30,
  "monitorMode": false,
  "monitorKey": "default",
  "includeDescription": false,
  "requestDelayMs": 750,
  "maxPagesPerSource": 1
}' |
apify call adastra-connect/remote-jobs-aggregator --silent --output-dataset

```

## MCP server setup

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

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/Qd5lyRVadq7xXiOyU/builds/CRgdFCNT1nRrKQOvf/openapi.json
