The Mine Works
Browse on Apify
How to Monitor Competitor Job Postings to Predict Their Strategy
← All posts
use-case November 3, 2025 · 9 min read

How to Monitor Competitor Job Postings to Predict Their Strategy

Job postings are the most honest signal of a competitor's roadmap. Learn how to track ATS boards automatically and turn hiring data into strategic

Try the scraper

The actor referenced in this article is live on Apify. Pay only for results delivered.

Open on Apify →

A competitor cannot hide their hiring. The moment they open a role on Greenhouse, Lever, or Ashby, the posting is public. If you know where to look — and you automate the looking — you get a 6-to-12-month preview of their product roadmap, new market bets, and technology decisions, all before they announce anything.

TL;DR: Monitor competitor job postings using ATS public APIs (Greenhouse, Lever, Ashby — zero authentication required) to predict strategy: new engineering roles reveal product direction, first sales hire in a region signals market entry, unfamiliar tech stacks mean platform shifts. Automate weekly collection with the ATS Jobs scraper, pipe results to Claude for interpretation, and get a Monday morning briefing on what every competitor is building.

This is not a new insight. CB Insights, investors, and growth teams have used job posting intelligence for years. What has changed is that you can now automate the entire pipeline — collection, interpretation, and alerting — in an afternoon.

Why Job Postings Are the Best Competitive Signal

Most competitive intelligence is lagged. Press releases describe decisions already made. Earnings calls describe results already booked. Blog posts describe features already shipped.

Job postings are leading. A company must hire before they can build. The average time from job posting to new hire to meaningful output is 6 to 12 months. When you see a pattern in their postings, you are looking at their roadmap, not their past.

Specific signals job postings reliably encode:

Product direction: A web-first company posting five iOS engineers is building a native app. A company posting “ML Platform Engineer” (not “ML Research Engineer”) is productionizing AI, not experimenting with it.

Market entry: The first sales hire in a new geography is the most reliable signal of geographic expansion. Companies do not hire salespeople in markets they are not serious about.

Technology decisions: The tech stack in a job description reflects actual infrastructure, not marketing copy. “Experience with ClickHouse and Flink” tells you more about their data architecture than their entire engineering blog.

Organizational structure: Whether they post individual contributor roles or manager roles in a function reveals whether they are scaling a team or building a new one from scratch.

Acquisition interest: A company that posts deep expertise requirements in a niche area they have never mentioned may be building toward an acquisition target — or building to compete with one.

The Data Source: Public ATS APIs

Greenhouse, Lever, and Ashby all expose public-facing job board APIs that require zero authentication. Every company using these ATSs is broadcasting their open roles to anyone who calls the right endpoint.

# Direct API calls — no API key, no auth
# Greenhouse: https://boards-api.greenhouse.io/v1/boards/{slug}/jobs
# Lever: https://api.lever.co/v0/postings/{slug}
# Ashby: https://jobs.ashbyhq.com/api/non-user-facing/job-board/{slug}

The ATS Jobs scraper wraps all three platforms in a single normalized call, handles pagination, and returns clean structured JSON — same schema regardless of which ATS the target company uses.

Setting Up the Monitoring Pipeline

Step 1: Build Your Target Company List

Identify which ATS each competitor uses. The easiest way is to look at their careers page URL:

  • greenhouse.io in the URL → Greenhouse slug is the subdomain
  • lever.co in the URL → Lever slug is the path segment
  • ashbyhq.com in the URL → Ashby slug is the path segment
COMPETITORS = {
    "greenhouse": [
        "stripe", "linear", "notion", "figma", "vercel",
    ],
    "lever": [
        "planetscale", "turso", "neon",
    ],
    "ashby": [
        "loom", "coda", "retool",
    ],
}

Step 2: Collect and Store Postings

from apify_client import ApifyClient
import json
import hashlib
from pathlib import Path
from datetime import datetime

apify = ApifyClient("YOUR_APIFY_TOKEN")

SEEN_FILE = Path("seen_postings.json")

def load_seen() -> set:
    if SEEN_FILE.exists():
        return set(json.loads(SEEN_FILE.read_text()))
    return set()

def save_seen(seen: set):
    SEEN_FILE.write_text(json.dumps(list(seen)))

def job_id(job: dict) -> str:
    key = f"{job.get('_company')}-{job.get('title')}-{job.get('location', '')}"
    return hashlib.md5(key.encode()).hexdigest()

def collect_new_postings(competitors: dict) -> list[dict]:
    seen = load_seen()
    new_jobs = []

    for platform, slugs in competitors.items():
        for slug in slugs:
            run = apify.actor("themineworks/ats-jobs").call(run_input={
                "platform": platform,
                "companySlug": slug,
                "maxJobs": 100,
                "includeDescription": True,
            })
            for job in apify.dataset(run["defaultDatasetId"]).iterate_items():
                job["_company"] = slug
                job["_platform"] = platform
                jid = job_id(job)
                if jid not in seen:
                    job["_first_seen"] = datetime.utcnow().isoformat()
                    new_jobs.append(job)
                    seen.add(jid)

    save_seen(seen)
    return new_jobs

Step 3: Interpret With Claude

Raw job postings are useful. Interpreted job postings are intelligence. Claude can read a batch of new postings and surface what they signal — not just list them.

import anthropic
import os

claude = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

