# Remote Tech Jobs Aggregator - 7 Job Boards, One Feed (`tiger19935/remote-tech-jobs-aggregator`) Actor

Remote jobs scraper and tech jobs aggregator: RemoteOK, Remotive, Arbeitnow, Himalayas, Jobicy, We Work Remotely and Hacker News in one clean, de-duplicated feed. Filter by keyword, seniority, location and salary. Pay only per job delivered.

- **URL**: https://apify.com/tiger19935/remote-tech-jobs-aggregator.md
- **Developed by:** [Andrew Boiko](https://apify.com/tiger19935) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 job delivereds

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 Tech Jobs Aggregator - 7 Job Boards, One Clean Feed

Pull remote and tech job listings from **seven public job sources** into one normalized, de-duplicated dataset: RemoteOK, Remotive, Arbeitnow, Himalayas, Jobicy, We Work Remotely and the monthly Hacker News "Who is hiring?" thread. Filter by keyword, category, seniority, location and salary, and pay only for the jobs you actually receive.

It reads the sources' public APIs and RSS feeds. It does not log in, does not use headless browsers and does not scrape LinkedIn, Indeed or Glassdoor.

### Who is it for

- **Job seekers and career coaches** who want one daily feed of fresh remote roles instead of checking seven sites.
- **Recruiters and sourcers** looking for companies that are hiring right now (a hiring company is a sales lead too).
- **Newsletter and job-board builders** who need a normalized feed with attribution fields already filled in.
- **Analysts** tracking which skills, countries and salary ranges show up in remote hiring.

### What you get

Every job is one flat row with the same fields no matter which source it came from: title, company, logo, location, remote type, country hints, salary range (when published), tags, category, seniority guess, employment type, posting date, apply URL, source URL, optional cleaned description and an attribution line. The same job published on several boards is returned once (matching on normalized company + title + location) and lists the other copies in `alsoOn`.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `sources` | array | all 7 | `remoteok`, `remotive`, `arbeitnow`, `himalayas`, `jobicy`, `weworkremotely`, `hackernews` |
| `keywords` | array | empty | Keep a job if ANY keyword is found (whole word) in title, tags or description |
| `excludeKeywords` | array | empty | Drop a job if ANY of these words is found |
| `categories` | array | all | `engineering`, `devops`, `data`, `design`, `product`, `marketing`, `sales`, `customer-support`, `management`, `finance`, `hr`, `writing`, `other` |
| `seniority` | array | all | `junior`, `mid`, `senior`, `lead` (guessed from the job title) |
| `locations` | array | any | Text match on the job location; `Europe`, `EU`, `Americas`, `Asia` are expanded to countries |
| `countries` | array | any | ISO-2 code or English country name |
| `includeWorldwide` | boolean | true | Let jobs open to "Worldwide / Anywhere" pass location and country filters |
| `minSalaryUsd` | integer | 0 | Minimum yearly salary in USD (see salary notes below) |
| `postedWithinDays` | integer | 30 | 1-365. Jobs without a date are kept |
| `maxResults` | integer | 200 | 1-5000. Newest jobs first |
| `includeDescription` | boolean | false | Add cleaned plain-text description, max 4,000 characters |
| `includeFailed` | boolean | false | Add free `type: "error"` rows for failed sources (never charged) |

Example input:

```json
{
  "sources": ["remoteok", "remotive", "himalayas", "hackernews"],
  "keywords": ["python", "golang"],
  "excludeKeywords": ["intern"],
  "seniority": ["senior", "lead"],
  "locations": ["Europe"],
  "minSalaryUsd": 100000,
  "postedWithinDays": 14,
  "maxResults": 100
}
```

### Output example

```json
{
  "type": "job",
  "id": "remoteok:1001",
  "source": "remoteok",
  "title": "Senior Python Engineer",
  "company": "Acme Labs",
  "companyLogo": "https://remoteok.com/assets/img/acme.png",
  "location": "Worldwide",
  "worldwide": true,
  "remoteType": "remote",
  "countryHints": [],
  "salaryMin": 120000,
  "salaryMax": 160000,
  "salaryCurrency": "USD",
  "salaryPeriod": "yearly",
  "salaryUsdYearlyEstimate": 160000,
  "tags": ["python", "django", "backend", "senior"],
  "category": "engineering",
  "seniorityGuess": "senior",
  "employmentType": null,
  "postedAt": "2026-10-13T08:12:00.000Z",
  "applyUrl": "https://remoteOK.com/l/1001",
  "sourceUrl": "https://remoteok.com/remote-jobs/remote-senior-python-engineer-acme-labs-1001",
  "description": null,
  "alsoOn": ["remotive:2001", "weworkremotely:acme-labs-senior-python-engineer"],
  "attribution": "Source: RemoteOK (https://remoteok.com). Job data from the RemoteOK public API; link back to the RemoteOK job page is required when you republish.",
  "scrapedAt": "2026-10-14T09:00:00.000Z"
}
```

Besides the dataset, each run stores `OUTPUT` (JSON summary with per-source status, counts, skipped reasons, charged events and `stoppedReason`) and `RESULTS_MD` (short Markdown summary) in the key-value store.

### Pricing

**Pay only for jobs delivered: $0.002 per job** ($2 per 1,000) (`job` event), so 1,000 jobs cost $2.00 before any Apify plan discount. You are never charged for source errors, empty results, duplicates, jobs filtered out, or jobs beyond your "maximum cost per run". When the budget is reached the run stops softly and keeps everything delivered so far. Free-plan users can try it on the free monthly credits; the default test input (10 jobs) costs about two cents. Platform compute usage is included in the price.

### How it works

1. All selected sources are fetched in parallel (each source politely and sequentially, with a clear User-Agent, timeouts and retries with backoff). One failing source never fails the run; it appears in `OUTPUT.sources` with its error.
2. Every record is normalized: HTML is cleaned to text, salaries are parsed, locations are mapped to country hints, seniority and category are derived from the title.
3. Filters are applied, duplicates across sources are merged, results are sorted by posting date (newest first) and capped at `maxResults`.
4. Each delivered job is charged once. Progress is saved in the key-value store, so after a migration or restart already delivered jobs are not pushed or charged again.

Source details: RemoteOK public JSON API (the first element is a legal notice that we skip and keep as attribution), Remotive public API, Arbeitnow job-board API (up to 5 pages), Himalayas jobs API (paged), Jobicy v2 API, We Work Remotely RSS category feeds, and Hacker News via the Algolia API (top-level comments of the latest "Ask HN: Who is hiring?" threads, parsed from the usual `Company | Role | Location | ...` first line).

### Salary notes

- RemoteOK, Himalayas and Jobicy return numeric ranges; Remotive and Hacker News salaries are parsed from free text (`$80k - $100k`, `EUR 70k-90k`).
- `salaryUsdYearlyEstimate` is filled only for USD amounts that are clearly yearly, or hourly (hourly x 2,080). Other currencies and monthly figures are left `null` rather than guessed with exchange rates.
- When `minSalaryUsd` is above 0, jobs without a usable USD estimate are dropped.

### Limits and honest caveats

- Coverage equals what these seven sources publish. It is not a LinkedIn, Indeed or Glassdoor scraper.
- Seniority, category, remote type and country hints are heuristics from titles and location text. Expect some wrong or empty values, especially on Hacker News comments, which are free-form.
- Location filters can only use the text the source gives. "Remote - US only" detection depends on the wording used.
- Several boards limit how many items their public endpoints return per request (often 20-100 recent jobs), so very old listings are not reachable.
- Source APIs can change without notice. If one stops working, the others still deliver and the run summary tells you which one failed.
- Duplicate detection is text based; the same role posted twice by one company in the same city can be merged.

### Legal and ethical notes

The data comes from public feeds that the sources offer for reuse. Their terms differ: **RemoteOK, Remotive, Himalayas and Jobicy ask for a visible link back to the original job and credit to the source** - every row carries `sourceUrl` and `attribution` for that purpose, please keep them when you republish. Hacker News comments are written by individuals and companies; do not use contact details found in them for unsolicited bulk marketing, and respect GDPR and the laws of your country. You are responsible for how you use the data. This actor is not affiliated with any of the listed sources.

### FAQ

**Does it need proxies or logins?** No. All sources are public and open.

**Why do I get fewer jobs than maxResults?** Filters, the posting-date window and de-duplication remove rows. Widen `postedWithinDays`, drop filters, or select more sources.

**Can I run it daily?** Yes. Schedule it in Apify; each run is independent and only charges for the rows it delivers. Use `postedWithinDays: 1` for a daily "new jobs" feed.

**Why is my salary filter so strict?** Many jobs publish no salary. With `minSalaryUsd` set, only jobs with a usable USD estimate are kept.

**Is the description full text?** With `includeDescription` it is cleaned plain text cut at 4,000 characters. Keyword filters use the whole description even when you do not request it.

**What happens if a source is down?** The run continues with the remaining sources and reports the failure in `OUTPUT`. If every source fails the run fails and nothing is charged.

**What does `alsoOn` mean?** The same job was found on other sources; the row you get is the most complete copy.

### Use via API

Replace `YOUR_TOKEN` with your Apify API token.

```bash
curl -X POST "https://api.apify.com/v2/acts/tiger19935~remote-tech-jobs-aggregator/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sources":["remoteok","arbeitnow"],"keywords":["developer"],"maxResults":10}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('tiger19935/remote-tech-jobs-aggregator').call({
    keywords: ['react'], seniority: ['senior'], maxResults: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'jobs');
```

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("tiger19935/remote-tech-jobs-aggregator").call(
    run_input={"keywords": ["python"], "locations": ["Europe"], "maxResults": 50}
)
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["company"], "-", job["title"], job["applyUrl"])
```

### Integrations

Use the Apify integrations for Make, n8n and Zapier to start the actor on a schedule and send each new job to Slack, Telegram, email or Airtable. For Google Sheets use the Apify Google Sheets integration or export the dataset as CSV/XLSX. A typical flow: schedule daily, `postedWithinDays: 1`, filter by your keywords, append rows to a sheet and notify a channel.

### More tools by the same author

- [Career Page Jobs Scraper](https://apify.com/tiger19935/career-page-jobs-scraper): Open jobs from company career pages (Greenhouse, Lever, Ashby and more)
- [Hiring Signals Monitor](https://apify.com/tiger19935/hiring-signals-monitor): Track which companies are hiring: new jobs and hiring surges
- [Djinni, DOU & JustJoin.it IT Jobs Scraper](https://apify.com/tiger19935/djinni-dou-justjoin-jobs-scraper): Ukrainian and Polish tech jobs in one run

# Actor input Schema

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

Which job boards to read. Leave all selected for the widest coverage; one source failing never fails the run. Values: remoteok, remotive, arbeitnow, himalayas, jobicy, weworkremotely, hackernews.

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

Keep a job if ANY keyword appears (whole word, case-insensitive) in its title, tags or description. Examples: python, react, golang, c++. Leave empty to keep all jobs.

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

Drop a job if ANY of these words appears in its title, tags or description. Example: unpaid, intern, wordpress.

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

Keep only these job categories. The category is derived from the job title first, then from the source's own category. Empty = all categories.

## `seniority` (type: `array`):

Keep only these levels. The level is guessed from the job title (for example 'Senior', 'Jr.', 'Lead', 'Principal'); jobs whose level cannot be guessed are dropped when this filter is set. Empty = all.

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

Keep jobs whose location text contains ANY of these terms, e.g. Germany, Berlin, Europe, EU, Americas, Asia. Region names are expanded to their countries. Empty = any location.

## `countries` (type: `array`):

Keep jobs that mention ANY of these countries, as a 2-letter ISO code or English name, e.g. US, DE, Portugal. Country detection works from the job's location text only. Empty = any country.

## `includeWorldwide` (type: `boolean`):

When on (default), jobs open to candidates anywhere ('Worldwide', 'Anywhere') also pass the location and country filters. Turn off to get only jobs that name your location.

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

Keep only jobs with a published salary of at least this many US dollars per year (hourly USD rates are converted with 2,080 hours). Jobs with no salary, a non-USD salary or a monthly salary are dropped when this is above 0. 0 = no salary filter.

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

Only jobs posted in the last N days (1-365). Jobs without a posting date are kept. Hacker News threads older than the window are ignored.

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

Maximum number of jobs to deliver (and pay for). Newest jobs come first. The run also stops softly when your maximum cost per run is reached.

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

Add the job description as cleaned plain text, truncated to 4,000 characters. Keywords are matched against the full description either way.

## `includeFailed` (type: `boolean`):

Push one extra row (type 'error', never charged) for every source that failed. The run summary in the OUTPUT record always lists source statuses.

## Actor input object example

```json
{
  "sources": [
    "remoteok",
    "arbeitnow"
  ],
  "keywords": [
    "developer"
  ],
  "excludeKeywords": [],
  "categories": [],
  "seniority": [],
  "locations": [],
  "countries": [],
  "includeWorldwide": true,
  "minSalaryUsd": 0,
  "postedWithinDays": 30,
  "maxResults": 10,
  "includeDescription": false,
  "includeFailed": false
}
```

# Actor output Schema

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

All result rows.

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

Counts, charged events and warnings.

# 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": [
        "remoteok",
        "arbeitnow"
    ],
    "keywords": [
        "developer"
    ],
    "maxResults": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("tiger19935/remote-tech-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": [
        "remoteok",
        "arbeitnow",
    ],
    "keywords": ["developer"],
    "maxResults": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("tiger19935/remote-tech-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": [
    "remoteok",
    "arbeitnow"
  ],
  "keywords": [
    "developer"
  ],
  "maxResults": 10
}' |
apify call tiger19935/remote-tech-jobs-aggregator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tiger19935/remote-tech-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/0hrtGBm7oDn0qzSWk/builds/11xYlc0Q5gxuSW91J/openapi.json
