Job Posting Monitor avatar

Job Posting Monitor

Pricing

from $17.00 / 1,000 results

Go to Apify Store
Job Posting Monitor

Job Posting Monitor

Finds companies hiring for your keywords on Google Jobs (with your SerpAPI key) and free job boards. One flat Clay-ready row per posting with company domain and LinkedIn URL. Optional: the full job description, a stable posting_key, and a row when a posting closes or is reposted.

Pricing

from $17.00 / 1,000 results

Rating

0.0

(0)

Developer

Mamba Labs

Mamba Labs

Maintained by Community

Actor stats

0

Bookmarked

5

Total users

3

Monthly active users

7 hours ago

Last modified

Share

📋 What can Job Posting Monitor do?

Give it role keywords and it returns the companies hiring for that role right now, one clean row per posting, with each company enriched with its domain, LinkedIn URL, employee count and industry.

This is the reverse of a domain-level ATS checker. You do not give it a company and ask "are they hiring?" You give it a role and ask "who is hiring for this?"

📦 What you get⚙️ Features and integrations
🏢 Hiring companies, deduplicated to one row per posting
🔗 Domain and LinkedIn URL resolved per company
🧹 Staffing agencies and freelance marketplaces filtered out
🧾 43 flat fields, snake_case, Clay-native
🔍 Google Jobs discovery via your own SerpAPI key
🏷️ Company type classification on every row
🔄 Cross-run delta detection, only new postings
⬇️ Export to JSON, CSV, Excel, HTML or XML

Bought by anyone running role-triggered outbound: recruiters, staffing-adjacent vendors, and sellers whose buying signal is a specific role opening.

🚫 The keyword list is fully generic, and the editorial default is just a preset. It ships with nine editorial and content titles (proofreader, copy editor, content editor, copywriter, content writer, technical writer, editorial, content marketing, content strategist) because that was the first use case, but the same actor tracks engineering, sales, marketing, operations or finance by changing the keywords. The Input tab prefill runs that preset on three keyless boards (Jobicy, Remotive and Himalayas), so it returns real postings with no key.

💡 Why use Job Posting Monitor?

If you wantRead these fields
The hiring companycompany_name, company_domain
An outreach targetcompany_linkedin_url
To size the accountemployee_count, industry
To drop the wrong kind of companycompany_type
The role and where to applyjob_title, job_url, location, is_remote
Recency and noveltyposting_date, is_new_posting
Why this row matchedmatched_keyword, job_description_snippet

🧹 Filtering out the noise is most of the value

Raw job search results are dominated by staffing agencies and freelance marketplaces reposting the same roles. This actor filters both.

Staffing and recruitment agencies are filtered on both the company name and the job description text, because plenty of agencies do not have "recruitment" in their name. Freelance marketplaces (Upwork, Fiverr, Freelancer, Toptal and similar) are excluded by name, and the list is yours to extend.

Everything that survives is classified: direct_employer, staffing_agency, government or unknown. If you disagree with the filter, turn it off and filter on company_type yourself.

🔄 Re-runs only return what is new

is_new_posting and the built-in delta cache mean a scheduled run does not re-emit everything it already showed you. Pass previous_run_date if you would rather control the window yourself.

📋 What data can Job Posting Monitor extract?

16 fields per posting.

FieldWhat it holds
company_nameThe hiring company
company_domainResolved canonical domain, or null
company_linkedin_urlCompany LinkedIn page, the primary outreach field
employee_count, industryFirmographics, where the company exposes them
company_typedirect_employer, staffing_agency, government or unknown
job_titleThe role title
job_urlDirect apply or posting URL
posting_dateNormalized to ISO, best effort
source_boardgoogle_jobs, greenhouse, lever or ashby
location, is_remoteWhere the role is
job_description_snippetFirst 500 characters of the description
matched_keywordWhich of your keywords this posting matched
is_new_postingtrue when not seen in a prior run
run_dateWhen the run happened

⚠️ employee_count and industry are frequently null, because the firmographic source only reports them when a company exposes them. The company_size_min and company_size_max filters therefore apply only when a size is known, and will silently keep companies of unknown size. company_linkedin_url, the field most people actually act on, resolves for the large majority.

