Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Monitor Job Changes and Company Changes with an API (Snapshot and Diff)

LeadOcean has no webhook watcher. Here is the 60-line script that does the job on a monthly dataset, with the records each run costs.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

TL;DR

  • Time: about 45 minutes to build, then one cron line.
  • Cost: about 400 records for the first snapshot of a 200-person watchlist (200 search rows, 200 enrichments), then about 200 per monthly check plus one per company. Sizing calls are free.
  • Output: a list of people whose current role changed and companies whose record changed, ready to push to Slack or a CRM.

You can monitor job changes with LeadOcean by saving a snapshot of each person and company, re-pulling it on a schedule, and diffing the two. LeadOcean does not push webhooks. The dataset refreshes monthly, so a monthly diff is the honest cadence.

If you need hourly alerts delivered to a webhook, Crustdata's Watcher page describes exactly that, with checks as often as hourly (Crustdata Watcher, October 2026). If a monthly signal is enough, this recipe costs a few hundred records and no new vendor.

Prerequisites

  • A LeadOcean API key in $LEADOCEAN_API_KEY. The Free plan gives 1,000 records for life, no card, 5 requests a second.
  • Python 3.9 or later with requests.
  • A watchlist: up to a few hundred company domains and the roles you care about.
  • Somewhere to run a monthly job (cron, GitHub Actions, n8n).

Step 1: Size the watchlist for free

Count before you spend. Counts cost zero records, so run them as often as you like. The free MCP tool leadocean_count_leads takes the same filters as search. By default it counts mailable people only: verified, catch_all_valid or catch_all.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["CA"],
    "limit": 1
  }'

count=true goes in the query string, not the JSON body, and meta.total caps at 100,000. For company lists use GET /v1/companies/count, which is also free.

Step 2: Pull the watchlist and keep the person IDs

Search by company domain and role. The body is flat JSON, filters are arrays, limit runs 1 to 100, and you page with cursor. Each person returned costs one record.

python
import os, json, requests

API = "https://api.leadocean.io"
H = {"x-api-key": os.environ["LEADOCEAN_API_KEY"], "Content-Type": "application/json"}

def watchlist(domains, titles):
    ids, cursor = [], None
    while True:
        body = {"domain": domains, "title": titles, "limit": 100}
        if cursor:
            body["cursor"] = cursor
        r = requests.post(f"{API}/v1/people/search", headers=H, json=body)
        r.raise_for_status()
        j = r.json()
        ids += [p["meta"]["source_ids"]["person_id"] for p in j["data"]]
        cursor = j.get("meta", {}).get("nextCursor")
        if not cursor:
            return ids

ids = watchlist(["acme.com", "globex.com"], ["VP Sales", "Head of Sales"])
json.dump(ids, open("watchlist.json", "w"))

Each row carries its ID at meta.source_ids.person_id. Run this once. Save watchlist.json. Every later run reuses the IDs.

Step 3: Snapshot each person's current role

Enrich by person_id. It returns the current role in contact_current_experiences[], plus resume_data with past roles when held. An enrichment that finds nothing is a 404 and costs nothing.

python
def current_role(pid):
    r = requests.post(f"{API}/v1/people/enrich", headers=H, json={"person_id": pid})
    if r.status_code == 404:
        return None
    r.raise_for_status()
    return r.json()["data"]["contact_data"].get("contact_current_experiences", [])

snapshot = {pid: current_role(pid) for pid in ids}
json.dump(snapshot, open("people_snapshot.json", "w"))

Keep the loop under your plan's rate limit: 5 requests a second on Free, 200 on Agency. Past employers carry no company_id yet, so match them by name, not ID.

Step 4: Snapshot the companies

Do the same for the companies behind the watchlist. Company enrich takes a domain (or a linkedin_url) in the JSON body. Keep the record's company_data and company_metrics blocks, then compare them on the next run.

python
def company(domain):
    r = requests.post(f"{API}/v1/companies/enrich", headers=H, json={"domain": domain})
    if r.status_code == 404:
        return None
    r.raise_for_status()
    d = r.json()["data"]
    return {"company_data": d.get("company_data"), "company_metrics": d.get("company_metrics")}

companies = {d: company(d) for d in ["acme.com", "globex.com"]}
json.dump(companies, open("company_snapshot.json", "w"))

Leave the record's meta block out of the snapshot. It describes the lookup, not the company, so keeping it makes every record look changed.

Step 5: Diff on a schedule

Run the same pulls monthly and compare. This is the whole monitor.

python
def diff(old, new):
    out = []
    for key, now in new.items():
        before = old.get(key)
        if before != now:
            out.append({"key": key, "before": before, "after": now})
    return out

old_p = json.load(open("people_snapshot.json"))
new_p = {pid: current_role(pid) for pid in ids}
moves = diff(old_p, new_p)

old_c = json.load(open("company_snapshot.json"))
new_c = {d: company(d) for d in old_c}
changed = diff(old_c, new_c)

print(json.dumps({"job_changes": moves, "company_changes": changed}, indent=2))
json.dump(new_p, open("people_snapshot.json", "w"))
json.dump(new_c, open("company_snapshot.json", "w"))

Schedule it the day after the monthly refresh:

bash
0 6 3 * * cd /opt/monitor && python3 monitor.py >> monitor.log 2>&1

A person who moved shows a different contact_current_experiences[]. When the new employer is a target account, pass the same person_id to GET /v2/people/email/work for a fresh address. That call costs one record, including a miss.

What you get

A free count run on 2026-10-21 for the Step 1 filters (jobLevel VP, jobFunction Sales & Business Development, country CA, mailable by default: verified, catch_all_valid or catch_all):

json
{
  "total": 5574,
  "totalIsExact": true,
  "totalAll": 18800,
  "distinctCompanies": 3873,
  "credits": 0
}

The script prints this shape. The values are placeholders (Acme, Globex, a made-up ID), not a real record:

json
{
  "job_changes": [
    {
      "key": "1000000001",
      "before": [{"job_title": "VP Sales", "company_name": "Acme"}],
      "after":  [{"job_title": "CRO", "company_name": "Globex"}]
    }
  ],
  "company_changes": []
}

An empty company_changes list is normal. Most companies do not change in a month.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCheck $LEADOCEAN_API_KEY is exported in the job's environment.
429Requests above the plan rate limit (5 a second on Free, 200 on Agency)Honor Retry-After and sleep between calls.
402The plan's records are spentFree is 1,000 records for life. Move to Solo ($97 a month) or shrink the watchlist.
Empty search resultsA filter value that is not an exact enum stringCheck values with leadocean_list_enum_values, for example VP, not vp, in jobLevel.
404 on enrichWe hold nothing for that person_idSkip it. It costs nothing.
meta.depthCapped is trueOne search pages to 10,000 rowsNarrow the filters, for example one domain batch per search.

FAQ

Does LeadOcean send job change alerts or webhooks?

No. You pull and diff. The dataset is refreshed monthly, so a monthly run catches every change we hold. For hourly alerts, look at a watcher product.

How many records does monitoring cost?

One record per person or company returned. Watching 200 people costs about 200 records a run. Counts, 404s and empty pages are free. Paid plans run on a fair-use pace, not credits. See pricing.

Can I see where someone worked before?

Yes. Person enrich returns resume_data with past roles, education and certifications, each when held. That helps you spot a champion who moved into a target account.

How do I track headcount growth at a company?

Diff the company record month to month. The company headcount enrichment page covers the field.

Where do I go next?

Read the company enrichment API guide for the full record shape, and best company data APIs to compare vendors.

Start monitoring job changes with a free API key

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →