# Remote Jobs from 4 Public Job Feeds in One Table (`usta/remote-jobs-feeds`) Actor

For remote job hunters and job boards: pull live listings from Remote OK, Jobicy, Arbeitnow and We Work Remotely's public feeds into one table, one row per job, with the source credit and apply link on every row.

- **URL**: https://apify.com/usta/remote-jobs-feeds.md
- **Developed by:** [US Tech Automations](https://apify.com/usta) (community)
- **Categories:** Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 result rows

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 Jobs from 4 Public Job Feeds in One Table

A **remote jobs scraper** and **remote jobs API** in one Actor: pulls
**Remote OK jobs** and **We Work Remotely jobs**, plus Jobicy and Arbeitnow,
from each site's own public feed, and returns one row per listing —
deduplicated across sources — instead of you writing four separate
integrations.

### Introduction

Four public job feeds, read live on every run, each with its own shape:
Remote OK's JSON API, Jobicy's JSON API, Arbeitnow's paged JSON API (kept to
its `remote: true` rows only — most of that board is on-site), and We Work
Remotely's public RSS feed. This Actor normalizes all four into one row
shape, drops exact company+title duplicates across sources (keeping the
earliest-posted copy), and pushes nothing invented: a source that fails or
is blocked contributes one honest error row instead of guessed data.

A fifth source, **Remotive**, is listed in the input because it is a named
source for this tool, but it is **never actually fetched**: Remotive's own
`robots.txt` disallows `/api/*`, which covers the job feed endpoint. This
Actor does not call a robots-disallowed path, so selecting Remotive always
returns one row explaining that instead of live data. See `SOURCE_TERMS.md`.

### Input

| Field | What it does |
|---|---|
| `sources` | Which feeds to read. Default: Remote OK, Jobicy, Arbeitnow, We Work Remotely. |
| `keywords` | Optional. Keep only jobs whose title or tags contain at least one of these words (case-insensitive). |
| `postedWithinDays` | Optional. Drop jobs posted more than this many days ago. |
| `maxItems` | Hard ceiling on rows returned (1–5000, default 200). This is also your spend cap. |
| `previousIds` | Optional. `dedupe_key` values from an earlier run; matching jobs come back with `is_new: false`. |
| `proxyConfiguration` | When set, every request goes through that proxy. Off by default. |

### Pricing

**$0.002 per row returned. No start fee.** A run capped at 200 rows costs at
most $0.40; a quiet run that returns fewer rows costs less.
`maxItems` is the spend cap: the run cannot cost more than that number times
$0.002, whatever the feeds hold.

Every row the run returns bills at that price, and that includes an error row
(at most one per source that failed or, for Remotive, is robots-blocked).
Error rows are counted against `maxItems` along with job rows, so the cap is a
cap on the whole run, not just on the job listings. If the cap is small enough
that both do not fit, room is kept for the error rows first, because they are
what tells you a source was down.

### Output — one row per job

| Field | What it holds |
|---|---|
| `source_name` | "Remote OK", "Jobicy", "Arbeitnow", "We Work Remotely", or "Remotive" |
| `source_url` | The exact job page on the source's own site, unmodified |
| `apply_url` | The source's own URL for this job — never rewritten by this Actor. (Jobicy's free feed itself returns its own listing page here, not the employer's ATS link; see `SOURCE_TERMS.md`.) |
| `job_id` | The source's own ID or slug for this job |
| `title` | Job title |
| `company` | Employer name |
| `company_logo` | Always `null` — this Actor never ships a source's logo image (Remote OK's terms specifically reserve its logo) |
| `location_limits` | Where the source says this role is open to, as published (may be a country, region, or "Anywhere") |
| `job_type` | e.g. "Full-Time", "Contract", when the source publishes one |
| `seniority` | e.g. "Senior", "Director" — only Jobicy publishes this; `null` from the other three |
| `salary_min` / `salary_max` / `salary_currency` | When the source publishes a number; a `0`/missing value is treated as unpublished, not a real $0 salary |
| `tags` | The source's own category/skill tags, as a list |
| `posted_at` | ISO 8601 UTC timestamp |
| `description_text` | Plain text (HTML tags stripped), capped at 5000 characters |
| `dedupe_key` | Normalized `company\|title`, used to drop exact duplicates across sources and to match `previousIds` |
| `is_new` | `true` unless `dedupe_key` was in `previousIds` |
| `fetched_at` | When this run read the source, in UTC |
| `error` | `null` on a real job row; on a source-failure row, every other field is `null` and this holds the reason |

### Example (real row, live run 2026-09-21, Jobicy)

```json
{
  "source_name": "Jobicy",
  "source_url": "https://jobicy.com/jobs/153812-creative-motion-design",
  "apply_url": "https://jobicy.com/jobs/153812-creative-motion-design",
  "job_id": "153812",
  "title": "Creative (Motion Design)",
  "company": "Superside",
  "company_logo": null,
  "location_limits": "Anywhere",
  "job_type": "Contract",
  "seniority": "Senior",
  "salary_min": null,
  "salary_max": null,
  "salary_currency": null,
  "tags": ["Creative & Design"],
  "posted_at": "2026-09-21T13:59:59Z",
  "description_text": "Superside is looking for a motion designer with the creativity, technical expertise, and innovation mindset to push the boundaries of motion design. [...truncated for this README; full run returned 3005 characters here]",
  "dedupe_key": "superside|creative motion design",
  "is_new": true,
  "fetched_at": "2026-09-21T17:23:58Z",
  "error": null
}
```