🛠️ How to find companies hiring for a role

  1. Open the Input tab and put your role titles in keywords.
  2. Paste your own SerpAPI key into serpapi_key. Google Jobs runs only with it.
  3. Set country and lookback_days to scope the search.
  4. Leave the staffing and freelance filters on.
  5. Click Start, then work the rows where company_type is direct_employer.

🧪 Using it in Clay

Point a Clay table at this actor's dataset, one Clay row per output row. The key columns are company_name, company_domain, company_linkedin_url, job_title and posting_date.

Use company_linkedin_url as the input to a LinkedIn step, and posting_date plus is_new_posting to prioritize fresh signals.

Schedule the actor and rely on the built-in delta cache, or pass previous_run_date, so each refresh only ingests new postings.

🌐 Choosing your job board sources

sources selects which boards a run searches. The default is google_jobs, which is what every earlier version did, so an existing saved input behaves exactly as it did before.

Sources run as an ordered chain, cheapest first, not all at once:

TierSourceKey neededBilled perWhat it covers
0jobicynonenothingRemote roles, marketing, writing, design and support heavy. Searched by tag, one request per keyword (up to 8); when every tag search is empty, the newest 50 postings are read instead.
0remotivenonenothingRemote roles, keyword searchable at the source.
0arbeitnownonenothingEurope heavy, strong on Germany and the Netherlands.
0themusenonenothingMid market and enterprise employers in the United States.
0remoteoknonenothingThe newest 100 remote postings on Remote OK.
0weworkremotelynonenothingThe We Work Remotely feed, remote only.
0himalayasnonenothingThe newest pages of Himalayas, remote only.
1adzunaAdzuna app id and keycallIndeed style aggregate coverage per country, dated at the source.
1indeedprivate testingnot availableIndeed search results. In private testing and not yet available to public accounts; see below.
2jobspipeJobsPipe keyjob returned30 plus boards normalized, with the employer domain on the row.
2google_jobsSerpAPI keysearchThe widest corpus: Indeed, LinkedIn, Glassdoor, ZipRecruiter and career pages.
2theirstackTheirStack keyjob returnedCareer sites and boards, with employer domain, LinkedIn URL and headcount on the row.

Tier 0 runs every keyword. Tier 1 runs only on keywords Tier 0 left short, and Tier 2 only on keywords still short after that, JobsPipe first and TheirStack last because its allowance is the smallest. A keyword is satisfied once the run has found enough_new_companies_per_keyword distinct new companies for it (default 3); when every keyword is satisfied the chain ends whatever budget is left. Between tiers every posting is checked against the cross-run delta cache, so a metered source is never asked for a posting the run already has.

Metered queries are narrowed to metered_window_days (default 2, today and yesterday). JobsPipe and TheirStack bill per job returned whether or not you have seen it before, and the delta cache dedupes after the bill, so the date window is what keeps those credits.

A posting that shows up on two boards is deduplicated on company plus normalized job title. The run log reports the raw count, the new count and the calls and credits spent per source and per country, and the same table is written to the run's key value store as RUN_SUMMARY.

countries loops several countries inside one run. Metered sources query each country separately; the Tier 0 boards are global remote boards and run once. Use the names Google Jobs accepts: United States, United Kingdom, Canada.

🖥️ Indeed (private testing)

Indeed is in private testing and is not yet available to public accounts. If you add indeed to sources, the run skips it, every other source runs as normal, and RUN_SUMMARY sets degraded: true with the reason indeed is in private testing and is not available on this account. No browser starts and nothing extra is charged.

The seven Tier 0 boards need no credentials of any kind, so a run with sources: ["jobicy"] works on a fresh account with nothing configured.

🔑 The keys

Google Jobs runs only on your own SerpAPI key, pasted into serpapi_key (stored encrypted). The actor holds no SerpAPI key of its own. Without one, Google Jobs is skipped, and the run log, the run status message, and degradation_reason in RUN_SUMMARY say so (and the summary row, when no posting row comes back). The Tier 0 boards still run.

The other metered sources work the same way: each runs only on your own key from the input (adzuna_app_id and adzuna_app_key, jobspipe_key, theirstack_key). The actor holds no provider key of its own. Without a key, that source is skipped, and the run log and degradation_reason in RUN_SUMMARY say so.

💵 How much does it cost to find job postings?

You are charged once per new posting row returned. A re-run that finds nothing new returns no rows and charges no result.

PlanPer postingPer 1,000 postings
Free$0.02$20.00
Bronze$0.019$19.00
Silver$0.018$18.00
Gold$0.017$17.00

There is also an Actor start event at $0.00005, charged once per run per GB of memory.

Free Apify plans get 25 results per calendar month, resetting on the 1st. Paid plans have no monthly cap. On a free plan, company enrichment is also capped at 2 companies per run.

🛑 Your spending limit is respected. Set a maximum cost per run in Apify, or max_charge_usd on the Input tab, and the run stops before it would go over, keeping every result already charged. Each row's billable_events_json lists the events that row was charged for.

💳 Enrichment runs other Mamba Labs actors on your account. Company enrichment calls Company Identity Resolver, Company Firmographic Enricher, and Domain to LinkedIn URL Resolver with the token that started the run, so their per result prices are billed to you as well. max_companies bounds how many companies are enriched. Metered job sources (SerpAPI, Adzuna, JobsPipe, TheirStack) bill the key you supply, not Apify.

📅 Scheduled pricing change, 2026-10-26 12:00 UTC. Results move from Apify's default per-row event to a named result event at the same price on every plan, so each result row costs exactly what it costs today. A run summary row and a blocked lookup row stop being charged. Until then every row is billed as described above.

⌨️ Input

Everything is on the Input tab. The options worth explaining:

FieldTypeDefaultWhat it does
keywordsarrayeditorial presetRole titles to search for. Any role works.
sourcesarray["google_jobs"]Which job boards to search, run as the chain above.
serpapi_keystring (secret)noneYour own SerpAPI key for google_jobs. Without it Google Jobs is skipped and the run says so.
adzuna_app_id, adzuna_app_keystringnoneYour Adzuna credentials for adzuna.
jobspipe_key, theirstack_keystringnoneYour JobsPipe and TheirStack keys.
countrystringUnited StatesGeographic filter for one country.
countriesarraynoneSeveral countries in one run; overrides country.
metered_window_daysinteger2Date window for metered queries, 1 to 7.
enough_new_companies_per_keywordinteger3New companies that satisfy a keyword and stop its chain.
metered_run_capinteger50Ceiling per metered source per country per run.
indeed_countriesarrayallLimit Indeed to these countries. Indeed is in private testing.
source_time_budgets_secsobjectnoneOverride a source's time window in seconds, for example {"indeed": 5}. A source that runs out of its window is retried once, then stopped, and the run moves on. Google Jobs always gets its turn.
persist_deltabooleantrueRemember returned postings as seen. Turn off for a test run so the next run still returns them.
lookback_daysinteger30Only return postings newer than this.
company_size_min, company_size_maxintegernoneDrop companies outside the range, when size is known.
exclude_staffing_agenciesbooleantrueFilter staffing and recruitment postings.
exclude_freelance_marketplacesbooleantrueFilter Upwork, Fiverr and similar.
freelance_marketplacesarray7 defaultsOverride or extend the marketplace list.
remote_onlybooleanfalseOnly return remote postings.
previous_run_datestringnoneISO date; only emit postings newer than this.
max_resultsinteger100Raw postings scanned before filtering.
max_companiesinteger40Cap on unique companies enriched per run. Bounds sub-actor cost.
track_postingsbooleanfalseKeep a posting history in your own account, fill posting_key, first_seen_at, last_seen_at, and change_type, and return closed and reposted postings. See below.
emit_closedbooleantrueWith tracking on, return one row per closed posting. Off records closures without returning them.
closed_after_missesinteger2With tracking on, complete runs in a row without the posting before it counts as closed.
closed_after_daysinteger30With tracking on, days not seen before a posting counts as closed.
state_keystringderivedWith tracking on, names this monitor's history record. Empty derives it from the keywords, sources, and countries.
modestringgtmgtm finds companies hiring for a role. job_seeker finds local postings for a person looking for work; see Job seeker mode below.
locationstringnoneJob seeker mode: the city to search around, for example Phoenix, Arizona, United States.
radius_kmintegernoneJob seeker mode: search radius in kilometers, sent to Google Jobs, which treats it as a hint.
employment_typesarraynoneKeep only full_time, part_time, contract, temporary or internship postings. A posting with no schedule type is kept.
exclude_keywordsarraynoneDrop a posting when any term appears in its title or description. Whole word, case insensitive.
pages_per_keywordinteger1 in job seeker modeGoogle Jobs pages of up to 10 results per keyword, 1 to 5. Each page is one SerpAPI search.
known_job_idsarraynoneJob seeker mode only. Google job ids you already hold. Paging for a keyword stops at the first page that brings no id outside this list and the ids already seen in the run. RUN_SUMMARY.job_seeker.pages lists every page searched.
include_full_descriptionbooleanfalseReturn job_description_full. Always on in job seeker mode.
max_charge_usdnumbernoneOptional. The run stops before the next result that would take this run's charges over this amount, and keeps every result already charged. Leave empty to rely only on the maximum cost per run you set in Apify.

