All Jobs Scraper - 26 Job Boards, One Schema, Parsed Salaries avatar

All Jobs Scraper - 26 Job Boards, One Schema, Parsed Salaries

Pricing

from $3.10 / 1,000 results

Go to Apify Store
All Jobs Scraper - 26 Job Boards, One Schema, Parsed Salaries

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

Techforce Global

Maintained by Community

Actor stats

3

Bookmarked

4

Total users

4

Monthly active users

11 days ago

Last modified

Share

All Jobs Scraper — 26 Job Boards Into One De-Duplicated Feed

Apify Actor MCP Ready Pricing Sources Output Keys

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

CredentialsNone 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 scopePublic job postings only. No candidate data, no applicant records, no recruiter contact details, no logins.
Personal dataEssentially none. Company names are businesses; job descriptions occasionally name a hiring manager, which is the only realistic exposure.
⚠️ Source quality variesTen 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.
⚠️ RedistributionSeveral 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:

TaskDoing it yourselfAll 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 typeFULL_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 feed
normalised to 15 fields - title, company, location, tri-state remote flag, normalised
employment type, salary as published plus parsed min/max/currency, description, URL, posted
date and capture timestamp. The ten default sources need no API keys. Optional delivery into
Notion, 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 Actor
once 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 unverified
selectors that can legitimately return zero rows when a site changes. arbetsformedlingen is
official but Sweden-only, and talroo needs your own Publisher ID/Password and returns nothing
without them. Enabling every source does not multiply your results; it multiplies your
failure surface.
failures_are_isolated_per_source: >
A source that errors is logged as '[source] fetch failed, skipping this source' and returns
nothing. 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 the
sources that returned rows, so one fast board cannot consume the whole quota. With 5 sources
and 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 parsed
salary_min / salary_max / salary_currency. NEVER average across currencies - group by
salary_currency first.
filtering_is_lenient_by_design: >
Rows are dropped only on a positive mismatch, never on missing data, because boards populate
different 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 - unlike
scrapers 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-signal
research, 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, guaranteed
completeness 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 the
second attempt instead of guessing.
never_blocks_the_dataset: >
Delivery runs after the dataset is saved. A wrong argument shape fails only the delivery
calls - 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; remote is a tri-state boolean; salary arrives both as the published string and as parsed salary_min / salary_max / salary_currency.

  • De-duplicated on the posting URL, so a job syndicated across four boards lands once — with source recording which board served it.

  • Fair allocation across sources. The final max_results is 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 mcpArguments stops 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

SourceScopeWhy it's opt-in
indeedGlobalHeavily rate-limited; needs a proxy
arbeitsagenturGermanyBest-effort; needs a key
france_travailFranceBest-effort
vdabBelgiumBest-effort
baytMENABest-effort
cv_libraryUKBest-effort
founditIndiaBest-effort
jobbankCanadaBest-effort
jobs_chSwitzerlandBest-effort
kariyerTurkeyBest-effort
onlinejobsphPhilippinesBest-effort
glintsSoutheast AsiaBest-effort
talent_comGlobalBest-effort
freelancerGlobal (contract)Best-effort
arbetsformedlingenSwedenOfficial, but Sweden-only
talrooUSReturns 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

ScenarioFit
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 runkeyword is a single string — one run each
Submitting applications❌ Read-only

⚡ Execution Rules (Avoid Common Agent Failures)

  1. keyword is required and singular. It's the only required input, and it's a string. An empty one logs Input is missing required field "keyword". Exiting. and ends SUCCEEDED with an empty dataset. For several titles, run once per keyword.

  2. 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.

  3. max_results is the total across all sources, not per source. Default 100, range 105000. It's dealt out round-robin, so with ten sources and max_results: 100 you get roughly ten from each — not 100 from the fastest.

  4. 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.

  5. A source returning zero rows is normal, not a failure. Check Raw results per source before filtering: in the log to see who contributed.

  6. remote is tri-state. true / false / null, and null means the board didn't say. remote_only drops a row only on a positive false, so unknowns are kept — filter harder yourself if you need certainty.

  7. Salary is usually absent, and always multi-currency. Use salary_min / salary_max only alongside salary_currency, and never average across currencies.

  8. posted_date is not normalised. Each board's own format is passed through. scraped_at is the only reliable ISO-8601 timestamp on the row.

  9. 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.

  10. 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.

  11. proxyConfiguration matters only for Indeed. The other sources are plain APIs. Leaving it off is fine unless indeed is enabled — and Indeed without a proxy is heavily blocked.

  12. 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_results so the run finishes.

  13. deliveryMode defaults to none. Delivery is opt-in here. When you do enable it, run once and read the log line Tool … expects arguments shaped like: before writing mcpArguments.

  14. 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.

