# Job Change Tracker (`mambalabs/job-change-tracker`) Actor

Checks whether your contacts still work where your CRM says. Returns current employer, title, start month, role history, and left, landed, or promoted status, with a namesake check and a source date on every row. Runs on your own data and your own search, Prospeo, and verifier keys.

- **URL**: https://apify.com/mambalabs/job-change-tracker.md
- **Developed by:** [Mamba Labs](https://apify.com/mambalabs) (community)
- **Categories:** Lead generation, Automation, Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $21.25 / 1,000 person reads

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Job Change Tracker

Send the contacts in your CRM. Get one row back per person: where they work now, their title, when they started, their role history, and whether they left the company you know them at, landed somewhere new, or moved up inside the same company.

Every row says how the person was matched, which source the answer came from, and how old that source is.

### What it does

For each person you send (a name plus the company your records place them at), the actor:

1. Reads any role history you already hold, such as a stored copy from your outreach tool or your own CRM. This costs nothing extra.
2. Finds the person's LinkedIn profile URL with your web search key when you did not send one. A namesake check accepts a profile only when the name matches and the history or the search result shows your company.
3. Reads the search result line for that profile, which usually names the current title and company.
4. Reads the role history with start and end months from Prospeo on your key.
5. Checks the company's own team and about pages, and Google News, for free.
6. Verifies the old work address with BounceBan on your key, only while the person still reads as at the company.

It then works out the status against the company you sent. Career Break and "Open to work" entries never count as a job. A move to a new employer is a new hire. A new role at the same employer, with the earlier role open or ended within 3 months, is a promotion.

The actor never opens linkedin.com and never calls another developer's actor. Paid lookups run on your own provider keys and bill your provider accounts.

### What you get

| Field | Example |
|---|---|
| `employment_status` | `current`, `between_roles`, `unknown` |
| `leaver_status` | `still_at_company`, `left`, `unknown` |
| `landing_status` | `landed`, `not_landed`, `not_applicable`, `unknown` |
| `current_company_name`, `current_title` | `AccuWeather`, `VP, AI Partnerships` |
| `start_month`, `start_precision`, `days_in_role` | `2026-08`, `month`, `74` |
| `move_type`, `move_type_reason`, `seniority_change` | `new_hire`, `different_company_name`, `same` |
| `previous_company_name`, `previous_title`, `left_month` | `Dataminr`, `Sr. Director`, `2026-07` |
| `role_history` | array of roles with company, title, start and end month |
| `match_method`, `match_confidence`, `match_evidence` | `name_search`, `high`, `["name","company_domain"]` |
| `source`, `source_detail`, `source_date`, `source_age_days` | `byod`, `heyreach`, `2026-10-07`, `7` |
| `evidence`, `sources_tried` | why the status was set, and what was tried |
| `read_status`, `error_reason` | `ok`, or why there is no reading |
| `billable_events_json` | the events charged for this row |

Rows are flat and snake_case, so they load into Clay, a sheet, or a CRM as they are. Two dataset views come with every run: Overview and Evidence.

### Input

One person:

```json
{
  "person_name": "Jane Doe",
  "company_domain": "acme.io",
  "company_name": "Acme",
  "last_known_title": "Head of Growth",
  "profile_url": "https://www.linkedin.com/in/janedoe",
  "email": "jane@acme.io",
  "input_id": "crm-48213",
  "serperApiKey": "...",
  "prospeoApiKey": "...",
  "bouncebanApiKey": "..."
}
```

Many people go in `people`, each with the same keys. A person can carry `known_history`, a role history you already hold:

```json
{ "source": "heyreach", "source_date": "2026-10-07", "experiences": "[{\"companyName\":\"Acme\",\"title\":\"Head of Growth\",\"start\":{\"year\":2021,\"month\":3},\"end\":{\"year\":2026,\"month\":7}}]" }
```

`roles` (with `company_name`, `title`, `start_month`, `end_month`) and a LinkedIn reader row (`linkedin_reader`) are read too. A history newer than `maxSourceAgeDays` (default 30) skips the Prospeo call for that person.

### Keys and what each one costs you

| Key | Used for | Calls per person |
|---|---|---|
| Serper or SerpAPI | Profile lookup and the current role line | 1 with a profile URL, 1 to 3 without |
| Prospeo | Role history with months | 0 or 1 (0 when your fresh history is used) |
| BounceBan | Old work address check | 0 or 1, only when the person still reads as at the company |

With no key at all, the actor still reads your own history, the company team pages, and Google News.

### Pricing

Pay per event. You pay for results, plus a small start fee per run.

| Event | Fires when | Free | Bronze | Silver | Gold |
|---|---|---|---|---|---|
| `person_read` | A person comes back with `read_status` ok | 0.025 USD | 0.02375 USD | 0.0225 USD | 0.02125 USD |
| `profile_resolved` | You sent no profile URL and the run found and accepted one at high or medium match | 0.006 USD | 0.0057 USD | 0.0054 USD | 0.0051 USD |
| `apify-actor-start` | Once per run, per GB of memory (one at the 256 MB default) | 0.001 USD | 0.001 USD | 0.001 USD | 0.001 USD |

Not charged: not found, ambiguous, error, and skipped rows; cache hits; and a person repeated in the same run. Set `max_charge_usd` and the run stops before any row that would pass it. On the free Apify plan the actor reads up to 60 people a month (1.50 USD of evaluation).

### Limits

- Your sources decide coverage. In a 60 person test (39 leavers, 21 still employed), keys alone caught 12 of 39 leavers with 0 false flags in 17; adding a fresh history of the buyer's own raised it to 28 of 39, still with 0 false flags. Prospeo lags for people who left, which is why a "still at company" answer from it carries low confidence.
- The search result line is a summary, not a full profile. Start months come from your own history or Prospeo.
- `left_month` needs a dated history. A leaver found only by the search line or the email check has no month.
- Two profiles that both pass the namesake check return `ambiguous` and nothing is charged.

### Pairs with

- New Hire Finder: from a company, find who started a matching role.
- Career Change Signal Engine: watches a contact list over time and delivers only the changes.

Built by [Mamba Labs](https://apify.com/mambalabs).

# Actor input Schema

## `person_name` (type: `string`):

Full name of the person to check.

## `company_domain` (type: `string`):

Website domain of the company your records place this person at, for example microsoft.com. Left, landed, and promoted are worked out against this company.

## `company_name` (type: `string`):

Name of that company. Used with or instead of the domain to match roles in the person's history.

## `last_known_title` (type: `string`):

The title your records hold. Echoed on the row as input_title.

## `profile_url` (type: `string`):

Optional. The person's LinkedIn profile URL, used as the identity. This actor never opens it. Without it, the run looks the profile up with your search key and charges profile_resolved when it finds and accepts one.

## `email` (type: `string`):

Optional. The person's work address at the last known company. With a BounceBan key, an undeliverable answer marks the person as left.

## `company_linkedin_url` (type: `string`):

Optional. The strongest company match when a role history carries LinkedIn company URLs.

## `location` (type: `string`):

Optional. Echoed for your records.

## `input_id` (type: `string`):

Optional. Returned unchanged on the row so you can join it back to your CRM or table.

## `known_history` (type: `string`):

Optional. A role history you already have, as JSON: {"source": "heyreach", "source_date": "2026-10-07", "experiences": \[...]} or {"source": "crm", "roles": \[{"company_name", "title", "start_month", "end_month"}]}. Read before any paid key; a history newer than Max source age skips Prospeo.

## `people` (type: `array`):

Many people at once. Each item takes the same keys as the single person fields: person_name, company_domain, company_name, last_known_title, profile_url, email, company_linkedin_url, location, input_id, and known_history (an object).

## `serperApiKey` (type: `string`):

Optional. Web search (serper.dev): finds profile URLs and reads the search result line for the current role. About 1 to 3 searches per person.

## `serpApiKey` (type: `string`):

Optional. Used for web search only when no Serper key is set.

## `prospeoApiKey` (type: `string`):

Optional. Prospeo enrich-person: the person's role history with start and end months. Skipped when you send a fresh history of your own.

## `bouncebanApiKey` (type: `string`):

Optional. Verifies the old work address only while the person still reads as at the company. Only an undeliverable answer is used.

## `maxSourceAgeDays` (type: `string`):

A history you send that is older than this is treated as lagging, so the run also reads Prospeo and the search result. Default 30.

## `checkCompanySite` (type: `string`):

Free. Looks for the person on up to 4 team and about pages of the last known company domain.

## `checkNews` (type: `string`):

Free. Cites up to 3 headlines from the last year that name the person with a move word. Evidence only; a headline never sets the employer.

## `verifyOldEmail` (type: `string`):

With a BounceBan key: one check per person who still reads as at the company. Uses the address you send, or one Prospeo returns on the company domain.

## `includeRoleHistory` (type: `string`):

No keeps role_count and drops the role_history array.

## `cacheTtlDays` (type: `string`):

Reuse a reading made in the last N days, uncharged, from a store in your own Apify account. 0 turns it off. Rows with your own history are never cached. Default 7.

## `maxPeople` (type: `string`):

Stop after this many people. Default 1000.

## `max_charge_usd` (type: `string`):

Optional. The run stops before any row that would take its event charges past this amount.

## `source_tag` (type: `string`):

Internal attribution tag.

## Actor input object example

```json
{
  "person_name": "Satya Nadella",
  "company_domain": "microsoft.com",
  "company_name": "Microsoft",
  "last_known_title": "Chairman and Chief Executive Officer",
  "people": [],
  "maxSourceAgeDays": "30",
  "checkCompanySite": "true",
  "checkNews": "true",
  "verifyOldEmail": "true",
  "includeRoleHistory": "true",
  "cacheTtlDays": "7",
  "maxPeople": "1000"
}
```

# Actor output Schema

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

No description

## `evidence` (type: `string`):

No description

## `results` (type: `string`):

No description

# 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 = {
    "person_name": "Satya Nadella",
    "company_domain": "microsoft.com",
    "company_name": "Microsoft",
    "last_known_title": "Chairman and Chief Executive Officer"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mambalabs/job-change-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 = {
    "person_name": "Satya Nadella",
    "company_domain": "microsoft.com",
    "company_name": "Microsoft",
    "last_known_title": "Chairman and Chief Executive Officer",
}

# Run the Actor and wait for it to finish
run = client.actor("mambalabs/job-change-tracker").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "person_name": "Satya Nadella",
  "company_domain": "microsoft.com",
  "company_name": "Microsoft",
  "last_known_title": "Chairman and Chief Executive Officer"
}' |
apify call mambalabs/job-change-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mambalabs/job-change-tracker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

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