📤 Output

One flat row per posting, exportable as JSON, CSV, Excel, HTML or XML.

{
"company_name": "Stripe",
"company_domain": "stripe.com",
"company_linkedin_url": "https://www.linkedin.com/company/stripe",
"employee_count": 8000,
"industry": "Financial Services",
"company_type": "direct_employer",
"job_title": "Senior Software Engineer",
"job_url": "https://boards.greenhouse.io/example/jobs/123",
"posting_date": "2026-06-25",
"source_board": "google_jobs",
"location": "San Francisco, CA",
"is_remote": true,
"job_description_snippet": "We are seeking a backend engineer to...",
"matched_keyword": "software engineer",
"is_new_posting": true,
"posted_via": "LinkedIn",
"search_country": "US",
"degraded": false,
"degradation_reason": null,
"run_date": "2026-07-01T14:06:22.297Z"
}

search_country is the country a metered query ran for, null for a global remote board. degraded is true, with a plain reason, on a row whose enrichment did not finish inside its time budget: a null company_domain on such a row means unresolved, not absent. Identity, firmographics and LinkedIn each have their own budget, and LinkedIn runs last, so a slow LinkedIn lookup can cost a LinkedIn URL and nothing else.

source_board is the source you selected. posted_via is the board the posting was actually published through, as reported by that source, so you can see the board distribution behind a google_jobs run without reconstructing it from the URLs.

🔁 Posting tracking

Off by default. With it off, every row is the same as in earlier builds, and the seven columns below are null. The full posting description is job_description_full (see the job seeker fields above).

Tracking (track_postings). The actor keeps one record per monitor in a named storage, job-posting-monitor-state, in your own Apify account. Each row then carries:

FieldWhat it holds
posting_keyStable key for the posting: pk_ plus 20 hex characters from the company domain, normalized title, and normalized location. The same key the other Mamba Labs hiring actors use. Null when the company domain did not resolve
first_seen_at, last_seen_atWhen the monitor first returned the posting, and when a run last saw it
change_typenew, closed, or reposted
closed_atWhen the run detected the closure. Closed rows only
closed_reasonats_gone, missed_runs, or not_seen_days. Closed rows only
closed_confidencehigh, medium, or low. Closed rows only

How a posting is detected as closed, in this order:

  1. ats_gone, high confidence. When the posting's apply link is on Greenhouse, Lever, SmartRecruiters, Workable, Recruitee, Personio, or Workday, the actor asks that board whether the posting still exists. Only a 404 or 410 closes it. A refusal or a timeout never does. Links on LinkedIn, Indeed, and other aggregators are never checked, because a dead posting there often still answers.
  2. missed_runs, medium confidence. The posting was missing from closed_after_misses runs in a row. A run counts as a miss only when every search that ever returned the posting ran and read its results to the end. Google Jobs keywords that end their pagination, and keyless boards read without truncation, can be complete. Adzuna, JobsPipe, TheirStack, and Indeed read a date window or one page, so their searches never count a miss. Expect most closures to come from the board check or from age.
  3. not_seen_days, low confidence. Not seen for closed_after_days.

A closed posting that comes back is returned once with change_type reposted. Closed and reposted rows are results and are billed like any other result row. persist_delta false also leaves the tracking record unwritten. Closed entries are dropped after 30 days and open entries after 90 days unseen.

🧾 More fields on every row