ParameterTypeRequiredDefaultNotes
keywordstringYesConsole prefill onlyJob title, skill or company. One per run
locationstring⬜ No""Free-text city or region. Blank = broad/remote search
countrystring (enum)⬜ No"" (Any)~95 countries. Used by country-scoped sources (Adzuna, USAJobs) and as a location fallback
remote_onlyboolean⬜ NofalseKeeps rows the source says are remote, plus unknowns
job_typestring (enum)⬜ Noallall · fulltime · parttime · contract · internship
max_resultsinteger⬜ No100105000, total across all sources, allocated round-robin
sourcesarray⬜ Nothe 10 defaultsPick from 26 — see Source Reliability
proxyConfigurationobject⬜ 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)

ParameterTypeDefaultDescription
mcpConnectorstring""A connector you've authorised on your Apify account — Notion, Slack, Linear, Jira, GitHub, Airtable, or any MCP-compatible connector.
deliveryModestring (enum)noneperJob (one call per job — best for one Notion page/row each) · chunked (split a large list) · summary (one call, all jobs) · none.
mcpToolstring""Tool to call — notion-create-pages, send_message, create_issue. Required when a connector is set.
mcpArgumentsobject{}Arguments for the tool. String leaves support {placeholder} tokens. Must match the tool's real argument shape.
mcpMessageTemplatestring""Template rendered and exposed to mcpArguments as {message}.

Placeholders by mode:

ModePlaceholders
perJob{title} {company} {location} {url} {source} {jobType} {remote} {salary} {description} {postedDate} {tags} {keyword} {jobCount} {message}
summary{jobsText} (the formatted job list) {keyword} {jobCount} {message}
chunkedthe same, plus {part} {partCount}{jobsText} holds one part

Getting mcpArguments right first time is unrealistic — so don't guess twice. Run once with mcpConnector and mcpTool set, then read the log line Tool '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.

FieldTypeNullableMeaning
sourcestringnoWhich board served this listing. Keep it — several boards require attribution
titlestringnoJob title
companystringyesHiring company
locationstringyesLocation as the source gave it — not normalised
remotebooleanyesTri-state: true / false / null. Null means unknown, not office-based
job_typestringnoNormalised: fulltime, parttime, contract, internship or unknown
salarystringyesHuman-readable salary as published. Usually absent
salary_minnumberyesParsed lower bound, when one could be derived
salary_maxnumberyesParsed upper bound
salary_currencystringyesCurrency of the parsed values. Always group by this before averaging
descriptionstringyesPlain-text description or excerpt
urlstringnoLink to the original posting. The de-duplication key
posted_datestringyesAs the source gave it — format varies by board
tagsarraynoBoard-supplied tags. [] when none
scraped_atstringnoISO-8601 capture timestamp — the only reliable date on the row

The three fields people misread: remote: null is unknown, a missing salary is the norm rather than an error, and posted_date is whatever the board printed — use scraped_at for 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, company or posted_date are null; 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: [...], then Raw results per source before filtering: {...}, then Total raw: N, after filter: N, after dedupe: N, pushed: N
  • Zero results is also a success. A keyword too narrow, remote_only on a keyword with no remote roles, or every enabled source coming back empty all end SUCCEEDED with 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_results is normal for niche keywords.

Dataset views

ViewShows
OverviewSource, title, company, location, remote, type, salary, posted date, link
Salary & TypeTitle, company, salary string plus parsed min/max/currency, type, remote, location
By SourceSource, title, company, location, dates, tags — for board coverage comparison

▶️ Invocation & Integration

Authenticate every call with Authorization: Bearer <APIFY_TOKEN>.

Apify Console

Open the Actor pageInput 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 os
from collections import Counter, defaultdict
from statistics import median
from apify_client import ApifyClient
client = 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'
# 1. Start with a hard spend limit
curl -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 SUCCEEDED
curl -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.