### Required credit (please keep this)

Remote OK's and Jobicy's own terms require a kept, followed link back to
the source on any page that shows this data, and Arbeitnow's and We Work
Remotely's terms ask for the same. Every row already carries `source_name`
and an unmodified `source_url`/`apply_url` for exactly this reason — if you
build a page or app from this data, keep that link visible and pointing at
the source's own URL, and do not swap in a different logo for Remote OK's.
See `SOURCE_TERMS.md` for the exact quoted terms.

### Recommendations

Run with `keywords` set to your role/skill so a small `maxItems` still
returns a relevant table. If you only want Arbeitnow's genuinely-remote
listings, expect a slower per-row yield from that source specifically — see
"Limitations."

### Limitations — what this does not do

- **Remotive is never fetched.** Its `robots.txt` blocks the feed path; see
  above and `SOURCE_TERMS.md`.
- **Arbeitnow's remote rows are rare and can trip rate limiting.** A live
  page of 250 Arbeitnow listings held only 12 marked `remote: true` on
  2026-09-21, so this Actor must scan many pages per remote row. A live test
  run on 2026-09-21 was answered with an HTTP 429 Cloudflare challenge after
  17 Arbeitnow page requests in one run; `fetch.py` now stops itself at 12
  Arbeitnow page requests per run to stay clear of that. A run that asks for
  many rows and selects Arbeitnow may still get fewer Arbeitnow rows than
  other sources, or an Arbeitnow error row, rather than a guess.
- **Jobicy's `apply_url` is Jobicy's own listing page, not the employer's
  application form**, because reaching the employer's own link requires a
  paid Jobicy API key this Actor does not use. See `SOURCE_TERMS.md`.
- **Jobicy can contribute at most 50 rows to any run.** This Actor reads
  Jobicy's free feed in one call at its own page size (`count=50`); it does
  not page Jobicy for more, so raising `maxItems` never gets you more than
  50 Jobicy rows in a single run.
- **No claim about uptime or how fresh a feed is beyond what was measured**
  in `LOCAL_RUN.md` at the time this Actor was built. Feeds update on each
  source's own schedule, not this Actor's.
- **No de-duplication against your own database** — `previousIds` only
  flags `is_new`; it does not filter rows out.
- When the proxy setting is set, every request goes through that proxy.
- **No salary normalization.** `salary_min`/`salary_max`/`salary_currency`
  are passed through as each source published them (period — hourly vs.
  yearly — is not separately captured); most Arbeitnow and We Work Remotely
  rows carry no salary at all.

### FAQ

**Where does the data come from?**
Four sources' own public feeds, read live on every run: Remote OK's JSON
API, Jobicy's JSON API, Arbeitnow's paged JSON API, and We Work Remotely's
public RSS feed. Never from a stored copy.

**Does it need an API key or a login?**
No. All four feeds used here are public and unauthenticated.

**Why did I get a row with `error` set and every other field blank?**
That source failed to answer, or (for Remotive) is permanently
robots-blocked. No job was invented to replace it.

**Why did I get fewer rows than `maxItems`?**
Either the selected sources and filters held that many jobs and no more, the
run hit `maxItems` first, (for Arbeitnow specifically) the 12-page
politeness ceiling was reached before enough `remote: true` rows turned up,
(for Jobicy specifically) its own feed caps at 50 rows per run, or a small
number of listings were dropped because the source gave no usable
source/apply link for them (see "Required credit" above).

**Is there support?**
Yes. Raise it through the Apify Store issues tab for this Actor, or email
operations@ustechautomations.com. Replies within 2 business days.

### Refunds and support

Refunds: if a run returns zero job rows for a valid input, email
operations@ustechautomations.com within 7 days and we refund that run.
Support: same address, replies within 2 business days.

# Actor input Schema

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

Which public feeds to read. 'remotive' is listed because its API is a named source for this tool, but it is never actually called: remotive.com's own robots.txt disallows /api/\*, so it always returns one error row explaining that instead of live data. See SOURCE\_TERMS.md.

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

Optional. Keep only jobs whose title or tags contain at least one of these words (case-insensitive). Leave blank for every job the selected sources return.

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

Optional. Drop jobs posted more than this many days ago. A row whose source did not publish a date is kept rather than guessed away.

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

Hard ceiling on rows returned, and therefore on the cost of the run. Every row bills at the listed price from row one; there is no start fee.

## `previousIds` (type: `array`):

Optional. The dedupe\_key values from an earlier run's output. Any job whose dedupe\_key is in this list is returned with is\_new set to false instead of true.

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

Off by default. Every source used here answers plain, unauthenticated HTTPS requests with a normal browser-style User-Agent, so a run does not need a proxy; turn one on only if a buyer's network needs it.

## Actor input object example

```json
{
  "sources": [
    "remoteok",
    "jobicy",
    "arbeitnow",
    "weworkremotely"
  ],
  "maxItems": 200,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

## `report` (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 = {
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("usta/remote-jobs-feeds").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 = { "maxItems": 200 }

# Run the Actor and wait for it to finish
run = client.actor("usta/remote-jobs-feeds").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 '{
  "maxItems": 200
}' |
apify call usta/remote-jobs-feeds --silent --output-dataset

```

## MCP server setup

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

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/Re3XOcbgfYY6Dthrf/builds/hXgBS22dzGjaitzYD/openapi.json