FieldTypeWhat it holds
degradation_reasonstringWhy the run was degraded, in plain language, or null when it was not.
row_statusstringok, degraded, error, or notice (a notice row carries a message, not a result). Present on every row.
error_reasonstringWhy the row is degraded or an error, in plain words. Null on an ok row. Present on every row.
billable_events_jsonstringJSON list of the pricing events this row was charged for, for example [{"event":"apify-default-dataset-item","count":1}]. Lets you reconcile a run from the dataset alone. The once per run start event is not repeated on rows.

💵 Pay, schedule and apply fields

Every row carries these columns. Google Jobs is the source that fills them; every other source leaves them null.

FieldTypeWhat it holds
salary_textstringPay as the posting shows it, for example $17 to $19 an hour.
salary_min, salary_maxnumberPay figures as posted, in the posting's own period.
salary_periodstringhour, day, week, month or year.
salary_annual_min, salary_annual_maxnumberThe same pay as a yearly figure: hour x 2080, day x 260, week x 52, month x 12.
employment_typestringfull_time, part_time, contract, temporary or internship.
schedule_textstringThe schedule type as Google Jobs shows it, for example Part-time.
job_description_fullstringThe full description. Filled in job seeker mode or with include_full_description.
qualifications, benefitsarrayLines from the Google Jobs highlights.
apply_optionsarrayEvery apply link Google Jobs lists, as {title, link}.
google_job_idstringThe Google Jobs id, a stable key for deduplication.

Pay is read from the pay string Google Jobs shows, then from a pay sentence in the description. A figure without a pay period in the description (a sign on bonus, a weight limit) is never read as pay.

🧭 Job seeker mode

Set mode to job_seeker to search for work near one place instead of for companies that are hiring. The run searches Google Jobs only, around location, keeps every posting (several stores of one employer are several rows), skips LinkedIn resolution, keeps firmographics for up to max_companies companies, and returns the full description. Staffing agency and freelance marketplace filters stay on. Job seeker runs keep their own record of postings already returned, separate from hiring signal runs.

💡 Tips

  • Work company_type: "direct_employer" first. It is the reason the filtering exists.
  • Tighten lookback_days before raising max_results. Fresh postings convert better than deep ones.
  • max_companies is your cost control. It bounds how much enrichment a run can trigger.
  • Schedule it and filter on is_new_posting to turn this into a standing feed rather than a one-time list.
  • Extend freelance_marketplaces with whatever else clutters your vertical.
  • No SerpAPI key yet? Start with sources: ["jobicy", "remotive"]. No credentials needed.
  • Group posted_via in your export to see which boards actually carry your role.

⚠️ Known limits

Discovery depth is bounded by max_results. Very high-volume keywords may need multiple runs or a tighter lookback_days.

Company enrichment is capped per run by max_companies, deliberately, to bound sub-actor cost.

Enrichment depends on name-to-domain resolution. Ambiguous or very small companies may not resolve, and those rows come back with a null domain and null firmographics rather than being dropped.

employee_count and industry coverage varies and is frequently null. The size filters apply only when a size is known.

Google Jobs coverage excludes some regions where the widget is disabled, including the EU.

Posting dates from aggregated boards are often relative, for example "25 days ago", and are normalized to a best-effort ISO date.

indeed is in private testing. It is not yet available to public accounts. A run that lists it skips it and says so.

google_jobs needs a SerpAPI key. Without one, that source is skipped and the run says so. The seven Tier 0 boards need no key.

Keywords match as a phrase inside the title or description. A bare writer also matches "Underwriter", and a bare editor matches "Video Editor", so use the specific title.

❓ FAQ

Do I need a SerpAPI key?

Only for google_jobs. The seven Tier 0 boards (Jobicy, Remotive, Arbeitnow, The Muse, Remote OK, We Work Remotely and Himalayas) need no key, and the Input tab prefill uses three of them. SerpAPI's free tier covers 250 searches per month if you want Google Jobs as well.

Why did a run return nothing?

Usually no key, a keyword and country combination the selected source does not serve, or nothing new since your last run (the delta cache). The run sets a status message saying which, emits zero rows and exits cleanly rather than erroring.

The run's RUN_SUMMARY record (Storage, Key value store) labels the outcome: run_outcome is no_results, blocked or timed_out, with run_outcome_reason, the sources that answered (sources_reached), the sources that refused (sources_blocked), and the run ID and build. A board that refused is labeled blocked; it is never reported as a board with zero postings.

A run that finds nothing costs nothing. Earlier versions pushed a single explanatory row when there were no new postings, and because a result is a dataset row, that row was billed as a result. It is not any more: those notices are status messages and log lines. If you have automation that reads a "no new postings" row, read the run status message instead.

How long does a run take, and what if it runs out of time?

Every request and every phase is budgeted against the run's own timeout, less a 90 second reserve. Discovery gets at most half of the time left, then identity, firmographic and LinkedIn enrichment each get their own share. A phase that does not finish keeps what it found, and the affected rows say so in degraded and degradation_reason. A run on a short timeout ends with the rows it has instead of being stopped by the platform with none.

Are staffing agencies really filtered out?

They are filtered on both company name and job description text, and anything that survives is labeled in company_type. If you would rather judge for yourself, turn the filter off and filter on that field.

Will a scheduled run keep showing me the same postings?

No. Cross-run delta detection means re-runs only emit postings you have not seen. is_new_posting carries the flag.

What happens if the enrichment sub-actors fail?

The row is still emitted with null enrichment fields and a structured error is logged. Discovery is never lost to an enrichment failure.

🧩 Want other GTM data?

Mamba Labs builds custom actors for B2B go-to-market teams. The public versions of that work live here on the Store, so our users get the same tooling we build under contract.

🕵️ Agent Accessibility Auditor🤖 AI Tooling Detector
📡 GTM Signals Aggregator🚀 Prospect Engine
📝 Publishing Frequency Tracker🦋 Bluesky Brand Presence Mapper
⏩ Sequencer Lead Push🔄 Company Change Event Feed
📇 Company Contact Details Extractor🧭 Company Discovery List Builder
🏢 Company Firmographic Enricher🪪 Company Identity Resolver
🌐 Company Social Presence Mapper🏷️ Contact Classifier
📈 Influencer Change Monitor🔎 Influencer Finder
🧾 Influencer Lead List Builder👤 Influencer Profile Scraper
📬 Domain Deliverability Checker🔗 Domain to LinkedIn URL Resolver
🛒 Ecommerce Platform Profiler✉️ Work Email Waterfall
🎪 Event Presence Index⚖️ Expert Witness Directory
💵 Company Funding Record💰 Funding and Press Signal Scanner
🐙 GitHub Organization Signal Scanner🧑‍💼 GTM Hiring Signal Scraper
🧱 Tech Stack Signal Detector🎯 ICP Fit Scorer
🔑 Job Board Keyword Signal Scanner🏛️ Legal Entity Resolver
📰 Link in Bio Scraper and Newsletter Detector💼 LinkedIn Company Page Mapper
💬 LinkedIn Post Tracker📢 Meta Ad Library Monitor
📸 Instagram Facebook Brand Presence Mapper📮 Outbound Stack Detector
📄 Page Finder and Extractor🧑 People Finder & Email Verifier
📌 Pinterest Brand Presence Mapper🏆 Government Contract Award Monitor
📅 Reporting Window Finder👽 Reddit Brand Presence Mapper
⭐ Trustpilot Reputation Enricher🤝 Influencer Talent Agency Lookup
👥 Team Page People Extractor🎵 TikTok Brand Presence Mapper
📊 Website Traffic Rank Estimator🏅 Workplace Program Detector
✖️ X Brand Presence Mapper▶️ YouTube Channel Stats Extractor

Every actor in the suite takes a domain or a company and returns one flat row, so they stack in the same Clay table without reshaping anything.

🛠️ Need something custom built for you or your team? Tell us what you are trying to find and we will build it. Talk to Mamba Labs.

🆘 Support

Something wrong, or a company misclassified? Open an issue on the Issues tab with the keyword and the row, and we will look at it.

ℹ️ Sourcing and legal. Job postings come from the SerpAPI google_jobs engine against your own key, and from public job board APIs. Company enrichment comes from Mamba Labs fleet actors reading public company data. The actor stores no personal data: it returns companies, roles and postings, not people. You are responsible for how you use the output.

Built by Mamba Labs.