ConditionWhat you observeCauseDeterministic agent action
MISSING_KEYWORDSUCCEEDED, empty dataset; log: Input is missing required field "keyword". Exiting.keyword blankModify Input: supply a non-empty keyword.
KEYWORD_TOO_SPECIFICVery few rows; after filter far below Total rawEvery keyword term must appear in the textModify Input: shorten the keyword; filter downstream instead.
NO_JOBS_AFTER_FILTERINGSUCCEEDED, empty dataset; log: No jobs matched after filtering…Keyword, location, remote_only or job_type too restrictiveModify Input: broaden one constraint at a time.
SOURCE_RETURNED_ZEROA source shows 0 in Raw results per sourceBest-effort board changed, region mismatch, or no matchesExpected. Not a failure — drop the source or ignore it.
SOURCE_FETCH_FAILEDLog: [source] fetch failed, skipping this sourceThat board errored; the run continuesExpected, partial. Retry later or disable it.
UNKNOWN_SOURCE_IGNOREDLog: Ignoring unknown source(s) in inputA misspelled source keyModify Input: use a key from the enum.
ALL_SOURCES_INVALIDLog: No valid sources selected - falling back to defaultsEvery key was unrecognisedExpected fallback. Fix the keys to control coverage.
TALROO_EMPTYtalroo contributes nothingIt ships no built-in accessModify Input: supply Publisher ID and Password, or remove it.
INDEED_EMPTYindeed contributes little or nothingNo proxy configuredModify Input: {"useApifyProxy": true} with a residential group.
PROXY_SETUP_FAILEDLog: Failed to create proxy configuration…Not logged in locally, or the plan lacks that proxy groupPrompt user: check plan and credentials; other sources still work.
ONE_SOURCE_DOMINATESExpected a spread, got a spread — but fewer per source than hopedRound-robin allocation is working as designedFix expectation, or raise max_results.
REMOTE_IS_NULLMany rows have remote: nullThe board didn't sayExpected. Null ≠ office-based. Filter harder if you need certainty.
SALARY_MISSINGMost rows have no salaryMost postings don't publish oneExpected. Benchmark on the subset that does.
MIXED_CURRENCIESSalary figures look wildly inconsistentMulti-currency datasetFix consumer: group by salary_currency before any aggregate.
POSTED_DATE_UNPARSEABLEposted_date won't parse consistentlyEach board's own format is passed throughFix consumer: use scraped_at, or parse per source.
NO_DATA_AFTER_ABORTAn aborted run left an empty datasetResults are written in one push at the endExpected. Lower max_results so the run completes.
FREE_PLAN_CAPPED2 jobs per source; status message says soFree Apify planUpgrade the plan.
DELIVERY_VALIDATION_ERROREvery connector call fails with an MCP input validation errormcpArguments doesn't match the tool's schemaRead the log: Tool … expects arguments shaped like: prints it. Dataset unaffected.
CONNECTOR_TOOL_NOT_FOUNDLog: tool not available, followed by the available toolsWrong mcpTool nameModify Input: copy an exact name from that log line.
CONNECTOR_UNREACHABLELog: Could not reach the connector: …Connector not authorised, or running locallyAuthorise it, or set deliveryMode: "none". Data already saved.
AUTH_INVALIDHTTP 401Missing or malformed APIFY_TOKENAbort & prompt user for a valid token.
RATE_LIMITED_APIFYHTTP 429Apify API rate / concurrency limitsRetry: exponential backoff (2s, 4s, 8s).
SYNC_TIMEOUTHTTP 408 on the sync endpointRun exceeded the synchronous ceilingReconfigure: 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…IntentConstructed 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 benchmarkScrape, then group by salary_currency and aggregate salary_min/salary_max
"Which companies are hiring for X?"Hiring signalScrape, then count by company
"Which board has the most X roles?"Coverage comparisonScrape, then count by source
"Add German and French boards"Regional coverageAppend arbeitsagentur, france_travail to sources
"Include Indeed"Extra sourceAdd indeed plus proxyConfiguration
"Government jobs only"Narrow source{"sources":["usajobs"]} — US federal roles
"Alert me to new postings"MonitoringFixed input on a schedule; de-duplicate on url
"Search these five job titles"Multi-keywordFive runskeyword takes one string
"Put these jobs in Notion"Scrape + deliverAbove, plus mcpConnector, mcpTool, deliveryMode: "perJob"
"It returned nothing"Empty resultShorten 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.

ChoiceEffect on cost
max_resultsThe direct lever on event charges — it caps what reaches the dataset.
Number of sourcesAdds fetch time and API calls, not event charges — the cap still governs.
Narrow filtersFewer delivered rows, so fewer charges — but the fetching still happened.
indeedThe slowest source, and the only one needing a proxy.
Best-effort sourcesCan cost run time and return nothing. Add them deliberately.
Free plan2 jobs per source, and no event charges at all.

Worked examples — relative run shape

