All Jobs Scraper - 26 Job Boards, One Schema, Parsed Salaries
Pricing
from $3.10 / 1,000 results
All Jobs Scraper - 26 Job Boards, One Schema, Parsed Salaries
All-in-one job scraper that aggregates 26+ job boards. Indeed, Adzuna, USAJobs, Reed, Remotive, and more into one de-duplicated feed by keyword. Get title, company, salary, location, remote status, and posting date, ready for JSON, CSV, Excel, or API/webhook delivery into Notion, Slack, or Airtable.
Pricing
from $3.10 / 1,000 results
Rating
5.0
(2)
Developer
Techforce Global
Maintained by CommunityActor stats
3
Bookmarked
4
Total users
4
Monthly active users
11 days ago
Last modified
Categories
Share
All Jobs Scraper — 26 Job Boards Into One De-Duplicated Feed
One keyword in, 26 job boards out — de-duplicated and normalised to a single shape. Indeed, Adzuna, USAJobs, Reed, Remotive, RemoteOK, The Muse, Jooble and 18 more, searched in parallel and merged into one feed with title, company, location, tri-state remote flag, normalised employment type, salary as published plus parsed min/max/currency, description, URL and posting date. The ten default sources need no API keys of your own. Optional delivery into Notion, Slack, Linear, Jira, GitHub or Airtable over MCP.
🔐 Trust & operations at a glance
| Credentials | None needed to start. The ten default sources work out of the box; you can supply your own keys for higher limits or to unlock opt-in boards. Delivery runs through the Apify MCP Proxy, so the Actor never sees your connector tokens. |
| Data scope | Public job postings only. No candidate data, no applicant records, no recruiter contact details, no logins. |
| Personal data | Essentially none. Company names are businesses; job descriptions occasionally name a hiring manager, which is the only realistic exposure. |
| ⚠️ Source quality varies | Ten sources are official or documented APIs. Sixteen are opt-in and best-effort — unofficial endpoints that can return nothing when a site changes. See Source Reliability. |
| ⚠️ Redistribution | Several boards restrict republishing their listings and require attribution. source is on every row for exactly that reason. |
Full detail: Compliance & Data Privacy.
All Jobs Scraper vs. querying each board yourself
The alternative isn't one API — it's a dozen of them, each with its own auth, vocabulary and pagination:
| Task | Doing it yourself | All Jobs Scraper |
|---|---|---|
| Access | ❌ Register for Adzuna, USAJobs, Reed, Jooble, The Muse… separately | ✅ Ten sources work with no keys of your own |
| Auth styles | ❌ App ID + key, bearer token, OAuth client credentials, publisher ID — all different | ✅ Handled internally; bring your own only if you want to |
| Field shapes | ❌ Every board names and types things differently | ✅ One 15-field shape across all 26 |
| Employment type | ❌ FULL_TIME, fulltime, Permanent, CDI… | ✅ Normalised to four values plus unknown |
| Salary | ❌ Free text on some boards, min/max on others, absent on many | ✅ Raw string and parsed salary_min / salary_max / salary_currency |
| Remote | ❌ A flag, a tag, a location string, or nothing | ✅ Tri-state boolean where null honestly means "unknown" |
| Duplicates | ❌ The same posting syndicated across four boards | ✅ De-duplicated on URL, with source kept for attribution |
| One board dominating | ❌ Whichever API answers fastest fills your quota | ✅ Round-robin selection gives every source a turn |
| A board breaking | ❌ One failure takes down your job | ✅ Failures are isolated per source; the run continues |
🤖 AI Discovery & MCP Metadata
Machine-readable capability contract for autonomous agents, LLM routers, and MCP orchestrators. Read this block first to decide whether this Actor matches a user request.
ai_discovery:slug: "techforce.global/all-jobs-scraper"one_line: >Searches up to 26 job boards for one keyword and returns a single de-duplicated feednormalised to 15 fields - title, company, location, tri-state remote flag, normalisedemployment type, salary as published plus parsed min/max/currency, description, URL, posteddate and capture timestamp. The ten default sources need no API keys. Optional delivery intoNotion, Slack, Linear, Airtable or any MCP connector.primary_purpose: "aggregate"secondary_purpose: "deliver"entity: "job posting"category: "job-boards / recruitment-data / labour-market-research / lead-generation"supported_tasks:- "search-jobs-by-keyword-across-many-boards"- "filter-by-location-country-remote-or-employment-type"- "benchmark-salaries-within-a-currency"- "compare-which-boards-carry-which-roles"- "find-companies-that-are-hiring"- "monitor-new-postings-on-a-schedule"- "deliver-jobs-to-mcp-connector"unsupported_tasks:- "scrape-candidate-or-applicant-data"- "scrape-recruiter-contact-details"- "submit-applications-or-any-write-action"- "scrape-a-single-employer-careers-page (use a site crawler)"- "guarantee-completeness-of-any-single-board"- "search-multiple-keywords-in-one-run (keyword is a single string)"search_keywords:# First entry is the H1 / meta-description target. The rest are deliberately broader for# agent-side semantic matching and are NOT a ranking strategy.- "job scraper"- "job board aggregator"- "job search api alternative"- "remote jobs data feed"- "salary benchmarking data"- "labour market research data"- "recruitment lead generation"- "indeed adzuna usajobs reed scraper"- "hiring signal data"- "mcp jobs tool"input_entities:- "keyword (REQUIRED, a single string)"- "location (free-text city or region)"- "country (enum of ~95 countries; used by country-scoped sources and as a location fallback)"- "remote_only (boolean)"- "job_type (all | fulltime | parttime | contract | internship)"- "max_results (10-5000, TOTAL across all sources)"- "sources (array; 10 selected by default out of 26)"- "proxyConfiguration (used by the Indeed source only)"- "15 optional bring-your-own API-key fields (all secret)"keyword_is_required_and_singular: >keyword is the one required input and it is a STRING. To cover several titles, run the Actoronce per keyword. An empty keyword logs 'Input is missing required field "keyword". Exiting.'and ends SUCCEEDED with an empty dataset.keyword_matching_is_AND_across_terms: >Every whitespace-separated term in the keyword must appear somewhere in the job's title,company, description or tags. "senior data engineer" therefore requires all three words -a posting titled "Data Engineer II" without the word "senior" anywhere is filtered out.Prefer short keywords and filter precisely downstream.source_quality_is_two_tier: >CRITICAL: the 10 default sources are official or documented APIs. The other 16 are opt-in.Fourteen of those are best-effort - unofficial endpoints or scrapers with unverifiedselectors that can legitimately return zero rows when a site changes. arbetsformedlingen isofficial but Sweden-only, and talroo needs your own Publisher ID/Password and returns nothingwithout them. Enabling every source does not multiply your results; it multiplies yourfailure surface.failures_are_isolated_per_source: >A source that errors is logged as '[source] fetch failed, skipping this source' and returnsnothing. The run continues with the others and still ends SUCCEEDED.results_are_spread_round_robin: >After filtering and de-duplication the final max_results is allocated round-robin across thesources that returned rows, so one fast board cannot consume the whole quota. With 5 sourcesand max_results 5 you get roughly one job from each.output_entities:["source","title","company","location","remote","job_type","salary","salary_min","salary_max","salary_currency","description","url","posted_date","tags","scraped_at",]remote_is_tri_state: >remote is true, false, or null - and null means the board did not say, not "office-based".remote_only filtering drops a row only on a positive false, so unknowns are kept.salary_is_usually_absent_and_multi_currency: >Most postings carry no salary at all. When present you get the raw string plus parsedsalary_min / salary_max / salary_currency. NEVER average across currencies - group bysalary_currency first.filtering_is_lenient_by_design: >Rows are dropped only on a positive mismatch, never on missing data, because boards populatedifferent subsets of fields. That keeps recall high at the cost of some loosely-matching rows.results_are_pushed_once_at_the_end: >Every job is written in a single push after all sources have been fetched, filtered,de-duplicated and selected. An aborted or timed-out run therefore saves NOTHING - unlikescrapers that stream rows as they go.pricing:model: "pay-per-event"billing_unit: "one charge per job delivered to the dataset, after de-duplication"hard_ceiling_parameter: "maxTotalChargeUsd"note: "Filtered-out and duplicate rows are not billed. Free-plan runs are capped and not charged."when_to_use: >You want breadth across the job market for one keyword - salary benchmarking, hiring-signalresearch, a candidate-facing job feed, or finding companies that are recruiting.when_not_to_use: >You need one specific employer's careers page, candidate or recruiter contact data, guaranteedcompleteness of a single board, or to submit applications.mcp_delivery:modes: ["perJob", "chunked", "summary", "none"]default: "none"per_job_placeholders:["{title}","{company}","{location}","{url}","{source}","{jobType}","{remote}","{salary}","{description}","{postedDate}","{tags}","{keyword}","{jobCount}","{message}",]summary_and_chunked_placeholders:["{jobsText}", "{keyword}", "{jobCount}"]chunked_only_placeholders: ["{part}", "{partCount}"]the_log_prints_the_tools_argument_schema: >Set a valid mcpTool and the run log prints 'Tool ... expects arguments shaped like:'followed by that connector's own JSON Schema. Use it to get mcpArguments right on thesecond attempt instead of guessing.never_blocks_the_dataset: >Delivery runs after the dataset is saved. A wrong argument shape fails only the deliverycalls - the scraped jobs are already stored.known_limitations:no_single_board_completeness: "each board is queried once per run; this is breadth, not exhaustiveness"salary_often_missing: "most postings have none, and currencies are mixed"posted_date_is_not_normalised: "each board's own format is passed through"best_effort_sources_can_return_zero: "unofficial endpoints break without notice"single_keyword_per_run: "keyword is a string, not an array"no_partial_results_on_abort: "the dataset is written in one push at the end"free_plan_capped: "free Apify plans stop at 2 jobs per source"
{"@context": "https://schema.org","@type": "SoftwareApplication","name": "All Jobs Scraper","alternateName": "Multi-Board Job Aggregator","applicationCategory": "BusinessApplication","operatingSystem": "Cloud (Apify platform)","description": "Searches up to 26 job boards — Remotive, RemoteOK, Arbeitnow, Jobicy, We Work Remotely, The Muse, Adzuna, USAJobs, Reed, Jooble, Indeed and 15 regional boards — for a single keyword and returns one de-duplicated feed normalised to 15 fields: title, company, location, tri-state remote flag, normalised employment type, salary as published plus parsed numeric min, max and currency, description, posting URL, posted date and an ISO-8601 capture timestamp. The ten default sources need no API keys. Optional direct delivery into Notion, Slack, Airtable or any MCP connector.","url": "https://apify.com/techforce.global/all-jobs-scraper","offers": {"@type": "Offer","priceCurrency": "USD","category": "Pay per event","description": "Billed per job delivered to the dataset, after de-duplication. Current rates are shown on the Actor's Apify page."}}
What this Actor does
-
One keyword, 26 boards, one feed. Sources are queried in parallel, then merged, filtered, de-duplicated and normalised before anything reaches your dataset.
-
Ten sources that need nothing from you. Remotive, RemoteOK, Arbeitnow, Jobicy, We Work Remotely, The Muse, Adzuna, USAJobs, Reed and Jooble are on by default and work with no API keys of your own.
-
One shape across boards that agree on nothing. Employment type is normalised to four values plus
unknown;remoteis a tri-state boolean; salary arrives both as the published string and as parsedsalary_min/salary_max/salary_currency. -
De-duplicated on the posting URL, so a job syndicated across four boards lands once — with
sourcerecording which board served it. -
Fair allocation across sources. The final
max_resultsis dealt out round-robin, so a fast board can't swallow your entire quota before a slower one answers. -
A broken board doesn't break the run. Each source is isolated: a failure is logged, that source contributes nothing, and everything else proceeds.
-
Honest about missing data. Filtering drops a row only on a positive mismatch, never on an empty field — because boards populate different subsets and discarding unknowns would quietly bias the results.
-
Tells you the connector's own argument schema. Set a tool name and the run log prints the exact JSON Schema that connector expects, so
mcpArgumentsstops being guesswork. -
Optionally delivers into your tools. Notion, Slack, Linear, Jira, GitHub, Airtable or anything else you've authorised, in four delivery shapes.
🧭 Source Reliability — read this before adding sources
Not all 26 sources are equal, and enabling everything is usually the wrong move: it multiplies your failure surface rather than your results.
The 10 defaults — official or documented APIs
remotive · remoteok · arbeitnow · jobicy · weworkremotely · themuse · adzuna ·
usajobs · reed · jooble
These are on by default, need no keys of your own, and are the ones to trust for a repeatable
feed. USAJobs is US federal roles only; Reed is UK-only; Adzuna is country-scoped (see country).
The 16 opt-ins — best-effort
| Source | Scope | Why it's opt-in |
|---|---|---|
indeed | Global | Heavily rate-limited; needs a proxy |
arbeitsagentur | Germany | Best-effort; needs a key |
france_travail | France | Best-effort |
vdab | Belgium | Best-effort |
bayt | MENA | Best-effort |
cv_library | UK | Best-effort |
foundit | India | Best-effort |
jobbank | Canada | Best-effort |
jobs_ch | Switzerland | Best-effort |
kariyer | Turkey | Best-effort |
onlinejobsph | Philippines | Best-effort |
glints | Southeast Asia | Best-effort |
talent_com | Global | Best-effort |
freelancer | Global (contract) | Best-effort |
arbetsformedlingen | Sweden | Official, but Sweden-only |
talroo | US | Returns nothing without your own Publisher ID/Password |
Best-effort means an unofficial endpoint or a scraper with unverified selectors. It can return zero rows without warning when the site changes — that's a source going quiet, not the Actor failing. Add them deliberately, for the region you actually need, and check the per-source counts in the log.
When to use it — and when not to
| Scenario | Fit |
|---|---|
| Breadth across the market for one role | ✅ Strong — the core use case |
| Remote-role feeds | ✅ Strong — five of the ten defaults are remote-first boards |
| Salary benchmarking | ✅ Strong — but group by salary_currency, and expect gaps |
| Finding companies that are hiring | ✅ Strong — company on every row |
| Comparing which boards carry which roles | ✅ Strong — source per row |
| Country or region targeting | ✅ Strong — country plus the regional opt-ins |
| Employment-type filtering | ✅ Strong — normalised across board vocabularies |
| New-posting monitoring | ✅ Strong — schedule it and de-duplicate on url |
| Results in Notion / Slack / Airtable | ✅ Strong — native MCP delivery |
| Exhaustive coverage of one board | ⚠️ Each board is queried once per run — breadth, not completeness |
| Precise multi-word matching | ⚠️ Every keyword term must appear somewhere in the text |
| Reliable posting dates | ⚠️ posted_date is passed through in each board's own format |
| Salary on most rows | ⚠️ Most postings simply don't publish one |
| A specific employer's careers page | ❌ Use a site crawler instead |
| Candidate or applicant data | ❌ Never collected |
| Recruiter contact details | ❌ Not collected |
| Several keywords in one run | ❌ keyword is a single string — one run each |
| Submitting applications | ❌ Read-only |
⚡ Execution Rules (Avoid Common Agent Failures)
-
keywordis required and singular. It's the only required input, and it's a string. An empty one logsInput is missing required field "keyword". Exiting.and endsSUCCEEDEDwith an empty dataset. For several titles, run once per keyword. -
Every keyword term must match. The words are ANDed across the job's title, company, description and tags.
"senior data engineer"drops a posting titled Data Engineer II that never says "senior". Search broad, filter narrow downstream. -
max_resultsis the total across all sources, not per source. Default100, range10–5000. It's dealt out round-robin, so with ten sources andmax_results: 100you get roughly ten from each — not 100 from the fastest. -
Enabling all 26 sources is usually a mistake. Sixteen are opt-in and fourteen of those are best-effort. Add the regional ones you actually need — see Source Reliability.
-
A source returning zero rows is normal, not a failure. Check
Raw results per source before filtering:in the log to see who contributed. -
remoteis tri-state.true/false/null, andnullmeans the board didn't say.remote_onlydrops a row only on a positivefalse, so unknowns are kept — filter harder yourself if you need certainty. -
Salary is usually absent, and always multi-currency. Use
salary_min/salary_maxonly alongsidesalary_currency, and never average across currencies. -
posted_dateis not normalised. Each board's own format is passed through.scraped_atis the only reliable ISO-8601 timestamp on the row. -
Filtering is deliberately lenient. Rows are dropped only on a positive mismatch, never on missing data, so expect some loose matches rather than silent over-filtering.
-
De-duplication is keyed on
url(falling back to title + company). The same job on four boards lands once, attributed to whichever source appeared first. -
proxyConfigurationmatters only for Indeed. The other sources are plain APIs. Leaving it off is fine unlessindeedis enabled — and Indeed without a proxy is heavily blocked. -
Nothing is saved until the end. All sources are fetched, filtered, de-duplicated and selected, then written in one push. An aborted or timed-out run saves nothing — size
max_resultsso the run finishes. -
deliveryModedefaults tonone. Delivery is opt-in here. When you do enable it, run once and read the log lineTool … expects arguments shaped like:before writingmcpArguments. -
Free Apify plans stop at 2 jobs per source and aren't charged. That's a smoke test, not a data pull.
dependencies:- rule: "keyword is required and must be a non-empty string"on_violation: "the run ends SUCCEEDED with an empty dataset and an error line in the log"- rule: "every whitespace-separated keyword term must appear in title/company/description/tags"note: "shorter keywords recall more; filter precisely downstream"- rule: "max_results is a global total, allocated round-robin across sources"note: "it is not a per-source limit"- rule: "indeed needs proxyConfiguration; the other sources do not"on_violation: "Indeed returns little or nothing"- rule: "talroo returns nothing without your own Publisher ID and Password"- rule: "delivery requires mcpConnector AND mcpTool"on_violation: "delivery is skipped with a warning; every dataset row is still written"- rule: "mcpArguments must match the connector tool's real argument schema"on_violation: "every delivery call fails with an MCP input validation error; the dataset is unaffected"authentication:actor_input: "none required; 15 optional bring-your-own key fields, all stored as secrets"platform: "Apify API token required for API / SDK / MCP invocation"target_sites: "the ten default sources need no keys of your own"
📥 Input Contract
.actor/input_schema.json is authoritative — if this page disagrees with the schema files,
the schema files win.
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
keyword | string | ✅ Yes | Console prefill only | Job title, skill or company. One per run |
location | string | ⬜ No | "" | Free-text city or region. Blank = broad/remote search |
country | string (enum) | ⬜ No | "" (Any) | ~95 countries. Used by country-scoped sources (Adzuna, USAJobs) and as a location fallback |
remote_only | boolean | ⬜ No | false | Keeps rows the source says are remote, plus unknowns |
job_type | string (enum) | ⬜ No | all | all · fulltime · parttime · contract · internship |
max_results | integer | ⬜ No | 100 | 10–5000, total across all sources, allocated round-robin |
sources | array | ⬜ No | the 10 defaults | Pick from 26 — see Source Reliability |
proxyConfiguration | object | ⬜ No | {"useApifyProxy": false} | Only the Indeed source uses it |
Unknown source keys are ignored with a warning; if nothing valid remains, the Actor falls back to the defaults rather than scraping nothing.
Bring your own API keys (optional)
Fifteen optional, secret fields let you supply your own credentials for adzuna, usajobs,
reed, jooble, themuse, arbeitsagentur, france_travail, vdab and talroo. Every default
source already works without them — use these only to run on your own account's rate limits, or to
enable talroo, which ships no built-in access and returns nothing until you provide a Publisher
ID and Password. A value you supply always overrides the built-in one.
Delivery (optional)
| Parameter | Type | Default | Description |
|---|---|---|---|
mcpConnector | string | "" | A connector you've authorised on your Apify account — Notion, Slack, Linear, Jira, GitHub, Airtable, or any MCP-compatible connector. |
deliveryMode | string (enum) | none | perJob (one call per job — best for one Notion page/row each) · chunked (split a large list) · summary (one call, all jobs) · none. |
mcpTool | string | "" | Tool to call — notion-create-pages, send_message, create_issue. Required when a connector is set. |
mcpArguments | object | {} | Arguments for the tool. String leaves support {placeholder} tokens. Must match the tool's real argument shape. |
mcpMessageTemplate | string | "" | Template rendered and exposed to mcpArguments as {message}. |
Placeholders by mode:
| Mode | Placeholders |
|---|---|
perJob | {title} {company} {location} {url} {source} {jobType} {remote} {salary} {description} {postedDate} {tags} {keyword} {jobCount} {message} |
summary | {jobsText} (the formatted job list) {keyword} {jobCount} {message} |
chunked | the same, plus {part} {partCount} — {jobsText} holds one part |
Getting
mcpArgumentsright first time is unrealistic — so don't guess twice. Run once withmcpConnectorandmcpToolset, then read the log lineTool 'x' expects arguments shaped like:— it prints that connector's own JSON Schema. A mismatched shape fails every delivery call with an MCP input-validation error, but the dataset is already saved, so it costs you nothing but the retry.
📤 Output Contract & Data Structure
One dataset row per job, written in a single push after de-duplication. Every row carries the same 15 keys.
| Field | Type | Nullable | Meaning |
|---|---|---|---|
source | string | no | Which board served this listing. Keep it — several boards require attribution |
title | string | no | Job title |
company | string | yes | Hiring company |
location | string | yes | Location as the source gave it — not normalised |
remote | boolean | yes | Tri-state: true / false / null. Null means unknown, not office-based |
job_type | string | no | Normalised: fulltime, parttime, contract, internship or unknown |
salary | string | yes | Human-readable salary as published. Usually absent |
salary_min | number | yes | Parsed lower bound, when one could be derived |
salary_max | number | yes | Parsed upper bound |
salary_currency | string | yes | Currency of the parsed values. Always group by this before averaging |
description | string | yes | Plain-text description or excerpt |
url | string | no | Link to the original posting. The de-duplication key |
posted_date | string | yes | As the source gave it — format varies by board |
tags | array | no | Board-supplied tags. [] when none |
scraped_at | string | no | ISO-8601 capture timestamp — the only reliable date on the row |
The three fields people misread:
remote: nullis unknown, a missingsalaryis the norm rather than an error, andposted_dateis whatever the board printed — usescraped_atfor anything time-sensitive.
Example output object
{"source": "remotive","title": "Senior Data Engineer","company": "Northwind Analytics","location": "Remote (EU)","remote": true,"job_type": "fulltime","salary": "€70,000 - €90,000 per year","salary_min": 70000,"salary_max": 90000,"salary_currency": "EUR","description": "You'll own the ingestion layer end to end — batch and streaming…","url": "https://remotive.com/remote-jobs/data/senior-data-engineer-1234567","posted_date": "2026-08-24","tags": ["data", "python", "aws"],"scraped_at": "2026-09-01T09:14:02.481920+00:00"}
Values illustrate shape and formatting — the company and role are invented. A typical run mixes rows like this with ones where
salary,companyorposted_datearenull; that's the data, not a defect.
What a successful run looks like
- Status:
SUCCEEDED - Dataset: one row per job, up to
max_results, spread across the sources that answered - Log:
Searching for "<keyword>" across sources: [...], thenRaw results per source before filtering: {...}, thenTotal raw: N, after filter: N, after dedupe: N, pushed: N - Zero results is also a success. A keyword too narrow,
remote_onlyon a keyword with no remote roles, or every enabled source coming back empty all endSUCCEEDEDwith an empty dataset and the warning "No jobs matched after filtering…". - A source contributing nothing is normal — best-effort boards go quiet, and regional ones won't match a keyword outside their market.
- Fewer rows than
max_resultsis normal for niche keywords.
Dataset views
| View | Shows |
|---|---|
| Overview | Source, title, company, location, remote, type, salary, posted date, link |
| Salary & Type | Title, company, salary string plus parsed min/max/currency, type, remote, location |
| By Source | Source, title, company, location, dates, tags — for board coverage comparison |
▶️ Invocation & Integration
Authenticate every call with Authorization: Bearer <APIFY_TOKEN>.
Apify Console
Open the Actor page → Input tab → type a
keyword → optionally set location, country, remote-only and job type → adjust sources if you need
regional coverage → Start.
Model Context Protocol (MCP)
claude mcp add --transport http apify \"https://mcp.apify.com?tools=techforce.global/all-jobs-scraper"
Then ask your agent for "remote data engineering roles paying over €70k".
Python (apify-client)
import osfrom collections import Counter, defaultdictfrom statistics import medianfrom apify_client import ApifyClientclient = ApifyClient(os.getenv("APIFY_TOKEN"))run = client.actor("techforce.global/all-jobs-scraper").call(run_input={"keyword": "data engineer", # short keyword: every term must match"remote_only": True,"job_type": "fulltime","max_results": 200,"deliveryMode": "none",})jobs = list(client.dataset(run["defaultDatasetId"]).iterate_items())if not jobs:raise SystemExit("Nothing matched — try a broader keyword or more sources.")# Which boards actually contributed?print(Counter(j["source"] for j in jobs))# Salary benchmarking — only rows that have one, and only within a currency.by_currency = defaultdict(list)for j in jobs:if j.get("salary_min") is not None and j.get("salary_currency"):by_currency[j["salary_currency"]].append(j["salary_min"])for currency, values in by_currency.items():print(f"{currency}: n={len(values):>3} median min {median(values):,.0f}")# remote is tri-state — null means the board didn't say.print(Counter(str(j["remote"]) for j in jobs))
JavaScript / TypeScript (apify-client)
import { ApifyClient } from "apify-client";const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor("techforce.global/all-jobs-scraper").call({keyword: "product manager",country: "Germany",sources: ["remotive", "arbeitnow", "adzuna", "themuse", "jooble"],max_results: 150,deliveryMode: "none",});const { items } = await client.dataset(run.defaultDatasetId).listItems();// Companies hiring, ranked by how many roles they have open.const byCompany = items.reduce<Record<string, number>>((acc, j) => {if (j.company) acc[j.company] = (acc[j.company] ?? 0) + 1;return acc;}, {});console.table(Object.entries(byCompany).sort((a, b) => b[1] - a[1]).slice(0, 10),);// posted_date formats vary by board — scraped_at is the dependable one.for (const j of items.slice(0, 5)) {console.log(`${j.source.padEnd(14)} ${j.posted_date ?? "no date"} ${j.title}`,);}
cURL — synchronous (waits, returns the dataset)
curl -X POST \-H 'Content-Type: application/json' \-H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \-d '{ "keyword": "devops engineer", "remote_only": true, "max_results": 50, "deliveryMode": "none" }' \'https://api.apify.com/v2/acts/techforce.global~all-jobs-scraper/run-sync-get-dataset-items'
cURL — asynchronous with a spend cap (recommended for large runs)
# 1. Start with a hard spend limitcurl -X POST -H 'Content-Type: application/json' \-H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \-d '{ "keyword": "nurse", "country": "United Kingdom", "max_results": 2000 }' \'https://api.apify.com/v2/acts/techforce.global~all-jobs-scraper/runs?maxTotalChargeUsd=10'# 2. Poll, then 3. fetch the dataset when SUCCEEDEDcurl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' 'https://api.apify.com/v2/actor-runs/<runId>'curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \'https://api.apify.com/v2/datasets/<DATASET_ID>/items?clean=true&format=json'
Adding regional coverage
{"keyword": "software engineer","country": "Germany","location": "Berlin","sources": ["remotive","remoteok","arbeitnow","jobicy","weworkremotely","themuse","adzuna","usajobs","reed","jooble","arbeitsagentur","france_travail"],"max_results": 300,"deliveryMode": "none"}
The ten defaults plus the two German/French opt-ins. Add regional boards for the region you're actually searching — a Turkish board won't help a Berlin search, and each extra source is another thing that can go quiet.
Including Indeed
{"keyword": "accountant","country": "United States","sources": ["remotive", "adzuna", "usajobs", "jooble", "themuse", "indeed"],"max_results": 200,"proxyConfiguration": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"]},"deliveryMode": "none"}
Indeed is the one source that genuinely needs proxyConfiguration — without it, expect little or
nothing from it while the other sources carry on normally.
With MCP delivery — one Notion row per job
{"keyword": "data analyst","max_results": 50,"mcpConnector": "<your-authorized-notion-connector>","mcpTool": "notion-create-pages","deliveryMode": "perJob","mcpArguments": {"parent": { "database_id": "<YOUR_NOTION_DATABASE_ID>" },"pages": [{"properties": {"Name": "{title}","Company": "{company}","Location": "{location}","URL": "{url}","Source": "{source}"},"content": "{description}"}]}}
Match the property names to your database's actual columns. If the call fails validation, the log
prints the schema Notion's tool really expects — fix mcpArguments from that, not from guesswork.
Scheduling for new-posting alerts
Put a fixed input on a schedule, keep max_results
modest, and de-duplicate against your own store on url. Anything not already seen is a new
posting.
⚠️ Autonomous Agent Error Handling & Resolution Matrix
Read this first: nothing about the sources fails the run. An empty keyword, a dead board and
an over-narrow search all end SUCCEEDED while the log carries the reason. An agent that checks
run status alone will read an empty dataset as success-with-data — check the row count and the
per-source counts in the log.
The CONDITION labels are documentation handles for observable states; this Actor writes no error
code into the dataset.
| Condition | What you observe | Cause | Deterministic agent action |
|---|---|---|---|
MISSING_KEYWORD | SUCCEEDED, empty dataset; log: Input is missing required field "keyword". Exiting. | keyword blank | Modify Input: supply a non-empty keyword. |
KEYWORD_TOO_SPECIFIC | Very few rows; after filter far below Total raw | Every keyword term must appear in the text | Modify Input: shorten the keyword; filter downstream instead. |
NO_JOBS_AFTER_FILTERING | SUCCEEDED, empty dataset; log: No jobs matched after filtering… | Keyword, location, remote_only or job_type too restrictive | Modify Input: broaden one constraint at a time. |
SOURCE_RETURNED_ZERO | A source shows 0 in Raw results per source | Best-effort board changed, region mismatch, or no matches | Expected. Not a failure — drop the source or ignore it. |
SOURCE_FETCH_FAILED | Log: [source] fetch failed, skipping this source | That board errored; the run continues | Expected, partial. Retry later or disable it. |
UNKNOWN_SOURCE_IGNORED | Log: Ignoring unknown source(s) in input | A misspelled source key | Modify Input: use a key from the enum. |
ALL_SOURCES_INVALID | Log: No valid sources selected - falling back to defaults | Every key was unrecognised | Expected fallback. Fix the keys to control coverage. |
TALROO_EMPTY | talroo contributes nothing | It ships no built-in access | Modify Input: supply Publisher ID and Password, or remove it. |
INDEED_EMPTY | indeed contributes little or nothing | No proxy configured | Modify Input: {"useApifyProxy": true} with a residential group. |
PROXY_SETUP_FAILED | Log: Failed to create proxy configuration… | Not logged in locally, or the plan lacks that proxy group | Prompt user: check plan and credentials; other sources still work. |
ONE_SOURCE_DOMINATES | Expected a spread, got a spread — but fewer per source than hoped | Round-robin allocation is working as designed | Fix expectation, or raise max_results. |
REMOTE_IS_NULL | Many rows have remote: null | The board didn't say | Expected. Null ≠ office-based. Filter harder if you need certainty. |
SALARY_MISSING | Most rows have no salary | Most postings don't publish one | Expected. Benchmark on the subset that does. |
MIXED_CURRENCIES | Salary figures look wildly inconsistent | Multi-currency dataset | Fix consumer: group by salary_currency before any aggregate. |
POSTED_DATE_UNPARSEABLE | posted_date won't parse consistently | Each board's own format is passed through | Fix consumer: use scraped_at, or parse per source. |
NO_DATA_AFTER_ABORT | An aborted run left an empty dataset | Results are written in one push at the end | Expected. Lower max_results so the run completes. |
FREE_PLAN_CAPPED | 2 jobs per source; status message says so | Free Apify plan | Upgrade the plan. |
DELIVERY_VALIDATION_ERROR | Every connector call fails with an MCP input validation error | mcpArguments doesn't match the tool's schema | Read the log: Tool … expects arguments shaped like: prints it. Dataset unaffected. |
CONNECTOR_TOOL_NOT_FOUND | Log: tool not available, followed by the available tools | Wrong mcpTool name | Modify Input: copy an exact name from that log line. |
CONNECTOR_UNREACHABLE | Log: Could not reach the connector: … | Connector not authorised, or running locally | Authorise it, or set deliveryMode: "none". Data already saved. |
AUTH_INVALID | HTTP 401 | Missing or malformed APIFY_TOKEN | Abort & prompt user for a valid token. |
RATE_LIMITED_APIFY | HTTP 429 | Apify API rate / concurrency limits | Retry: exponential backoff (2s, 4s, 8s). |
SYNC_TIMEOUT | HTTP 408 on the sync endpoint | Run exceeded the synchronous ceiling | Reconfigure: async POST /runs + polling. |
Provenance: every log line, status and dataset condition above is taken from this Actor's own source and verified against it. The three HTTP rows describe the Apify API transport layer, not this Actor — confirm them against the Apify API reference before wiring them into automated retry logic.
🗣️ Natural Language → Actor Mapping
| A user says… | Intent | Constructed Actor input |
|---|---|---|
| "Find data engineer jobs" | Core search | {"keyword":"data engineer","max_results":100} |
| "Remote Python roles" | Remote filter | {"keyword":"python","remote_only":true} |
| "Full-time nursing jobs in the UK" | Country + type | {"keyword":"nurse","country":"United Kingdom","job_type":"fulltime"} |
| "Internships in Berlin" | Location + type | {"keyword":"intern","location":"Berlin","country":"Germany","job_type":"internship"} |
| "Contract designers" | Type filter | {"keyword":"designer","job_type":"contract"} |
| "What do data engineers earn?" | Salary benchmark | Scrape, then group by salary_currency and aggregate salary_min/salary_max |
| "Which companies are hiring for X?" | Hiring signal | Scrape, then count by company |
| "Which board has the most X roles?" | Coverage comparison | Scrape, then count by source |
| "Add German and French boards" | Regional coverage | Append arbeitsagentur, france_travail to sources |
| "Include Indeed" | Extra source | Add indeed plus proxyConfiguration |
| "Government jobs only" | Narrow source | {"sources":["usajobs"]} — US federal roles |
| "Alert me to new postings" | Monitoring | Fixed input on a schedule; de-duplicate on url |
| "Search these five job titles" | Multi-keyword | Five runs — keyword takes one string |
| "Put these jobs in Notion" | Scrape + deliver | Above, plus mcpConnector, mcpTool, deliveryMode: "perJob" |
| "It returned nothing" | Empty result | Shorten the keyword, drop remote_only, check per-source counts in the log |
| "Get me the candidates" | Candidate data | ❌ Never collected |
| "Apply to these for me" | Write action | ❌ Read-only |
Should NOT route here: candidate, applicant or CV data · recruiter contact details · one specific employer's careers page (use a site crawler) · guaranteed completeness of a single board · several keywords in one run · submitting applications or any write action.
🧭 Agent Execution & Routing Logic
Does the user want JOB POSTINGS as data?├── NO → wrong Actor.│ one employer's careers page → a site crawler│ candidate / recruiter data → not obtainable here│└── YES│├── keyword — REQUIRED, a SINGLE string.│ several titles? → ONE RUN PER KEYWORD│ remember: EVERY term must appear in the posting's text│├── Narrow it (all optional, all lenient — unknowns are kept)│ ├── location → free text│ ├── country → drives Adzuna/USAJobs and acts as a fallback│ ├── remote_only → drops only positively non-remote rows│ └── job_type → normalised across board vocabularies│├── Choose sources deliberately│ ├── default (10) → official/documented APIs, no keys needed. START HERE.│ ├── + regional → add ONLY the region you're searching│ ├── + indeed → ALSO set proxyConfiguration│ └── + talroo → needs YOUR Publisher ID/Password, else it returns nothing│├── Size the run│ ├── max_results → TOTAL across sources, dealt round-robin (10-5000)│ └── nothing is saved until the end → size it so the run finishes│├── What is the actual question?│ ├── salary → group by salary_currency FIRST, then aggregate│ ├── who's hiring → count by company│ ├── board value → count by source│ ├── remote → remote == true only (null means unknown)│ └── freshness → scraped_at; posted_date formats vary by board│└── Should results land in another app?├── yes → mcpConnector + mcpTool, THEN read the log's printed argument│ schema before writing mcpArguments│ one row each → perJob · long list → chunked · digest → summary└── no → deliveryMode "none" (the default)[Execute Actor]├──► SUCCEEDED with rows → check per-source counts, then analyse├──► SUCCEEDED, 0 rows → READ THE LOG: no keyword / over-filtered / all sources empty└──► 2 jobs per source → free plan cap, not a data pull
💰 Cost Control & Pricing Transparency
Billing model: pay-per-event. You are charged per job delivered to the dataset, after de-duplication — so filtered-out and duplicate rows cost you nothing in event charges. Current rates are on the Actor's Apify Store page; the Console shows the live total mid-run.
| Choice | Effect on cost |
|---|---|
max_results | The direct lever on event charges — it caps what reaches the dataset. |
| Number of sources | Adds fetch time and API calls, not event charges — the cap still governs. |
| Narrow filters | Fewer delivered rows, so fewer charges — but the fetching still happened. |
indeed | The slowest source, and the only one needing a proxy. |
| Best-effort sources | Can cost run time and return nothing. Add them deliberately. |
| Free plan | 2 jobs per source, and no event charges at all. |
Worked examples — relative run shape
| Scenario | Sources | Jobs delivered | Notes |
|---|---|---|---|
Probe — 1 keyword, max_results: 10 | 10 defaults | ≤ 10 | Baseline. Confirms the keyword actually matches. |
Standard research — max_results: 100 | 10 defaults | ≤ 100 | ~10 per source after round-robin. |
Regional deep-dive — max_results: 300 | 10 + 2 regional | ≤ 300 | More sources, same billing ceiling. |
Everything on — max_results: 100 | all 26 | ≤ 100 | Same charge, longer run, more failure surface. |
Bulk feed — max_results: 2000 | 10 defaults | ≤ 2,000 | Set maxTotalChargeUsd. |
| Over-narrow keyword | 10 defaults | 0 | Near-zero event cost; the fetching still ran. |
Keeping runs predictable:
- First run:
max_results: 10on the default sources. Confirms the keyword matches before you scale. - Add sources for coverage, not volume —
max_resultscaps the output either way, so extra boards buy you diversity and cost you time. - Broaden the keyword rather than the source list when results are thin. Over-specific keywords are the most common cause of an empty run.
- Always set
maxTotalChargeUsdfor runs in the high hundreds or thousands. - Schedule small and often for monitoring, de-duplicating on
urlin your own store. - Free Apify plans get 2 jobs per source and are never charged.
🛠️ Troubleshooting
Most problems are an over-specific keyword, an opt-in source that's gone quiet, or a
misunderstanding of what null means — not scraper bugs.
| # | Symptom you see | Most likely cause | Fix |
|---|---|---|---|
| 1 | SUCCEEDED but the dataset is empty | keyword blank, or nothing matched after filtering | Read the log: Input is missing required field "keyword" or No jobs matched after filtering…. |
| 2 | Far fewer rows than Total raw suggests | Every keyword term must appear in the posting text | Shorten the keyword — "data engineer" beats "senior data engineer II". |
| 3 | One source shows 0 in the per-source counts | Best-effort board changed, or the region doesn't match the keyword | Expected. Drop it, or ignore it — the run isn't broken. |
| 4 | Log: [source] fetch failed, skipping this source | That board errored | Partial by design. Retry later, or disable that source. |
| 5 | indeed returns nothing | It needs a proxy | Set proxyConfiguration with a residential group. |
| 6 | talroo returns nothing | It ships no built-in access | Supply Publisher ID and Password, or remove it. |
| 7 | Enabling all 26 sources didn't give me more jobs | max_results caps the total regardless of source count | Raise max_results; sources buy diversity, not volume. |
| 8 | Fewer jobs per source than expected | Round-robin allocation spreads the cap evenly | Working as designed. Raise max_results. |
| 9 | Lots of remote: null | The board didn't say | Null ≠ office-based. Filter remote === true if you need certainty. |
| 10 | Most rows have no salary | Most postings don't publish one | Benchmark on the subset that does. |
| 11 | Salary averages look absurd | Mixed currencies in one aggregate | Group by salary_currency before averaging. |
| 12 | posted_date won't parse | Each board's format is passed through unchanged | Use scraped_at, or parse per source. |
| 13 | Results look loosely related to my keyword | Filtering is lenient — rows are kept unless they positively mismatch | Filter harder downstream; recall is deliberately favoured. |
| 14 | The same job appears twice | Different URLs on different boards defeat URL de-duplication | Dedupe further on title + company if needed. |
| 15 | An aborted run saved nothing | Results are written in one push at the end | Lower max_results so the run finishes. |
| 16 | Only 2 jobs per source | Free plan cap | Upgrade; the status message says so. |
| 17 | Every connector call fails validation | mcpArguments doesn't match the tool's real schema | Read Tool … expects arguments shaped like: in the log and copy that shape. The dataset is unaffected. |
| 18 | Slack / Notion received nothing | Delivery needs both mcpConnector and mcpTool, and deliveryMode defaults to none | Set all three. |
| 19 | HTTP 401 / 403 from the Apify API | Missing, expired or malformed APIFY_TOKEN | Regenerate in Console → Settings → API & Integrations. |
| 20 | HTTP 429 / 408 from the Apify API | Apify rate limits / the sync endpoint's time ceiling | Back off (2s → 4s → 8s), or switch to async POST /runs + polling. |
Diagnostic checklist before opening an issue
- Read the per-source counts —
Raw results per source before filtering:tells you instantly whether the problem is a source or your filters. - Compare
Total raw/after filter/after dedupe/pushed— the stage where the number collapses is the stage to fix. - Shorten the keyword to one word and re-run.
- Fall back to the ten defaults to rule out a best-effort source.
- Turn off
remote_onlyandjob_typeto see whether filtering is the constraint. - Check
country— a country-scoped source with the wrong country returns nothing.
If the issue survives all six, open an Issues ticket (or email support) with the run ID, the input you used, and what you expected.
❓ FAQ
Do I need API keys for any job board?
No. The ten default sources — Remotive, RemoteOK, Arbeitnow, Jobicy, We Work Remotely, The Muse, Adzuna, USAJobs, Reed and Jooble — work out of the box. The optional key fields exist so you can run on your own account's rate limits, or unlock talroo, which ships no built-in access.
Which sources should I actually enable?
Start with the ten defaults. Add regional boards only for the region you're searching, and add indeed only if you also set a proxy. Enabling all 26 doesn't multiply results — max_results caps the total either way — it multiplies the number of things that can go quiet.
What does "best-effort" mean?
That the source is an unofficial endpoint or a scraper with unverified selectors. It can return zero rows without warning when the site changes. Fourteen of the sixteen opt-ins are in this category — see Source Reliability.
Can I search several keywords in one run?
No. keyword is a single required string. Run the Actor once per keyword and combine the datasets — de-duplicating on url as you go.
Why did my specific keyword return almost nothing?
Because every whitespace-separated term must appear somewhere in the posting's title, company, description or tags. "senior data engineer" requires all three words; a posting titled Data Engineer II that never says "senior" is filtered out. Search broad, filter narrow downstream.
Is max_results per source or in total?
In total. It's then dealt out round-robin across the sources that returned rows, so one fast board can't consume the whole quota. With ten sources and max_results: 100, expect roughly ten from each.
What happens when a job board is down?
That source is logged as failed, contributes nothing, and the run continues with the others — still ending SUCCEEDED. Check Raw results per source before filtering: in the log to see who answered.
Why is remote null on so many rows?
Because the board didn't say. remote is deliberately tri-state: true, false, or null for unknown. remote_only drops a row only on a positive false, so unknowns stay in rather than being silently discarded.
Why do most jobs have no salary?
Because most postings don't publish one — that's the labour market, not the scraper. When a salary is present you get the raw string plus parsed salary_min, salary_max and salary_currency.
How should I benchmark salaries?
Filter to rows where salary_min (or salary_max) and salary_currency are both present, group by currency, then aggregate within each group. Averaging across currencies produces a meaningless number.
Why is posted_date inconsistent?
Each board publishes its own format and the value is passed through unchanged. scraped_at is ISO-8601 and is the only date you can sort on reliably across sources.
How are duplicates handled?
Postings are de-duplicated on url, falling back to title + company. A job syndicated across four boards lands once, attributed to whichever source appeared first. Genuinely different URLs for the same role can still slip through — dedupe further downstream if that matters.
Does the output contain personal data?
Barely. Company names are businesses, not individuals; no candidate, applicant or recruiter contact data is collected. The only realistic exposure is a job description that happens to name a hiring manager.
Can I republish the job listings I collect?
Check each board's terms first — several restrict redistribution and require attribution, which is exactly why source is on every row. Internal research, benchmarking and candidate matching sit on far safer ground than republishing a feed.
Does an aborted run keep partial results?
No. Every job is written in a single push after all sources have been fetched, filtered and de-duplicated, so an aborted or timed-out run saves nothing. Size max_results so the run completes.
Why does my MCP delivery fail with a validation error?
Because mcpArguments must match the connector tool's real argument shape exactly. Run once with mcpConnector and mcpTool set, then read the log line Tool … expects arguments shaped like: — it prints that tool's own JSON Schema. The dataset is saved regardless, so a failed delivery costs you nothing but the retry.
Which delivery mode should I use?
perJob for one Notion page or database row per job, chunked for large lists into services with request caps, summary for a single digest, none (the default) for dataset-only runs. Delivery only works on the Apify platform.
What does it cost, and what do I get on the free plan?
Pay-per-event: you're billed per job delivered to the dataset after de-duplication, so filtered and duplicate rows cost nothing in event charges. max_results is therefore your direct cost lever. Free Apify plans return 2 jobs per source and are not charged. Pass maxTotalChargeUsd for a hard per-run ceiling.
🔍 Companion machine-readable files
| File | What it defines |
|---|---|
| .actor/input_schema.json | Every input, the 26-source enum, the country list and the 15 secret key fields |
| .actor/dataset_schema.json | All 15 output fields with nullability, and the three Console views |
| .actor/output_schema.json | Where the run's output is exposed |
| .actor/actor.json | Actor identity, storage wiring and the environment variables backing the built-in keys |
If this page and the schema files disagree, the schema files win. They are what the platform validates against at run time; this page is documentation and can lag a release.
Related Actors by Techforce Global
| Actor | Best for |
|---|---|
| This Actor | Job listings aggregated and normalised across 26 boards |
| Clutch.co Scraper | B2B agency and service-provider profiles |
| Website Contact Scraper | Business contact details from an employer's own site |
| Website Content Crawler | Whole-site text, markdown or HTML for RAG and search |
| Smart Event Scraper | Event listings across six platforms |
A natural pairing: aggregate hiring companies here, then run Website Contact Scraper over their domains to reach the businesses directly.
Browse the full catalogue at scraper.techforce.global.
🔐 Compliance & Data Privacy
-
Public job postings only. No logins, no candidate or applicant records, no CVs, no recruiter contact details, no employer dashboards.
-
Very little personal data — by design. Company names are businesses. The only realistic exposure is a description that names a hiring manager or lists a direct contact; if you retain descriptions at scale, that's worth a filtering pass.
-
⚠️ Redistribution is the real constraint here. Several boards restrict republishing their listings, and several require attribution.
sourceandurlare on every row precisely so you can honour that. Check the terms of each board you enable — the obligations are theirs, not Apify's, and they differ. -
Ten sources are official or documented APIs; sixteen are best-effort. Using an official API means accepting its terms, including any limits on commercial reuse. Using a best-effort source means accepting both a technical risk (it can go quiet) and a terms risk (it isn't a sanctioned interface).
-
Salary data is as published, and mostly absent. Publishing "the average salary for X" from a run where most rows have no salary — or where currencies are mixed — produces a number that looks authoritative and isn't. Say how many rows your figure is based on, and in which currency.
-
Postings go stale fast. A filled or withdrawn role stays in your dataset until you re-run. Anything candidate-facing needs a refresh cadence and a visible capture date;
scraped_atis there for that. -
Aggregated hiring data can be sensitive to the employer. Layoffs, expansions and pivots are readable from job postings before they're announced. That's legitimate research — but treat conclusions about a named company with the care you'd apply to any inference.
-
Attribution over mirroring. Link back to
urlrather than presenting a board's listings as your own catalogue.
🆘 Support & Custom Pipeline Engineering
Need automated multi-city scheduled feeds, webhook enrichment, or enterprise data warehouse syncs?
- Email: bhavin.shah@techforceglobal.com
- Custom Enterprise Integrations: Book a 15-Minute Technical Consultation
- Maintained by: Techforce Global — Specialists in High-Performance Web Scrapers and Agentic Workflows.
🏷️ Structured data for search & AI discovery
SoftwareApplication describes the Actor; FAQPage wraps the FAQ above so those answers are
eligible for rich results, matching it question-for-question.
{"@context": "https://schema.org","@type": "SoftwareApplication","name": "All Jobs Scraper","alternateName": "Multi-Board Job Aggregator","applicationCategory": "BusinessApplication","applicationSubCategory": "Web Scraping / Job Aggregation & Labour Market Research","operatingSystem": "Cloud (Apify platform)","description": "Searches up to 26 job boards — Remotive, RemoteOK, Arbeitnow, Jobicy, We Work Remotely, The Muse, Adzuna, USAJobs, Reed, Jooble, Indeed and 15 regional boards — for a single keyword and returns one de-duplicated feed normalised to 15 fields: title, company, location, tri-state remote flag, normalised employment type, salary as published plus parsed numeric min, max and currency, description, posting URL, posted date and an ISO-8601 capture timestamp. The ten default sources need no API keys. Optional direct delivery into Notion, Slack, Airtable or any MCP connector.","url": "https://apify.com/techforce.global/all-jobs-scraper","author": {"@type": "Organization","name": "Techforce Global","url": "https://www.techforceglobal.com"},"provider": {"@type": "Organization","name": "Apify","url": "https://apify.com"},"offers": {"@type": "Offer","priceCurrency": "USD","category": "Pay per event","description": "Billed per job delivered to the dataset, after de-duplication. Current rates are shown on the Actor's Apify page.","url": "https://apify.com/techforce.global/all-jobs-scraper"},"featureList": ["26 job boards searchable from one keyword","Ten default sources that need no API keys","One normalised 15-field shape across every board","Cross-board de-duplication keyed on posting URL","Round-robin allocation so no single board consumes the result quota","Per-source failure isolation - one broken board never sinks the run","Salary as published plus parsed numeric min, max and currency","Tri-state remote flag that distinguishes unknown from office-based","Normalised employment type across inconsistent board vocabularies","Lenient filtering that keeps rows with missing data instead of discarding them","Per-row source attribution for board coverage comparison and required attribution","Optional bring-your-own API keys for higher limits, stored as secrets","Connector argument schema printed to the log to make MCP delivery configurable","Optional MCP delivery to Notion, Slack, Airtable and more","Three Console dataset views including salary and source breakdowns"],"isAccessibleForFree": false,"softwareRequirements": "Apify account and API token","keywords": "job scraper, job board aggregator, job search API, remote jobs, salary benchmarking, labour market data, recruitment data, Indeed scraper, Adzuna, USAJobs, Reed, job feed, MCP connector"}
{"@context": "https://schema.org","@type": "FAQPage","mainEntity": [{"@type": "Question","name": "Do I need API keys for any job board?","acceptedAnswer": {"@type": "Answer","text": "No. The ten default sources — Remotive, RemoteOK, Arbeitnow, Jobicy, We Work Remotely, The Muse, Adzuna, USAJobs, Reed and Jooble — work out of the box. The optional key fields exist so you can run on your own account's rate limits, or unlock talroo, which ships no built-in access."}},{"@type": "Question","name": "Which sources should I actually enable?","acceptedAnswer": {"@type": "Answer","text": "Start with the ten defaults. Add regional boards only for the region you're searching, and add indeed only if you also set a proxy. Enabling all 26 doesn't multiply results — max_results caps the total either way — it multiplies the number of things that can go quiet."}},{"@type": "Question","name": "What does \"best-effort\" mean?","acceptedAnswer": {"@type": "Answer","text": "That the source is an unofficial endpoint or a scraper with unverified selectors. It can return zero rows without warning when the site changes. Fourteen of the sixteen opt-ins are in this category."}},{"@type": "Question","name": "Can I search several keywords in one run?","acceptedAnswer": {"@type": "Answer","text": "No. keyword is a single required string. Run the Actor once per keyword and combine the datasets — de-duplicating on url as you go."}},{"@type": "Question","name": "Why did my specific keyword return almost nothing?","acceptedAnswer": {"@type": "Answer","text": "Because every whitespace-separated term must appear somewhere in the posting's title, company, description or tags. \"senior data engineer\" requires all three words; a posting titled Data Engineer II that never says \"senior\" is filtered out. Search broad, filter narrow downstream."}},{"@type": "Question","name": "Is max_results per source or in total?","acceptedAnswer": {"@type": "Answer","text": "In total. It's then dealt out round-robin across the sources that returned rows, so one fast board can't consume the whole quota. With ten sources and max_results: 100, expect roughly ten from each."}},{"@type": "Question","name": "What happens when a job board is down?","acceptedAnswer": {"@type": "Answer","text": "That source is logged as failed, contributes nothing, and the run continues with the others — still ending SUCCEEDED. Check 'Raw results per source before filtering:' in the log to see who answered."}},{"@type": "Question","name": "Why is remote null on so many rows?","acceptedAnswer": {"@type": "Answer","text": "Because the board didn't say. remote is deliberately tri-state: true, false, or null for unknown. remote_only drops a row only on a positive false, so unknowns stay in rather than being silently discarded."}},{"@type": "Question","name": "Why do most jobs have no salary?","acceptedAnswer": {"@type": "Answer","text": "Because most postings don't publish one — that's the labour market, not the scraper. When a salary is present you get the raw string plus parsed salary_min, salary_max and salary_currency."}},{"@type": "Question","name": "How should I benchmark salaries?","acceptedAnswer": {"@type": "Answer","text": "Filter to rows where salary_min (or salary_max) and salary_currency are both present, group by currency, then aggregate within each group. Averaging across currencies produces a meaningless number."}},{"@type": "Question","name": "Why is posted_date inconsistent?","acceptedAnswer": {"@type": "Answer","text": "Each board publishes its own format and the value is passed through unchanged. scraped_at is ISO-8601 and is the only date you can sort on reliably across sources."}},{"@type": "Question","name": "How are duplicates handled?","acceptedAnswer": {"@type": "Answer","text": "Postings are de-duplicated on url, falling back to title + company. A job syndicated across four boards lands once, attributed to whichever source appeared first. Genuinely different URLs for the same role can still slip through — dedupe further downstream if that matters."}},{"@type": "Question","name": "Does the output contain personal data?","acceptedAnswer": {"@type": "Answer","text": "Barely. Company names are businesses, not individuals; no candidate, applicant or recruiter contact data is collected. The only realistic exposure is a job description that happens to name a hiring manager."}},{"@type": "Question","name": "Can I republish the job listings I collect?","acceptedAnswer": {"@type": "Answer","text": "Check each board's terms first — several restrict redistribution and require attribution, which is exactly why source is on every row. Internal research, benchmarking and candidate matching sit on far safer ground than republishing a feed."}},{"@type": "Question","name": "Does an aborted run keep partial results?","acceptedAnswer": {"@type": "Answer","text": "No. Every job is written in a single push after all sources have been fetched, filtered and de-duplicated, so an aborted or timed-out run saves nothing. Size max_results so the run completes."}},{"@type": "Question","name": "Why does my MCP delivery fail with a validation error?","acceptedAnswer": {"@type": "Answer","text": "Because mcpArguments must match the connector tool's real argument shape exactly. Run once with mcpConnector and mcpTool set, then read the log line 'Tool … expects arguments shaped like:' — it prints that tool's own JSON Schema. The dataset is saved regardless, so a failed delivery costs you nothing but the retry."}},{"@type": "Question","name": "Which delivery mode should I use?","acceptedAnswer": {"@type": "Answer","text": "perJob for one Notion page or database row per job, chunked for large lists into services with request caps, summary for a single digest, none (the default) for dataset-only runs. Delivery only works on the Apify platform."}},{"@type": "Question","name": "What does it cost, and what do I get on the free plan?","acceptedAnswer": {"@type": "Answer","text": "Pay-per-event: you're billed per job delivered to the dataset after de-duplication, so filtered and duplicate rows cost nothing in event charges. max_results is therefore your direct cost lever. Free Apify plans return 2 jobs per source and are not charged. Pass maxTotalChargeUsd for a hard per-run ceiling."}}]}