Job Board Aggregator — Multi-Platform Job Listing Scraper
Pricing
from $50.00 / 1,000 job postings
Job Board Aggregator — Multi-Platform Job Listing Scraper
One feed for the whole hiring market. Aggregate live job listings across LinkedIn, Glassdoor, and ZipRecruiter with salary normalization and skill extraction — spot hiring trends and comp benchmarks by role or company. Pay per result.
Pricing
from $50.00 / 1,000 job postings
Rating
0.0
(0)
Developer
Creator Fusion
Maintained by CommunityActor stats
0
Bookmarked
98
Total users
21
Monthly active users
5 days ago
Last modified
Share
Job Board Aggregator — Indeed, LinkedIn, Glassdoor & ZipRecruiter API for Recruiting & Lead-Gen Agents
Job-market data API that scrapes Indeed, Glassdoor, ZipRecruiter, and LinkedIn in one call and returns typed JSON rows — schema below. Every listing is normalized to a single 35-field contract (salary range + annualized estimate, skills, experience level, education requirement, benefits) and deduplicated across boards. Built for recruiting, lead-gen, and market-research agents that need multi-board coverage without stitching four scrapers together.
Why agents use this actor
- Deterministic typed output — every row follows the dataset schema below; stable
idhash per posting, consistent field names regardless of source board. - Multi-board aggregation in one call — one input, four job boards, cross-board deduplication. No fan-out orchestration on your side.
- Parsed intelligence, not raw HTML — salary min/max/period/currency plus
estimatedAnnualSalary,technicalSkills/softSkillsextraction,experienceLevel,yearsExperienceRequired,educationRequired. - Clear error semantics — invalid input or zero results exits non-zero with a status message; per-site failures are reported in the
SUMMARYkey-value record, never as billable dataset rows. - No auth or cookies needed — public listings only; per-site resilience built in (
scrapeIndividuallyisolates board failures).
Input schema
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
searchTerm | string | yes | — (prefill: software engineer) | Job title or keywords to search for |
location | string | yes | — (prefill: Austin, TX) | City, state, or country for job search |
sites | array | no | ["glassdoor", "zip_recruiter", "linkedin"] | Boards to scrape. Allowed: indeed, glassdoor, zip_recruiter, linkedin |
resultsPerSite | integer | no | 25 | Max results per site (1–100) |
jobType | string | no | "" | Filter: fulltime, parttime, contract, internship, or "" for all |
isRemote | boolean | no | false | Only return remote positions |
distanceMiles | integer | no | 50 | Search radius from location (5–100) |
hoursOld | integer | no | 168 | Only jobs posted within this many hours (1–720) |
countryIndeed | string | no | usa | Indeed country: usa, uk, canada, germany, france, india, australia |
scrapeIndividually | boolean | no | true | Scrape each board separately so one board's failure doesn't kill the run |
enableDeduplication | boolean | no | true | Remove duplicate listings across boards (title+company+location hash) |
extractSkills | boolean | no | true | Extract technical and soft skills from descriptions |
proxyConfiguration | object | no | Apify residential proxy | Proxy settings. Residential is the default because Glassdoor and LinkedIn block datacenter IPs; residential traffic is billed to your Apify account |
Output schema
Single mode: every dataset row is one normalized job listing with all fields below. Fields the source board doesn't provide are null (or empty string / empty array), never omitted.
| Field | Type | Nullable | Description |
|---|---|---|---|
id | string | no | Stable 16-char hash of title+company+location |
source | string | no | indeed, glassdoor, zip_recruiter, or linkedin |
title | string | no | Job title |
company | string | no | Company name |
location | string | no | Location as posted |
jobType | string | yes | fulltime, parttime, contract, internship, or null |
isRemote | boolean | no | Board-flagged remote position |
experienceLevel | string | yes | entry, mid, senior, executive, or null (inferred) |
yearsExperienceRequired | integer | yes | Min years parsed from description |
educationRequired | string | yes | HighSchool, Associates, Bachelors, Masters, PhD, or null |
salaryMin | number | yes | Posted salary lower bound |
salaryMax | number | yes | Posted salary upper bound |
salaryPeriod | string | yes | hourly, weekly, monthly, yearly, or null |
salaryCurrency | string | no | Currency code (default USD) |
estimatedAnnualSalary | number | yes | Range midpoint annualized from salaryPeriod |
technicalSkills | array<string> | no | Detected tech skills (empty if extractSkills false) |
softSkills | array<string> | no | Detected soft skills |
allSkills | array<string> | no | technicalSkills + softSkills |
skillsCount | integer | no | Total skills detected |
description | string | no | Description text, truncated to 5000 chars (often empty on LinkedIn) |
descriptionLength | integer | no | Full description length in chars |
requirements | array<string> | no | Parsed requirement phrases (max 10) |
benefits | array<string> | no | Detected benefit keywords (401k, pto, equity, ...) |
companyLogo | string | yes | Logo URL |
companyRating | number | yes | Company rating (mainly Glassdoor) |
companyIndustry | string | yes | Industry |
companySize | string | yes | Headcount range |
companyUrl | string | yes | Company profile URL on the source board |
postedDate | string | no | YYYY-MM-DD (empty string when unknown) |
scrapedAt | string | no | ISO 8601 UTC scrape timestamp |
applyUrl | string | no | Direct URL to the posting |
sourceUrl | string | no | Same as applyUrl |
hasRemoteOption | boolean | no | Description mentions remote / work from home |
hasVisaSponsorship | boolean | no | Description mentions visa / sponsorship |
isEasyApply | boolean | no | Board-flagged one-click apply |
Real example row (from a live run):
{"id": "98ffae5ccb0ec729","source": "linkedin","title": "Software Engineer, Realtime","company": "Cloudflare","location": "Austin, TX","jobType": null,"isRemote": false,"experienceLevel": null,"yearsExperienceRequired": null,"educationRequired": null,"salaryMin": null,"salaryMax": null,"salaryPeriod": null,"salaryCurrency": "USD","estimatedAnnualSalary": null,"technicalSkills": [],"softSkills": [],"allSkills": [],"skillsCount": 0,"description": "","descriptionLength": 0,"requirements": [],"benefits": [],"companyLogo": null,"companyRating": null,"companyIndustry": null,"companySize": null,"companyUrl": "https://www.linkedin.com/company/cloudflare","postedDate": "2026-08-13","scrapedAt": "2026-08-15T01:34:22.114155","applyUrl": "https://www.linkedin.com/jobs/view/4441070330","sourceUrl": "https://www.linkedin.com/jobs/view/4441070330","hasRemoteOption": false,"hasVisaSponsorship": false,"isEasyApply": false}
Error semantics
- Invalid input (empty
searchTerm/location, emptysites): the run exits with code 1 and a status message explaining what's missing. No dataset items. - Zero results (all boards blocked, errored, or no matching jobs): the run exits with code 1 and a per-site status message — never a silent SUCCEEDED with 0 items. Retry with different boards, a broader search, or proxies.
- Partial failure (some boards succeed, some fail): the run SUCCEEDS with the rows it got. Per-site outcomes (
success/empty/error+ error text) are in theSUMMARYkey-value record (also stored assummaryfor backward compatibility) undersiteStatus. Failed sites never produce dataset rows, so you're never billed for errors. - Retry guidance for agents: check
SUMMARY.siteStatus; a403on Glassdoor means enable residential proxy; LinkedInempty/401is rate limiting — retry later or lowerresultsPerSite.
Use from AI agents (MCP)
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?tools=apricot_blackberry/job-board-aggregator","headers": { "Authorization": "Bearer <YOUR_APIFY_TOKEN>" }}}}
Works in Claude, Cursor, ChatGPT deep research connectors, and any MCP client; the input schema above is the tool's parameter schema.
Use from code
curl:
curl -X POST "https://api.apify.com/v2/acts/apricot_blackberry~job-board-aggregator/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"searchTerm": "software engineer", "location": "Austin, TX", "sites": ["glassdoor", "zip_recruiter", "linkedin"], "resultsPerSite": 10}'
JavaScript (apify-client):
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('apricot_blackberry/job-board-aggregator').call({searchTerm: 'software engineer',location: 'Austin, TX',sites: ['glassdoor', 'zip_recruiter', 'linkedin'],resultsPerSite: 10,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Python (apify_client):
from apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("apricot_blackberry/job-board-aggregator").call(run_input={"searchTerm": "software engineer","location": "Austin, TX","sites": ["glassdoor", "zip_recruiter", "linkedin"],"resultsPerSite": 10,})items = client.dataset(run["defaultDatasetId"]).list_items().items
Use from automation platforms
- n8n / Make / Zapier: use the native Apify integration and choose
job-board-aggregatorby name. - LangChain / LlamaIndex: use the Apify tool wrappers (
ApifyWrapper/ApifyActor) with the same input JSON. - Webhooks: configure an Apify webhook to fire on run completion (
ACTOR.RUN.SUCCEEDED/FAILED) to push new listings into your ATS or CRM pipeline without polling.
Supported job sites
- Indeed — largest job board, best coverage, most reliable (7+ countries via
countryIndeed) - Glassdoor — includes company ratings; aggressive anti-bot, needs residential proxy
- ZipRecruiter — good for SMB jobs
- LinkedIn — professional network jobs; strictest rate limits (~10 pages per IP), list endpoint returns no descriptions
Rate limits & best practices
- LinkedIn has the strictest rate limits; Indeed is the most reliable with minimal rate limiting.
- Recommended: 25–50 results per site per run; use scheduled runs for continuous monitoring.
- Keep
scrapeIndividuallyon so one blocked board doesn't sink the run.
Pricing
$0.10 start + $0.05 per job listing delivered (PAY_PER_EVENT). A default run (3 sites × 25 results/site, deduplicated) typically lands around $2–4; residential proxy usage (the default) is billed to your Apify account on top of this.
FAQ
Why are some fields null/empty? Boards report different data. LinkedIn's list endpoint returns no descriptions, so description-derived fields (skills, requirements, education) are empty for LinkedIn rows.
How does deduplication work? MD5 hash of lowercased title+company+location(first 20 chars); first occurrence wins. Duplicate count is in the SUMMARY record.
Do I need an account on the job boards? No — public listings only, no login or cookies.
Changelog
v2.1 (2026-08)
- Output dataset schema published (
.actor/dataset_schema.json) — typed, agent-readable contract for all 35 fields. - Agent integration docs: MCP server config, curl/JS/Python snippets.
indeedadded to thesitesinput enum (was documented but not selectable).- Fail-loud semantics: invalid input or zero total results now exits with code 1 and a descriptive status message instead of silently succeeding with 0 items.
- Run summary now also stored under the standard
SUMMARYkey-value record (existingsummaryrecord kept for backward compatibility).
v2.0
- Per-site individual scraping (
scrapeIndividually) with per-site success/failure status. - Proxy rotation support; skill/requirement/benefit extraction; cross-board deduplication.