ScenarioSourcesJobs deliveredNotes
Probe — 1 keyword, max_results: 1010 defaults≤ 10Baseline. Confirms the keyword actually matches.
Standard research — max_results: 10010 defaults≤ 100~10 per source after round-robin.
Regional deep-dive — max_results: 30010 + 2 regional≤ 300More sources, same billing ceiling.
Everything on — max_results: 100all 26≤ 100Same charge, longer run, more failure surface.
Bulk feed — max_results: 200010 defaults≤ 2,000Set maxTotalChargeUsd.
Over-narrow keyword10 defaults0Near-zero event cost; the fetching still ran.

Keeping runs predictable:

  1. First run: max_results: 10 on the default sources. Confirms the keyword matches before you scale.
  2. Add sources for coverage, not volumemax_results caps the output either way, so extra boards buy you diversity and cost you time.
  3. Broaden the keyword rather than the source list when results are thin. Over-specific keywords are the most common cause of an empty run.
  4. Always set maxTotalChargeUsd for runs in the high hundreds or thousands.
  5. Schedule small and often for monitoring, de-duplicating on url in your own store.
  6. 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 seeMost likely causeFix
1SUCCEEDED but the dataset is emptykeyword blank, or nothing matched after filteringRead the log: Input is missing required field "keyword" or No jobs matched after filtering….
2Far fewer rows than Total raw suggestsEvery keyword term must appear in the posting textShorten the keyword — "data engineer" beats "senior data engineer II".
3One source shows 0 in the per-source countsBest-effort board changed, or the region doesn't match the keywordExpected. Drop it, or ignore it — the run isn't broken.
4Log: [source] fetch failed, skipping this sourceThat board erroredPartial by design. Retry later, or disable that source.
5indeed returns nothingIt needs a proxySet proxyConfiguration with a residential group.
6talroo returns nothingIt ships no built-in accessSupply Publisher ID and Password, or remove it.
7Enabling all 26 sources didn't give me more jobsmax_results caps the total regardless of source countRaise max_results; sources buy diversity, not volume.
8Fewer jobs per source than expectedRound-robin allocation spreads the cap evenlyWorking as designed. Raise max_results.
9Lots of remote: nullThe board didn't sayNull ≠ office-based. Filter remote === true if you need certainty.
10Most rows have no salaryMost postings don't publish oneBenchmark on the subset that does.
11Salary averages look absurdMixed currencies in one aggregateGroup by salary_currency before averaging.
12posted_date won't parseEach board's format is passed through unchangedUse scraped_at, or parse per source.
13Results look loosely related to my keywordFiltering is lenient — rows are kept unless they positively mismatchFilter harder downstream; recall is deliberately favoured.
14The same job appears twiceDifferent URLs on different boards defeat URL de-duplicationDedupe further on title + company if needed.
15An aborted run saved nothingResults are written in one push at the endLower max_results so the run finishes.
16Only 2 jobs per sourceFree plan capUpgrade; the status message says so.
17Every connector call fails validationmcpArguments doesn't match the tool's real schemaRead Tool … expects arguments shaped like: in the log and copy that shape. The dataset is unaffected.
18Slack / Notion received nothingDelivery needs both mcpConnector and mcpTool, and deliveryMode defaults to noneSet all three.
19HTTP 401 / 403 from the Apify APIMissing, expired or malformed APIFY_TOKENRegenerate in Console → Settings → API & Integrations.
20HTTP 429 / 408 from the Apify APIApify rate limits / the sync endpoint's time ceilingBack off (2s → 4s → 8s), or switch to async POST /runs + polling.

Diagnostic checklist before opening an issue

  1. Read the per-source countsRaw results per source before filtering: tells you instantly whether the problem is a source or your filters.
  2. Compare Total raw / after filter / after dedupe / pushed — the stage where the number collapses is the stage to fix.
  3. Shorten the keyword to one word and re-run.
  4. Fall back to the ten defaults to rule out a best-effort source.
  5. Turn off remote_only and job_type to see whether filtering is the constraint.
  6. 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

FileWhat it defines
.actor/input_schema.jsonEvery input, the 26-source enum, the country list and the 15 secret key fields
.actor/dataset_schema.jsonAll 15 output fields with nullability, and the three Console views
.actor/output_schema.jsonWhere the run's output is exposed
.actor/actor.jsonActor 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.


ActorBest for
This ActorJob listings aggregated and normalised across 26 boards
Clutch.co ScraperB2B agency and service-provider profiles
Website Contact ScraperBusiness contact details from an employer's own site
Website Content CrawlerWhole-site text, markdown or HTML for RAG and search
Smart Event ScraperEvent 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. source and url are 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_at is 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 url rather 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?



🏷️ 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."
}
}
]
}