# AI & ML Engineer Jobs Scraper — 8 Boards in One (`nomad-agent/ml-ai-dev-bundle`) Actor

AI & ML jobs aggregator: one run merges 8 boards (aijobs.net, LinkedIn, Hacker News Who-is-Hiring, Y Combinator, Built In, RemoteOK/Remotive/WeWorkRemotely, WTTJ, JustJoin.it) into a URL-deduped dataset with structured salary, remote flag and seniority. Delta mode for daily alerts.

- **URL**: https://apify.com/nomad-agent/ml-ai-dev-bundle.md
- **Developed by:** [Nomad Dev](https://apify.com/nomad-agent) (community)
- **Categories:** Jobs, AI
- **Stats:** 8 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 job results

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

## AI & ML Engineer Jobs Scraper — 8 Boards in One

> **Claude / Codex skill to describe and setup this actor: [SKILL.md](https://github.com/Exdenta/OinkAIJobSearch/blob/main/skill/ml-ai-dev-bundle/SKILL.md)**

One call, eight sources, one bill. This **AI/ML jobs aggregator** runs 8 job-board scrapers tuned for AI/ML roles inside a single Actor, merges and dedupes into one dataset, and normalizes **structured salary, remote flag and seniority uniformly across every board**.

### What machine learning jobs data does this scraper extract?

Each result is one flat JSON record per job posting. The same structured columns are filled for every source (from whichever field that source exposes), so you never have to special-case per board:

| Field | Type | Meaning |
|---|---|---|
| `source` | string | Which child board the record came from, e.g. `"ai_jobs_net"` |
| `id` | string | Stable source-side identifier (`""` when the source has none) |
| `title` | string | Job title as posted |
| `company` | string | Hiring company / organisation |
| `location` | string | Location / duty station (may include remote hints) |
| `url` | string | Direct link to the posting and primary cross-source deduplication key |
| `postedAt` | string | Posting date where the source provides it, else `""` |
| `snippet` | string | Backward-compatible alias of `description` |
| `description` | string | Complete description; records without one are omitted and text is never length-truncated |
| `salary` | string | Human-readable salary text, or composed from the numbers below |
| `hiringContactName` | string | null | LinkedIn rows: person named as the job poster |
| `hiringContactTitle` | string | null | That person's LinkedIn headline |
| `hiringContactUrl` | string | null | Their LinkedIn profile URL |
| `salaryMin` | number | null | Lower bound of the pay range as a number |
| `salaryMax` | number | null | Upper bound of the pay range as a number |
| `salaryCurrency` | string | null | Currency code (USD, EUR, GBP, PLN, …) |
| `salaryPeriod` | string | null | Pay period, one of `year`/`month`/`week`/`day`/`hour` |
| `isRemote` | boolean | null | `true` only for fully-remote, `false` for hybrid/on-site, `null` if unknown |
| `remoteType` | string | null | `remote` / `hybrid` / `on-site` (keeps the hybrid nuance) |
| `seniority` | string | null | Experience level, e.g. `"Senior"`, `"Mid-Senior level"` |
| `employmentType` | string | null | Commitment / contract type, e.g. `"Full-time"`, `"Contract"` |

Structured salary and the remote/seniority/type fields are populated from each source's own structured data where available: `ai_jobs_net`, `builtin`, `remote_boards`, `wttj` and `justjoinit` expose `salaryMin/Max/Currency/Period` directly; for `ycombinator_was` and `hackernews` the bundle parses the numbers out of the free-text pay line when the poster includes one. `linkedin` doesn't quote pay on its listings, so its salary fields stay empty/`null`; a `hackernews` post that names no figure is left `null` too. Any field a source doesn't provide is `""` (strings) or `null` (numbers/booleans) rather than fabricated.

### How the bundle works

This is a **bundle Actor**: one endpoint that runs every job-board scraper listed below **in-process** — their code ships inside this Actor, so no child actor runs are launched and no per-source fees stack on top. You pay this bundle's pay-per-event pricing only. Sources run concurrently, every record is mapped onto one flat schema with salary/remote/seniority normalized uniformly, and results are **deduped by URL** across boards. You can restrict the run to a subset with the `sources` input. Each source fails open independently: if one board errors or times out, the others still return.

### How to scrape machine learning jobs with this Actor

1. Click **Try for free** / **Run** — no login to the target site, no cookies, no proxies to configure.
2. Adjust the input (keyword, filters, `maxItems`) or keep the defaults.
3. Run it and export the dataset as JSON, CSV or Excel, or read it over the [API](https://docs.apify.com/api/v2).

Run it from your own code:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("nomad-agent/ml-ai-dev-bundle").call(run_input={"maxItems": 50})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], "—", item["company"], item["url"])
```

Or a single HTTP call that runs the Actor and returns items in one response:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/nomad-agent~ml-ai-dev-bundle/run-sync-get-dataset-items?token=%3CYOUR_APIFY_TOKEN%3E" \
  -H "Content-Type: application/json" \
  -d '{"maxItems": 50}'
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `sources` | array | `["linkedin", "ai_jobs_net", "hackernews", "ycombinator_was", "builtin", "remote_boards", "wttj", "justjoinit"]` | Which boards to include. Leave empty to run the full default set. All sources run in-process — no per-source fees. |
| `keyword` | string | `""` | Optional free-text filter forwarded to sources that support it (others ignore it). |
| `maxItemsPerSource` | integer | `36` | Cap on items fetched from EACH board before merge. |
| `maxItems` | integer | `288` | Hard cap on the merged, deduped output. Default is sources × `maxItemsPerSource` (the zero-config ceiling). Set `0` for no cap. |
| `cacheTtlSeconds` | integer | `1800` | How long to reuse results already fetched from a source instead of re-fetching. `0` = always fetch fresh. |
| `concurrency` | integer | `6` | How many boards to fetch in parallel. *(Advanced)* |
| `runTimeoutSecs` | integer | `240` | How long to give each source before returning what it has collected so far. Sources run in parallel, so this is a per-source ceiling, not a budget shared across them. *(Advanced)* |
| `apifyToken` | string (secret) | `""` | Leave empty — injected automatically on the Apify platform. Only set for local runs outside the platform. *(Advanced)* |

### Output example

```json
{
  "source": "ai_jobs_net",
  "id": "200475",
  "title": "Machine Learning Engineer",
  "company": "Hugging Face",
  "location": "Remote",
  "url": "https://aijobs.net/job/machine-learning-engineer-remote-200475/",
  "postedAt": "2026-06-28",
  "snippet": "We're hiring an ML engineer to work on our open-source model tooling. You will...",
  "description": "We're hiring an ML engineer to work on our open-source model tooling. You will...",
  "salary": "$140,000–$200,000/yr",
  "salaryMin": 140000,
  "salaryMax": 200000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "isRemote": true,
  "remoteType": "remote",
  "seniority": "Senior",
  "employmentType": "Full-time"
}
```

Every record from every board has this same shape. Fields the source doesn't provide come back as `""` (strings) or `null` (numbers/booleans) — e.g. `linkedin` and `hackernews` rows have empty salary fields.

### Pricing

Pay per event: **$0.01 per Actor start** and **$0.003 per job returned** ($3 per 1,000 jobs). That is the whole bill — every source runs inside this Actor, so there are **no child-actor fees** on top.

Zero-config run estimate (defaults, all 8 sources): up to ~288 merged items for roughly **$0.88 all-in** ($0.01 start + 288 × $0.003). Real runs usually cost less — not every board returns the full cap, and cross-board duplicates are billed once.

### Use cases

- AI-specialist job boards
- ML-engineer alert bots
- AI-talent market research
- Recruiting pipelines for data/ML teams

### FAQ

**Is it legal to scrape machine learning jobs?**
This Actor reads only publicly available job postings — data any visitor can see without logging in. No personal data behind authentication is touched. Review the target site's terms and your local regulations for your specific use case.

**Do I need an account on the target site?**
No. Postings are fetched from public pages/APIs — no login, cookies or session tokens.

**How fresh is the data?**
Every run fetches live listings. Results are cached for `cacheTtlSeconds` (default 30 min, set 0 to always hit the source live).

**How many jobs can I get?**
`maxItems` caps the run (set 0 for no cap). Most sources paginate from newest to oldest.

**Something broken or missing?**
Open an issue on the Actor's **Issues** tab — it is monitored and reliability fixes ship fast.

### Integrations

Export the dataset as JSON, CSV or Excel, or read it straight from the [Apify API](https://docs.apify.com/api/v2). Works out of the box with Make, Zapier and n8n via their Apify integrations, can be called synchronously with `run-sync-get-dataset-items` from any backend, and is usable by AI agents through the [Apify MCP server](https://mcp.apify.com/).

**Is this Actor useful to you?**
A quick ⭐ review on the Actor's **Reviews** tab helps other AI/ML and developer job seekers find it — and tells us what to build next.

### Related Actors

- [AI Jobs Scraper (aijobs.net) — ML & Data Roles](https://apify.com/nomad-agent/ai-jobs-net-scraper)
- [LinkedIn Jobs Scraper — No Login, No Cookies](https://apify.com/nomad-agent/linkedin-scraper)
- [Hacker News Who Is Hiring Scraper — HN Jobs](https://apify.com/nomad-agent/hackernews-scraper)
- [Y Combinator Jobs Scraper — Work at a Startup](https://apify.com/nomad-agent/ycombinator-was-scraper)
- [Built In Jobs Scraper — US Tech & Startup Jobs](https://apify.com/nomad-agent/builtin-scraper)
- [Remote Jobs Scraper — RemoteOK Remotive WWR](https://apify.com/nomad-agent/remote-boards-scraper)
- [Welcome to the Jungle Jobs Scraper (WTTJ)](https://apify.com/nomad-agent/wttj-scraper)
- [JustJoin.it Jobs Scraper — Polish Tech & IT Jobs](https://apify.com/nomad-agent/justjoinit-scraper)

***

**From the maker of [Oink](https://github.com/Exdenta/OinkAIJobSearch)** — an open-source, AI-powered job-search bot for Telegram that runs on these Actors. [Try the free bot](https://t.me/job_search_everyday_bot), get a managed instance at [oinkjobsearch.com](https://oinkjobsearch.com), or browse the [full catalog of 50+ Actors](https://apify.com/nomad-agent).

# Actor input Schema

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

Which boards to include. Leave empty to run the full default set. All sources run in-process inside this Actor — one bill, no child actor fees.

## `keyword` (type: `string`):

Optional free-text filter forwarded to sources that support it (others ignore it).

## `maxItemsPerSource` (type: `integer`):

Cap on items fetched from EACH board before merging. Each item that survives deduping is billed once by this bundle — there are no separate per-source fees.

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

Hard cap on the merged, deduped output. Default (288) is sources × 'Max items per source' — the natural zero-config ceiling. Set 0 to remove the cap — cost then scales with every source's raw output.

## `cacheTtlSeconds` (type: `integer`):

How long to reuse results already fetched from a source instead of fetching again. Set 0 to always fetch fresh data.

## `concurrency` (type: `integer`):

How many source boards to fetch at the same time.

## `runTimeoutSecs` (type: `integer`):

How long to give each source before returning what it has collected so far. Sources run in parallel, so this is a per-source ceiling, not a budget shared across them.

## `apifyToken` (type: `string`):

Leave empty — injected automatically when this Actor runs on the Apify platform. Only set this when running the code outside the platform, e.g. on your own machine.

## Actor input object example

```json
{
  "sources": [
    "linkedin",
    "ai_jobs_net",
    "hackernews",
    "ycombinator_was",
    "builtin",
    "remote_boards",
    "wttj",
    "justjoinit"
  ],
  "maxItemsPerSource": 36,
  "maxItems": 288,
  "cacheTtlSeconds": 1800,
  "concurrency": 6,
  "runTimeoutSecs": 240
}
```

# Actor output Schema

## `dataset` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("nomad-agent/ml-ai-dev-bundle").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("nomad-agent/ml-ai-dev-bundle").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 '{}' |
apify call nomad-agent/ml-ai-dev-bundle --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nomad-agent/ml-ai-dev-bundle"
        }
    }
}

```

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/3NcZZ1KxyIoXvvOzN/builds/vsM015Al55d1bF6ls/openapi.json
