# Career Change Signal Engine (`mambalabs/career-change-signal-engine`) Actor

Send the contacts you watch and get back only what changed: who left, who landed where, who replaced them, and who started after a role closed. Each row has evidence, a source date, a segment, a first line draft, and Slack, HubSpot, HeyReach, and Instantly payloads. Runs on your own data and keys.

- **URL**: https://apify.com/mambalabs/career-change-signal-engine.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 $17.00 / 1,000 contact checkeds

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

### 🔑 What can Career Change Signal Engine do?

Career Change Signal Engine reads the people on your watch list and returns only what changed since you last checked: who left, who landed where, who replaced them, and who started after a role closed. Every row says how the person was matched, which source answered, and how old that source is.

- **Only changes.** Send last run's reading per person (`state_source: caller`), or let the engine keep state between runs (`state_source: actor`). A person who has not changed returns no row.
- **Your watch list, nothing else.** The engine reads only the `people` you send. A run takes up to 150 people by default and 1,000 at most.
- **Leavers and landings.** No current role at the company you know: a leaver. A current role somewhere else: a move or a landing, with the new company, title, and start month when a source dates them.
- **Replacements.** For a leaver, the current holder of the departed title at the old company, ranked, for a person to confirm.
- **New hires after a closed posting.** Send Account Hiring Watch `closed` rows and get the people who started in that role.
- **Replies and bounces as evidence.** "No longer with", "has left Acme", and hard bounces count. Out of office replies do not.
- **Ready to use.** Segment (landed, replacement, new hire, past champion), a first line draft, and Slack card, HubSpot properties, HeyReach, and Instantly payloads on every row.
- **No LinkedIn fetch.** Readings come from data you already hold (a HeyReach stored copy, a Proofed LinkedIn reader row, or your own role list), web search, Prospeo, and an email verifier on your own keys, and the company's public team pages.

### 💡 Why use Career Change Signal Engine?

Champions who change jobs buy again, and the person who replaces a leaver inherits the budget. Most CRMs learn about a move months late. The engine checks the people you care about every two weeks and hands you the change with its evidence, so a person decides in seconds whether to reach out.

### 🛠️ How to watch contacts for job changes

1. Add the people you watch to `people`: name, company, title, and a LinkedIn profile URL when you have it.
2. Add your keys in `providerKeys` (optional): `serper` or `serpapi`, `prospeo`, `bounceban`, `findymail`.
3. Run it. Schedule it every 14 days with the same `watch_list_id`.
4. Read the signals in the dataset, or set `webhook_url` to receive them signed.

### 📋 What data does Career Change Signal Engine return?

| Field | Example |
|---|---|
| `signal_type` | `leaver_detected`, `landed`, `moved`, `replacement_proposed`, `new_hire` |
| `segment` | `landed`, `replacement`, `new_hire`, `past_champion`, `watching` |
| `person_name`, `profile_url`, `external_id` | `Jane Doe`, `https://www.linkedin.com/in/janedoe`, your id |
| `previous_company_name`, `previous_title`, `left_month` | `Acme`, `Head of Content`, `2026-08` |
| `current_company_name`, `current_title`, `start_month`, `days_in_role` | `Contoso`, `VP Content`, `2026-09`, `40` |
| `leaver_confidence`, `identity_match`, `needs_confirmation` | `likely`, `profile_url`, `true` |
| `evidence` | each signal with its source and source date |
| `first_line_draft` | a template line from row fields only |
| `slack_card_json`, `hubspot_properties_json`, `heyreach_tags` | delivery payloads |
| `source`, `source_detail`, `source_date` | `byod`, `proofed_linkedin_reader`, `2026-10-14` |

One `run_summary` row per run carries the counts and is never charged.

### 💵 How much does it cost?

Pay per event:

| Event | Price | When |
|---|---|---|
| `contact_checked` | 0.020 USD | once per watched person per run with a usable reading |
| `signal_delivered` | 0.068 USD | once per change returned |

BRONZE, SILVER, and GOLD plans pay 5, 10, and 15 percent less. Suppressed people, people not due, unreadable people, the run summary, and rows held for review cost nothing. `max_spend_usd` stops a run before it crosses your limit. Calls to your own providers bill your own accounts.

### ⌨️ Input

| Field | Default | Notes |
|---|---|---|
| `people` | required | Your watch list |
| `watch_list_id` | `default` | Names the list and its state |
| `state_source` | `actor` | `caller` when you keep state and send `last_reading` |
| `first_run_mode` | `report` | `baseline` stores state and returns nothing on the first run |
| `max_people_per_run` | 150 | At most 1,000; a longer list fails before any charge |
| `providerKeys` | none | Secret; never logged or stored |
| `replies`, `bounces`, `posting_changes` | none | Optional evidence |
| `suppression` | none | Emails, profile URLs, opt outs, client domains |
| `webhook_url`, `webhook_secret` | none | Standard Webhooks signature |

### ⚠️ Known limits

- A logged out profile view gives employment status and company, not titles or dates. Titles and start months come from a HeyReach stored copy, Prospeo, or your own data.
- A person with two open roles reads as still at the company you know.
- A leaver found only from a reply, with no reading, is `likely` and needs a person to confirm.
- Executive moves from the Company Change Event Feed are not in this version.