def interpret_new_postings(new_jobs: list[dict]) -> str:
    if not new_jobs:
        return "No new postings this week."

    # Build company summaries
    by_company = {}
    for job in new_jobs:
        company = job["_company"]
        if company not in by_company:
            by_company[company] = []
        by_company[company].append({
            "title": job.get("title"),
            "department": job.get("department"),
            "location": job.get("location"),
            "description_excerpt": (job.get("description") or "")[:400],
        })

    response = claude.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2000,
        messages=[{
            "role": "user",
            "content": f"""You are a competitive intelligence analyst. Analyze these new job postings from the past week.

NEW POSTINGS BY COMPANY:
{json.dumps(by_company, indent=2)}

Write a Monday morning competitive intelligence brief covering:

1. MOST SIGNIFICANT SIGNALS — What are the 3 most important things these postings tell us about competitor direction? Be specific: "Company X is building Y because they posted Z roles."

2. COMPANY-BY-COMPANY SUMMARY — For each company with new postings, one paragraph: what are they hiring for, what does it suggest about their roadmap?

3. PATTERNS ACROSS COMPANIES — Are multiple competitors hiring in the same area? That signals an industry-wide shift worth noting.

4. WHAT TO WATCH — Which companies or role types should we track most closely next week?

Be specific and analytical. "They're hiring engineers" is not useful. "They posted 4 ML Platform roles and 2 data infrastructure roles but zero ML Research roles, which suggests they are productionizing an existing capability rather than building a new one" is useful."""
        }]
    )

    return response.content[0].text

Step 4: Schedule and Alert

import schedule
import time

def weekly_competitive_brief():
    print("Collecting new job postings...")
    new_jobs = collect_new_postings(COMPETITORS)
    print(f"Found {len(new_jobs)} new postings")

    brief = interpret_new_postings(new_jobs)

    # Save locally
    filename = f"competitive_brief_{datetime.now().strftime('%Y_%m_%d')}.md"
    with open(filename, "w") as f:
        f.write(f"# Competitive Intelligence Brief — {datetime.now().strftime('%B %d, %Y')}\n\n")
        f.write(f"**New postings analyzed:** {len(new_jobs)}\n\n")
        f.write(brief)

    print(f"Brief saved to {filename}")
    print("\n" + "="*60 + "\n")
    print(brief)

# Run every Monday at 8am
schedule.every().monday.at("08:00").do(weekly_competitive_brief)

while True:
    schedule.run_pending()
    time.sleep(60)

The Signals That Matter Most

Not all job postings carry equal intelligence value. These are the patterns worth watching closely:

First hire in a function: The first data engineer, the first compliance officer, the first sales hire in a new city. First hires signal new bets. Tenth hires in a function signal scale, which is less interesting for strategy.

Role titles that don’t exist yet: If a competitor posts “Agentic Infrastructure Engineer” or “AI Reliability Engineer,” they are naming a discipline that does not have standard terminology. They are inventing their organizational model in real time.

Senior versus junior mix: A company posting all Staff and Principal engineers and no mid-level engineers is either burning out, scaling fast, or building a new team from scratch. Any of these is worth understanding.

Location and remote signals: A company that was fully remote suddenly posting office-required roles in a specific city may be making a strategic partnership or acquisition that requires physical presence.

Volume spikes: A company that normally posts 5 roles per month suddenly posting 20 is in an expansion phase. A company that drops from 20 to 0 has either hit a hiring freeze or a structural problem.

Real Example: What Competitor Job Postings Revealed

A growth team at a Series B developer tools company tracked their top three competitors weekly. Signals they caught before public announcements:

  • One competitor posted 6 “Enterprise Sales Engineer” roles across New York, London, and Singapore in a single month, despite being primarily self-serve. This preceded their enterprise tier launch by 8 months.

  • Another competitor’s engineering job descriptions shifted from mentioning PostgreSQL to mentioning ClickHouse and Apache Iceberg across multiple roles over a 6-week period. This signaled a data infrastructure rewrite before any engineering blog post.

  • A third competitor posted “Head of Partnerships — Systems Integrators” with a description explicitly mentioning “SAP, Salesforce, and ServiceNow ecosystem.” Their integration marketplace launched 9 months later.

None of these signals required inside information. They were in public job postings, visible to anyone who was reading.

Frequently Asked Questions

Is it legal to scrape public ATS job boards?

Yes. Greenhouse, Lever, and Ashby job boards are explicitly public-facing APIs with no authentication requirement. The data is intended to be publicly accessible for job seekers. The hiQ v. LinkedIn ruling (9th Circuit) confirmed that scraping publicly accessible data does not violate the Computer Fraud and Abuse Act. Avoid storing personal employee data, and use the data for research and analysis rather than automated mass outreach.

How often should I run the collection to catch new postings quickly?

Daily collection is sufficient for most competitive intelligence purposes. Most strategic signals emerge from patterns over weeks, not hours. If you are in a fast-moving competitive situation, twice-daily collection is reasonable. Avoid running more frequently than necessary — it creates noise and wastes compute.

How many companies can I effectively monitor at once?

The practical limit is determined by signal-to-noise ratio, not technical constraints. Monitoring 50 companies produces so many postings that patterns get lost. Most effective competitive intelligence programs track 10 to 20 carefully selected companies — the ones where hiring signals are most relevant to your own strategy.

What if a competitor uses a different ATS like Workday or Lever Pro?

The ATS Jobs scraper covers Greenhouse, Lever, and Ashby, which together power the majority of growth-stage and late-stage tech companies. For companies using Workday, iCIMS, or other enterprise ATSs, their careers pages typically have searchable job lists that require different scraping approaches. A practical alternative: monitor their LinkedIn company page job postings, which aggregates across ATS platforms.

How do I separate signal from routine backfill hiring?

Establish a baseline. After 4 to 6 weeks of collection, you will know each company’s typical posting volume by department and role type. Deviations from baseline — more than 2x volume in any category, new role types that haven’t appeared before, sudden geographic expansion — are the signals worth investigating. Claude’s pattern analysis does this automatically when you provide historical context alongside new postings.

Related Actor

Try the scraper referenced in this article — live on Apify, pay only for results.

Open ats-jobs on Apify →