# IT Jobs Scraper — Tech Jobs Feed: Dice, StepStone, Remote (`halobartku/tech-jobs-feed`) Actor

IT jobs scraper & unified tech jobs feed API: Dice, StepStone, Remotive, Arbeitnow, RemoteOK in one run. Normalised rows: title, company, location, remote, salary, posted date, apply URL. Independently validated (remotive 18/18, dice 100/100). Built by an autonomous AI agent.

- **URL**: https://apify.com/halobartku/tech-jobs-feed.md
- **Developed by:** [B](https://apify.com/halobartku) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $100.00 / 1,000 job source harvesteds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Tech Job Boards Feed — Dice, StepStone & Remote Boards, Unified

One run → ONE normalised job feed from five tech job boards. Same fields from
every source, one `source` column telling you which board a row came from.
Query them all without writing five scrapers, five parsers and five
normalisers — that normalisation layer IS this actor.

### Sample output (real rows from a live run)

```json
{"source": "dice", "source_id": "d6492c4ceda1228660114a072c5b8ba1", "title": "Sr. Python Developer", "company": "TEKsystems c/o Allegis Group", "location": "Menlo Park, California, USA", "country": "USA", "employment_type": "Full-time", "is_remote": false, "posted_at": "2026-08-18T12:19:05Z", "salary": "USD 70.00 - 90.00 per hour", "description_snippet": "…first ~1,500 chars, HTML stripped…", "job_url": "https://www.dice.com/job-detail/bc8523b8-41bb-4382-8004-dcc68c20edef", "apply_url": "https://www.dice.com/job-detail/bc8523b8-41bb-4382-8004-dcc68c20edef"}
```

Every source returns this identical shape — 13 flat fields, no nesting, no
pagination to walk.

### What you get

Every dataset item is a flat JSON row with identical fields, whichever board
it came from:

| field | notes |
|---|---|
| `source` | `dice` | `stepstone` | `remotive` | `arbeitnow` | `remoteok` |
| `source_id` | the job's ID on that board |
| `title`, `company` | normalised, whitespace-cleaned |
| `location`, `country` | free text as the board states it |
| `employment_type` | Full-time / Contract / … (empty when the board has none) |
| `is_remote` | `true`/`false` when the board says so, `null` when it doesn't |
| `posted_at` | ISO-8601 UTC everywhere, regardless of source format |
| `salary` | as published (often empty — these boards mostly hide pay) |
| `description_snippet` | first ~1 500 chars, HTML stripped |
| `job_url`, `apply_url` | canonical links |

### Sources

- **Dice** (US tech market) — via the same public JSON API dice.com's own
  frontend calls. Full summaries, salary text, employment type, posted dates.
- **StepStone** (DE/EU market) — server-rendered result pages, parsed from
  the public search. German relative dates ("vor 3 Tagen") normalised to ISO.
- **Remotive, RemoteOK** — remote-only tech boards, public JSON APIs.
  RemoteOK rows link back to remoteok.com (their ToS attribution).
- **Arbeitnow** — DE/AT job-market aggregator, public JSON API.

Multiple search terms are run against every included source and merged,
deduplicated, capped per source. **Blocked or empty sources are skipped and
never billed** — you pay per source that actually returned data.

### How keyword search matches (per board)

Boards differ in what their search can do, and this actor uses each board's
real capability instead of pretending they're equal:

- **Dice** — keyword search runs **server-side** on the board itself; you get
  exactly what the board's own search returns (full-text matches included).
- **Remotive** — the board's server-side search is currently dead (every term
  returns the same global feed; measured 2026-08-24), so the keyword is
  matched **client-side against the full description**. Niche terms can be
  valid-empty — that is the board's limitation, disclosed, not hidden.
- **RemoteOK** — has no search endpoint (one whole-feed API), so the keyword
  is matched client-side against the full description, not just the title.
- **StepStone** — server search + client title/company/location check.
- **Arbeitnow** — no server-side search at all. The actor pulls
  `maxPerSource × 6` recent listings and filters client-side. Niche terms can
  live deeper in the feed than that window: a measured 'python' search
  (2026-08-22) recalled 10 of 15 matches inside the newest 746 listings —
  the 5 misses sat at feed positions 692–731, past the 6× window.

### Input

- `searchTerms` (required) — e.g. `["python", "kubernetes"]`
- `sources` — which boards; default all five
- `location` — free text, applied everywhere
- `remoteOnly` — keep only remote-flagged rows
- `postedSince` — `24h`, `7d`, `30d`
- `maxPerSource` — cap per search term × source (default 100)

Minimal agent input: `{"searchTerms": ["python"]}` — everything else has a
sane default.

### For AI agents & LLM apps

One flat JSON object per job, stable field names, no HTML to parse. Semantic
notes an agent should know:

- `is_remote: null` means the board genuinely doesn't say — not "false".
- `source_id` is the board's stable job id — dedupe across runs on
  `(source, source_id)`.
- An empty dataset is a valid answer (term genuinely matches nothing on
  that day); it is charged only per source that returned rows. A source
  that is down or returns nothing is skipped, logged, and never charged.
- `posted_at` is ISO-8601 UTC always — normalised from "vor 3 Tagen",
  relative timestamps, and every board's own format.

### Keywords

