# LinkedIn Company Hiring Tracker (`coregent/linkedin-company-hiring-tracker`) Actor

Track open LinkedIn jobs for a list of companies and get one clean hiring-signal row per company - open roles, top functions, locations, seniority mix, remote/hybrid count, and change since last run. No LinkedIn login or cookies.

- **URL**: https://apify.com/coregent/linkedin-company-hiring-tracker.md
- **Developed by:** [Delowar Munna](https://apify.com/coregent) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 1 total users, 1 monthly users, 97.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.60 / 1,000 company-results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## LinkedIn Company Hiring Tracker

![LinkedIn Company Hiring Tracker](https://raw.githubusercontent.com/coregentdevspace/linkedin-company-hiring-tracker-assets/main/thumbnail-linkedin-company-hiring-tracker.jpg)

Track **open LinkedIn jobs for a list of companies** and get back **one clean hiring-signal row per company** — total open roles, newest/oldest posting date, top hiring functions, top locations, seniority mix, remote/hybrid/on-site mix, a transparent **0–100 hiring-signal score**, and **change since the last tracked run**. Built for **B2B sales teams, recruiters, staffing agencies, market researchers, and competitor monitoring**.

This is a **company-level hiring signal tracker — not a generic job feed**. You get a watchlist-ready summary (*who is hiring, where, in which functions, and whether hiring is increasing*), not thousands of individual job rows.

**No LinkedIn login, no cookies, no session IDs.** The actor reads LinkedIn's public company and guest jobs surfaces over HTTP. You pay one flat event per saved company row.

### ✨ Why this tracker

- **One row per company** — open roles, function/seniority/location/remote mix, and a hiring signal, all flat and CSV-ready. No nested objects.
- **Company-first input** — paste LinkedIn company URLs (recommended) or plain company names (best-effort resolution).
- **Change tracking** — compares each company to its previous run via a named key-value store, so scheduled runs show hiring momentum (`increased` / `decreased` / `unchanged` / `first_seen`).
- **Transparent hiring-signal score** — rule-based (no AI), explained below.
- **Pay-Per-Event** — one flat `company-result` event per saved company. Duplicates, failed inputs, and filtered companies are never charged.
- **No login / cookies / sessions** — just company URLs or names.

***

### 🚀 Quick start — sample inputs

#### Example 1 — track a watchlist by company URL

```json
{
    "companyUrls": ["https://www.linkedin.com/company/atlassian/", "https://www.linkedin.com/company/canva/"],
    "maxCompanies": 100,
    "maxJobsPerCompany": 250,
    "postedDate": "past30d",
    "includeJobSamples": true,
    "includeChangeTracking": true,
    "stateStoreName": "linkedin-company-hiring-tracker-state",
    "minOpenJobs": 1,
    "deduplicateCompanies": true,
    "proxyConfiguration": { "useApifyProxy": true }
}
```

#### Example 2 — by company name + custom residential proxy via your own provider

```json
{
    "companyNames": ["Stripe", "Notion", "Figma"],
    "maxCompanies": 50,
    "maxJobsPerCompany": 150,
    "postedDate": "past7d",
    "minOpenJobs": 5,
    "includeChangeTracking": false,
    "proxyConfiguration": {
        "useApifyProxy": false,
        "proxyUrls": ["http://user:pass@proxy.iproyal.com:12321"]
    }
}
```

> Provide **at least one** of `companyUrls` or `companyNames`. If you provide both, URLs are processed first and companies are deduplicated so each is processed and charged once.

> The actor blocks Apify Residential proxy; if you need residential routing, supply your own provider via `proxyConfiguration.proxyUrls`. See **🚦 Proxy policy** below.

***

### 📦 Output

The dataset has one view: **Company hiring signals** — one flat row per company.

![LinkedIn Company Hiring Tracker — company hiring signals table view](https://raw.githubusercontent.com/coregentdevspace/linkedin-company-hiring-tracker-assets/main/linkedin-company-hiring-tracker-output-overview.png)

#### Sample record — Company hiring signals

```json
{
    "input_company_url": "https://www.linkedin.com/company/atlassian/",
    "input_company_name": null,
    "company_name": "Atlassian",
    "company_linkedin_url": "https://www.linkedin.com/company/atlassian/",
    "company_slug": "atlassian",
    "company_id": "1679",
    "company_industry": "Software Development",
    "company_size": "10001+ employees",
    "company_headquarters": "Sydney, NSW",
    "open_jobs_total": 148,
    "jobs_scanned": 148,
    "jobs_scan_limited": false,
    "newest_job_posted_at": "2026-05-24",
    "oldest_job_posted_at": "2026-04-28",
    "top_function_1": "engineering",
    "top_function_1_count": 54,
    "top_function_2": "sales",
    "top_function_2_count": 22,
    "top_function_3": "product",
    "top_function_3_count": 16,
    "top_location_1": "Sydney, NSW",
    "top_location_1_count": 38,
    "top_location_2": "San Francisco, CA",
    "top_location_2_count": 21,
    "remote_jobs_count": 18,
    "hybrid_jobs_count": 42,
    "onsite_jobs_count": 88,
    "seniority_entry_count": 5,
    "seniority_mid_count": 76,
    "seniority_senior_count": 49,
    "seniority_manager_count": 18,
    "sample_job_title_1": "Senior Software Engineer",
    "sample_job_url_1": "https://www.linkedin.com/jobs/view/1234567890/",
    "sample_job_title_2": "Product Manager",
    "sample_job_url_2": "https://www.linkedin.com/jobs/view/1234567891/",
    "sample_job_title_3": "Enterprise Account Executive",
    "sample_job_url_3": "https://www.linkedin.com/jobs/view/1234567892/",
    "previous_open_jobs_total": 121,
    "open_jobs_change": 27,
    "open_jobs_change_label": "increased",
    "hiring_signal_score": 95,
    "hiring_signal_label": "Very strong",
    "reason_tags": "many_open_roles|hiring_increased|fresh_jobs|multi_function_hiring|senior_hiring|remote_or_hybrid_roles|engineering_heavy",
    "source_type": "company_url",
    "scrape_status": "success",
    "error_message": null,
    "scraped_at": "2026-05-25T00:00:00.000Z"
}
```

#### Output fields

- **Input echo**: `input_company_url`, `input_company_name`, `source_type`
- **Company identity**: `company_name`, `company_linkedin_url`, `company_slug`, `company_id`, `company_industry`, `company_size`, `company_headquarters`
- **Open-roles**: `open_jobs_total`, `jobs_scanned`, `jobs_scan_limited`, `newest_job_posted_at`, `oldest_job_posted_at`
- **Function mix**: `top_function_1..3` + `_count`
- **Location mix**: `top_location_1..2` + `_count`
- **Workplace mix**: `remote_jobs_count`, `hybrid_jobs_count`, `onsite_jobs_count`
- **Seniority mix**: `seniority_entry_count`, `seniority_mid_count`, `seniority_senior_count`, `seniority_manager_count`
- **Samples** (if enabled): `sample_job_title_1..3`, `sample_job_url_1..3`
- **Change tracking**: `previous_open_jobs_total`, `open_jobs_change`, `open_jobs_change_label`
- **Signal**: `hiring_signal_score`, `hiring_signal_label`, `reason_tags`
- **Status**: `scrape_status`, `error_message`, `scraped_at`

***

### 🎯 Hiring-signal score

Transparent rule-based score (0–100) computed from the company aggregates — no AI, no external enrichment.

| Signal                                     | Points |
| ------------------------------------------ | -----: |
| 50+ open roles                             |    +40 |
| 20–49 open roles                           |    +30 |
| 5–19 open roles                            |    +20 |
| 1–4 open roles                             |    +10 |
| Open roles increased by 10+ since last run |    +20 |
| Open roles increased by 1–9 since last run |    +10 |
| Newest job posted within the last 7 days   |    +10 |
| At least 3 functions hiring                |    +10 |
| Senior + manager roles present, count ≥ 5  |    +10 |
| Remote or hybrid roles present             |     +5 |

Score is capped at 100. The change component is only applied when a previous snapshot exists (otherwise the row is tagged `first_seen`).

**Labels**: `Very strong` (80–100) · `Strong` (60–79) · `Moderate` (35–59) · `Weak` (1–34) · `No visible hiring` (0).

`reason_tags` is a **pipe-separated** list explaining the score — e.g. `many_open_roles`, `moderate_open_roles`, `few_open_roles`, `hiring_increased`, `hiring_decreased`, `first_seen`, `fresh_jobs`, `multi_function_hiring`, `senior_hiring`, `remote_or_hybrid_roles`, `engineering_heavy`, `sales_heavy`, `scan_limited`.

***

### 🔁 Change tracking

When `includeChangeTracking` is on, the actor stores each company's open-jobs count in a **named key-value store** (`stateStoreName`). On the next run for the same company it fills:

- `previous_open_jobs_total` — the count from the last run.
- `open_jobs_change` — current minus previous.
- `open_jobs_change_label` — `increased` / `decreased` / `unchanged` / `first_seen`.

The snapshot is only updated **after** a company row is successfully saved and charged. Use the same `stateStoreName` across scheduled runs to build a simple hiring trend per company.

***

### 💰 Pricing

**Pay-Per-Event**. One flat event per saved company row (final per-event price is configured on the Apify console):

| Event            | Charged when                                                                                       |
| ---------------- | -------------------------------------------------------------------------------------------------- |
| `company-result` | Once per valid, unique company row that passed the `minOpenJobs` filter and was saved to the data. |

So your bill is simply `results_saved × price_per_event`. The actor honors the user-configured per-run spending cap (Apify `eventChargeLimitReached`): it caps how many companies it resolves up-front to what the limit can pay for, and stops cleanly the moment the cap is reached.

**Not charged**: duplicate companies, failed inputs (e.g. an unresolvable name), companies filtered out by `minOpenJobs`, and failed/blocked requests.

#### 🚦 Proxy policy

Use **Apify Datacenter** proxy or **no proxy** for normal runs — both work for LinkedIn's public company/jobs surface at this actor's conservative concurrency.

**Apify Residential proxy is not supported.** The actor will fail at startup if `proxyConfiguration.apifyProxyGroups` includes `RESIDENTIAL`. Reason: in pay-per-event actors, residential bandwidth (~/GB) is billed to the developer, not the run user, so a single bandwidth-heavy run could exceed the per-result event revenue.

If you genuinely need residential routing, supply your own residential provider via the proxy editor's **Custom proxy URLs** field — that traffic goes through your provider, not Apify, and is unaffected:

```
http://user:pass@proxy.iproyal.com:12321
http://user:pass@proxy.brightdata.com:22225
http://user:pass@proxy.oxylabs.io:7777
```

***

### 📊 Run summary

After each run, a `RUN_SUMMARY` entry is written to the key-value store (with `FAILED_INPUTS` when any input could not be resolved):

```json
{
    "actor_name": "LinkedIn Company Hiring Tracker",
    "inputs_total": 25,
    "company_urls_total": 20,
    "company_names_total": 5,
    "companies_after_deduplication": 23,
    "successful_companies": 20,
    "partial_companies": 2,
    "failed_companies": 1,
    "raw_jobs_found": 1840,
    "jobs_scanned": 950,
    "results_saved": 22,
    "duplicates_removed": 2,
    "filtered_out": 1,
    "charged_events": 22,
    "blocked_requests": 3,
    "retry_count": 8,
    "change_tracking_enabled": true,
    "state_store_name": "linkedin-company-hiring-tracker-state",
    "runtime_seconds": 312,
    "scraped_at": "2026-05-25T00:00:00.000Z"
}
```

`charged_events` equals the number of successfully saved company rows.

***

### ⚙️ Filters & limits

| Setting                | Effect                                                                                              |
| ---------------------- | --------------------------------------------------------------------------------------------------- |
| `maxCompanies`         | Stops processing new companies once reached (1–1000).                                               |
| `maxJobsPerCompany`    | Stops scanning a company's jobs once reached; sets `jobs_scan_limited` + `scan_limited` tag.        |
| `postedDate`           | `any` / `past24h` / `past7d` / `past30d`. Applied at the LinkedIn source where available.           |
| `minOpenJobs`          | Save only companies with at least this many open jobs. Filtered companies are not saved or charged. |
| `deduplicateCompanies` | Process each company once (by ID → URL → slug → name).                                              |

***

### 🚧 Limitations (V1)

- **Public guest data only**: no login, cookies, or member-only content. Some company metadata (`company_id`, `company_industry`, `company_size`, `company_headquarters`) is only filled when LinkedIn exposes it publicly; otherwise it falls back to `null` and the company's jobs are still tracked.
- **Worldwide scope**: jobs are counted across LinkedIn's global "Worldwide" listing so a company's full open-role footprint is captured regardless of where the run executes.
- **Workplace mix is best-effort**: LinkedIn's public job *cards* do not expose a remote/hybrid flag, so `remote_jobs_count` / `hybrid_jobs_count` are only set when a job's title or location text says so. Otherwise a located role is counted as `onsite_jobs_count` (per the on-site fallback). Precise remote/hybrid splits would require visiting each job's detail page, which V1 avoids for cost/speed.
- **Company-level output**: one row per company. There is no per-job dataset in V1 (sample jobs are flat fields only).
- **`open_jobs_total`** reflects the unique public jobs visible during the run, up to `maxJobsPerCompany`.
- **Ambiguous company names** resolve to whichever company owns that exact LinkedIn slug (e.g. `csg` → the company at `/company/csg/`), which may not be the one you intended. For precise targeting, use `companyUrls`.
- **Company-name resolution** is best-effort. Names that cannot be resolved to a public company are reported in `FAILED_INPUTS` and are not charged.
- **No** recruiter/contact extraction, email enrichment, website crawling, salary parsing, or AI scoring.
- LinkedIn guest pagination tops out around ~1,000 jobs per company.

***

### ❓ FAQ

**Do I need a LinkedIn account or cookies?**
No. The actor only uses LinkedIn's public company and guest jobs surfaces.

**Can I track companies by name instead of URL?**
Yes — put them in `companyNames`. The actor resolves them best-effort to a public company; unresolved names land in `FAILED_INPUTS`. URLs are more reliable.

**How does change tracking work across scheduled runs?**
Keep `includeChangeTracking: true` and reuse the same `stateStoreName`. Each run compares to the previous snapshot and fills `open_jobs_change` / `open_jobs_change_label`.

**How is `open_jobs_total` counted?**
It's the number of unique public jobs found for the company during the run, capped by `maxJobsPerCompany` (when capped, `jobs_scan_limited` is `true`).

**Can I export to CSV?**
Yes — every field is flat (no nested objects). Use Apify's CSV / Excel export, or call the dataset API with `format=csv`.

**Will I get blocked?**
The actor uses conservative concurrency, realistic headers, session rotation, and retry/backoff. Default Apify Proxy is sufficient for typical runs. If one company is blocked it's marked partial/failed and the run continues.

***

### 🛠️ Technical notes

- **Stack**: Node.js 22 · Apify SDK 3 · Crawlee `CheerioCrawler` · Cheerio + native fetch. No browser.
- **Surfaces**: LinkedIn public company page (metadata + numeric org id) and the guest `seeMoreJobPostings` jobs search scoped per company.
- **Concurrency**: `min=1`, `max=5` (conservative; tune after real runs).
- **Memory**: 1 GB min · 2 GB default · 4 GB max.
- **Proxy**: Apify Proxy enabled by default; custom configs accepted; Apify Residential rejected at startup.

# Actor input Schema

## `companyUrls` (type: `array`):

Primary input. LinkedIn company URLs to track, for example "https://www.linkedin.com/company/atlassian/". Provide at least one of Company LinkedIn URLs or Company names.

## `companyNames` (type: `array`):

Convenience input. Plain company names the actor will try to resolve to a public LinkedIn company. Names that cannot be resolved are reported in FAILED\_INPUTS.

## `maxCompanies` (type: `integer`):

Maximum number of companies processed in this run. Range 1-1000.

## `maxJobsPerCompany` (type: `integer`):

Maximum number of job cards scanned per company before stopping (sets jobs\_scan\_limited). Controls cost and runtime. Range 1-1000.

## `postedDate` (type: `string`):

Only consider jobs posted within this window, applied at the LinkedIn source where possible.

## `includeJobSamples` (type: `boolean`):

Add up to 3 sample job titles and URLs as flat fields per company. Does not switch to per-job output and does not affect counts or scoring.

## `includeChangeTracking` (type: `boolean`):

Compare each company's current open-jobs count against the previous run stored in a named key-value store, and fill previous\_open\_jobs\_total / open\_jobs\_change / open\_jobs\_change\_label. Ideal for scheduled runs.

## `stateStoreName` (type: `string`):

Name of the Apify named key-value store holding per-company snapshots between runs. Used only when change tracking is enabled.

## `minOpenJobs` (type: `integer`):

Save only companies with at least this many open jobs. Applied after aggregation; filtered companies are not saved or charged. Range 0-10000.

## `deduplicateCompanies` (type: `boolean`):

Skip repeated companies so each is processed and charged once (by company ID, then URL, then slug, then name).

## `proxyConfiguration` (type: `object`):

Apify Proxy configuration. Defaults to Apify Proxy enabled. Apify Residential is NOT supported and will fail the run at startup; if you need residential routing, supply your own provider via Custom proxy URLs (proxyUrls).

## Actor input object example

```json
{
  "companyUrls": [
    "https://www.linkedin.com/company/atlassian/",
    "https://www.linkedin.com/company/canva/"
  ],
  "companyNames": [],
  "maxCompanies": 100,
  "maxJobsPerCompany": 250,
  "postedDate": "any",
  "includeJobSamples": true,
  "includeChangeTracking": true,
  "stateStoreName": "linkedin-company-hiring-tracker-state",
  "minOpenJobs": 0,
  "deduplicateCompanies": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Flat table view with one row per company: identity, open-roles counts, top functions/locations, seniority and remote/hybrid mix, change since last run, and the derived hiring-signal score, label, and reason tags.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companyUrls": [
        "https://www.linkedin.com/company/atlassian/",
        "https://www.linkedin.com/company/canva/"
    ],
    "companyNames": [],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("coregent/linkedin-company-hiring-tracker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "companyUrls": [
        "https://www.linkedin.com/company/atlassian/",
        "https://www.linkedin.com/company/canva/",
    ],
    "companyNames": [],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("coregent/linkedin-company-hiring-tracker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companyUrls": [
    "https://www.linkedin.com/company/atlassian/",
    "https://www.linkedin.com/company/canva/"
  ],
  "companyNames": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call coregent/linkedin-company-hiring-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=coregent/linkedin-company-hiring-tracker",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/z1pcCaX0wbEe38jUh/builds/e9anuHibMcsdenboU/openapi.json