### ❓ FAQ

**Does it scrape LinkedIn?** No. It requests no linkedin.com page.

**Which keys do I need?** None for data you already hold. A search key and Prospeo add readings for people without one.

**Can I keep state myself?** Yes: `state_source: caller` and send each person's `last_reading`.

### 🆘 Support

Open an issue on the actor page.

> ℹ️ **Sourcing and legal.** Readings come from data you supply, from providers you hold keys for, and from public company pages. The actor returns information about the people you send. You are responsible for a lawful basis to process it and for how you use the output.

# Actor input Schema

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

Your watch list, one object per person: person_name, profile_url (optional), company_domain, company_name, title, work_email, relationship (prospect, mgl, deal_contact, customer_champion), external_id, crm_id, deal_id, known_history (a HeyReach copy, a Proofed LinkedIn reader row, or generic roles), last_reading (with state_source caller). The engine reads only these people. At most max_people_per_run per run.

## `watch_list_id` (type: `string`):

Names the list. State is kept per watch list id when state_source is actor.

## `state_source` (type: `string`):

actor: the engine keeps state between runs in a named store. caller: you send last_reading per person and keep state yourself.

## `read_mode` (type: `string`):

stored_first: your known_history first, then your keys. live_only: ignore known_history. stored_only: only known_history, no key calls.

## `first_run_mode` (type: `string`):

report: the first run returns every change it finds. baseline: the first run stores state and returns nothing, charging only contact_checked.

## `max_people_per_run` (type: `integer`):

A list longer than this fails the run before any read or charge. Default 150, at most 1,000.

## `providerKeys` (type: `object`):

Your own keys: serper or serpapi (web search), prospeo, bounceban (verifier), findymail, exa. Never written to state, output, logs, or the webhook.

## `linkedin_reader_dataset_id` (type: `string`):

Optional: the dataset id of a Proofed LinkedIn reader run on the same Apify account. Rows join to people by profile URL.

## `replies` (type: `array`):

Optional reply texts: external_id or work_email or profile_url, text, received_at, channel, is_auto_reply. Text is classified and dropped; only its sha1 is kept.

## `bounces` (type: `array`):

Optional bounces: work_email, bounce_type, smtp_code, smtp_text, bounced_at.

## `posting_changes` (type: `array`):

Optional Account Hiring Watch rows. A closed posting starts a new hire search at that company.

## `suppression` (type: `object`):

emails, profile_urls, opted_out_emails, opted_out_profile_urls, company_domains, company_names. Suppressed people are dropped before the read and cost nothing.

## `icp_tiers` (type: `object`):

Optional map of company domain to tier, shown on landed rows. Never blocks a row.

## `features` (type: `object`):

Switches: replacement, closed_posting, contact_data, first_line, icp, include_promotions. Defaults on except include_promotions.

## `sender_context` (type: `string`):

Optional topic phrase for the first line draft, for example: content production at scale.

## `max_replacement_candidates` (type: `integer`):

Candidates proposed per leaver. A person confirms each.

## `max_spend_usd` (type: `integer`):

Stops the run before a charge that would cross it.

## `sweep_interval_days` (type: `integer`):

With state_source actor, a person read within this many days is skipped.

## `concurrency` (type: `integer`):

People read at once.

## `webhook_url` (type: `string`):

Optional: signed Standard Webhooks POST of up to 100 signal rows per request.

## `webhook_secret` (type: `string`):

Signing secret; a whsec\_ value is base64 decoded.

## Actor input object example

```json
{
  "people": [
    {
      "person_name": "Satya Nadella",
      "company_domain": "microsoft.com",
      "company_name": "Microsoft",
      "title": "Chairman and Chief Executive Officer",
      "external_id": "example-1"
    }
  ],
  "watch_list_id": "default",
  "state_source": "actor",
  "read_mode": "stored_first",
  "first_run_mode": "report",
  "max_people_per_run": 150,
  "max_replacement_candidates": 3,
  "sweep_interval_days": 14,
  "concurrency": 6
}
```

# Actor output Schema

## `signals` (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 = {
    "people": [
        {
            "person_name": "Satya Nadella",
            "company_domain": "microsoft.com",
            "company_name": "Microsoft",
            "title": "Chairman and Chief Executive Officer",
            "external_id": "example-1"
        }
    ],
    "watch_list_id": "default"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mambalabs/career-change-signal-engine").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 = {
    "people": [{
            "person_name": "Satya Nadella",
            "company_domain": "microsoft.com",
            "company_name": "Microsoft",
            "title": "Chairman and Chief Executive Officer",
            "external_id": "example-1",
        }],
    "watch_list_id": "default",
}

# Run the Actor and wait for it to finish
run = client.actor("mambalabs/career-change-signal-engine").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 '{
  "people": [
    {
      "person_name": "Satya Nadella",
      "company_domain": "microsoft.com",
      "company_name": "Microsoft",
      "title": "Chairman and Chief Executive Officer",
      "external_id": "example-1"
    }
  ],
  "watch_list_id": "default"
}' |
apify call mambalabs/career-change-signal-engine --silent --output-dataset

```

## MCP server setup

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

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/vX2Yga1a6wGaXaP14/builds/aPIfWlKb5foBsYcn7/openapi.json