dice jobs scraper · stepstone scraper · remotive jobs api · remoteok jobs
api · arbeitnow scraper · tech job boards unified · unified job feed ·
remote tech jobs api · job search aggregator · python jobs dataset ·
developer jobs dataset · job data for ai agents · job postings api ·
tech job market data · germany tech jobs · usa tech jobs scraper ·
job board monitor · recruiting data feed

### FAQ

**Am I charged for sources that fail or return nothing?**
No. The charge event is `source-harvested` — one per source that actually
returned written rows. Blocked, down, or empty sources are skipped, logged
in run status, and billed at zero.

**Why is a salary often empty?**
Because the board doesn't publish it. The field is there when they do —
Dice publishes pay text frequently, the remote boards rarely.

**Why do I get fewer rows for a niche term on remotive/arbeitnow?**
Those boards have no working server-side search; matching is client-side
over their recent-listings window (disclosed above with measured numbers).
Broad terms fill the cap; ultra-niche terms can be valid-empty.

**Can I run it on a schedule?**
Yes — daily or hourly runs with the same `searchTerms` give you a
time-series of the tech job market. Dedupe on `(source, source_id)`.

**Is there an MCP / API example?**

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("halobartku/tech-jobs-feed").call(run_input={"searchTerms": ["python"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], "|", item["company"], "|", item["source"])
```

### Changelog

- **0.1.8** — remotive precision fix: board's server-side search found dead
  (every term returned the same 18-listing feed); remotive rows now
  client-side keyword-filtered. Empty queries verified: 0 rows, 0 charges.
- **0.1.6** — empty-coverage gate: a term that matches nothing is a valid
  empty answer (SUCCEEDED, 0 rows, 0 charges), never a run failure.
- **0.1.5** — remoteok full-description matching; arbeitnow 6× fetch
  headroom (measured recall disclosed above).
- **0.1** — first public release: five boards, one schema.

### Honest limits

- **Dice is US-only** (its API is `countryCode=US`).
- **StepStone is the German site** — German listings, occasionally English.
- **Salary data is sparse** on all five boards; the field is there when they
  publish it.
- **Wellfound (AngelList Talent) and ZipRecruiter are deliberately NOT
  included.** Both sit behind an interactive Cloudflare challenge on every
  endpoint (page, API, regional domains — verified 2026-08-19). An actor that
  needs residential-proxy farms and challenge solvers to maybe-work is a
  liability, not a product. If they ever drop the wall, they'll be added.
- StepStone's remote filter (`wfh=2`) and Dice's remote filter
  (`workplaceTypes=Remote`) restrict server-side; the other boards are
  remote-by-definition.

### AI-authored disclosure

This actor, its code, and its documentation were written end-to-end by an
autonomous AI agent (Jarvis, operated by the publisher). Field mappings were
verified against each board's live responses on the build date above —
including independent from-scratch ground-truth comparisons (receipts in the
publisher's validation log) and daily automated smoke tests.

# Actor input Schema

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

One or more keywords, e.g. \["python", "kubernetes"]. Each term is searched on every included source; results are merged and deduplicated into one normalised feed.

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

Which boards to harvest. dice = Dice.com (US, official client API), stepstone = StepStone.de (DE/EU, SSR HTML), remotive / remoteok = remote-only tech boards (public APIs), arbeitnow = DE/AT aggregator (public API). Default: all five.

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

Free-text location, applied on every source ('Berlin', 'New York'). Empty = all locations. For Dice this is also pushed into the server-side search; for the remote boards it filters the location field.

## `remoteOnly` (type: `boolean`):

Keep only rows flagged remote by the source (Dice includeRemote, StepStone Home-Office facet, remotive/remoteok are remote by definition).

## `postedSince` (type: `string`):

Keep jobs posted within this window: '24h', '7d', '30d'. Empty = no date filter.

## `maxPerSource` (type: `integer`):

Hard cap per (term × source). 1–1000. Default 100.

## Actor input object example

```json
{
  "searchTerms": [
    "python developer"
  ],
  "sources": [
    "dice",
    "stepstone",
    "remotive",
    "arbeitnow",
    "remoteok"
  ],
  "remoteOnly": false,
  "postedSince": "",
  "maxPerSource": 100
}
```

# Actor output Schema

## `items` (type: `string`):

All results, one dataset item per row. Fetch as JSON via this URL with an APIFY\_TOKEN bearer header. A run-level summary is written to the run's terminal status message.

# 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 = {
    "searchTerms": [
        "python developer"
    ],
    "sources": [
        "dice",
        "stepstone",
        "remotive",
        "arbeitnow",
        "remoteok"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("halobartku/tech-jobs-feed").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 = {
    "searchTerms": ["python developer"],
    "sources": [
        "dice",
        "stepstone",
        "remotive",
        "arbeitnow",
        "remoteok",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("halobartku/tech-jobs-feed").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 '{
  "searchTerms": [
    "python developer"
  ],
  "sources": [
    "dice",
    "stepstone",
    "remotive",
    "arbeitnow",
    "remoteok"
  ]
}' |
apify call halobartku/tech-jobs-feed --silent --output-dataset

```

## MCP server setup

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

```

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/gpViMebestC5RusdE/builds/ybtYUIlhokusFq36O/openapi.json
