Skip to main content
A Person Entity Watcher monitors a list of people you supply (by profile URL) and notifies you when their profiles change. You describe what to watch for; each scheduled run compares every person against their last snapshot and delivers only what changed. The list is yours to edit any timeadd or remove people without recreating the watch. Where Person Discovery Watchers find new people matching a filter, Entity Watchers track a known set — an ATS of candidates, key contacts, a target roster — and surface movement within it:
  • A tracked person starts a new job or changes their title or headline — a strong “in motion” signal.
  • A tracked person earns a certification or receives an award.
  • A tracked person relocates, adds a degree, or lists a new skill.
Create a person watch with:
Every request requires the x-api-version: 2025-11-01 header and a Bearer token. Replace YOUR_API_KEY in each example with your API key. This endpoint is open to all API customers — no per-endpoint grant is needed.
Pricing — you pay only for notifications. The first run of every watch is a free baseline that records each person’s starting values internally (for diffing only — it isn’t delivered) and never fires. After that you’re charged per notification — one charge for each watched person that changed on a run. A run that surfaces nothing costs nothing, and the rate does not depend on how often you check (every_hours) — only on the data-freshness tier you pick (5 credits at the default 30-day freshness). If your balance is too low, the run is suspended rather than partially delivered. See Pricing for the canonical rates.

How a person entity watcher runs

1

Create the watch

POST your entities (people to watch), a track (what to watch for), a config (schedule + caps), and one or more notifications channels. The response returns the full watch object, including its id.
2

Baseline run (free)

The first run records an internal snapshot of the fields you’re tracking for each person — just enough to diff against next time. With no prior snapshot to compare, the baseline never fires and is never charged, and isn’t itself delivered — it only establishes “before”. To read a person’s current profile on demand, use the Enrich APIs.
3

Recurring runs

On your schedule (every_hours), the watcher re-checks each person, diffs them against their last snapshot, and delivers a notification for every person whose track condition just became true. You’re charged 5 credits per notification.
An Entity Watcher only fires on a transition — the moment a value crosses from its previous state to a new one. It never fires on the baseline, and won’t re-notify about a change it already reported. To read a list’s current state on demand instead of watching for change, use the Person Enrich API.

What you can track

The track describes the change that triggers a notification — a tree of condition leaves, optionally combined with and / or groups. A leaf has the shape:
Crustdata writes “greater-than-or-equal” as => and “less-than-or-equal” as =< — not >= / <=. Those reversed forms return unknown operator.
value is required for the comparison operators and omitted for changed and added. Combine leaves with a group node — { "op": "and" | "or", "conditions": [ … ] } — to watch several signals at once. An or group fires if any child fires; an and group fires only when all conditions hold and at least one just became true.

Common person signals

Examples only — a person profile has 200+ addressable fields, and you can track any of them (any scalar with changed, any array with added).
field paths address the same profile structure the Enrich API returns, so browse the full set of trackable fields in the Person Enrich reference. changed requires a scalar path and added an array path — mixing them returns a 400 (for example, "changed" on the array experience.employment_details.current is rejected; watch basic_profile.current_title instead).

Recipes: combine signals

Group leaves with or to fire on any of several signals, or and to require them together. These track blocks are all live-verified.
Drop either into the track field of a create request. Groups nest, so you can mix and/or — e.g. “a title change and a new certification.”

track and fields are different

Two independent knobs, and the distinction matters: Set them independently. Track one field but deliver many (watch for a title change, yet receive the full employment history and education), or track many and deliver few. track decides whether you get a notification; fields decides what’s inside it.
fields defaults to a minimal projection. If you omit fields, the delivered record contains only basic_profile and social_handles.The field you’re tracking is not automatically added to the payload — the fired changes array always tells you exactly what moved, but to get the surrounding profile data in record, request it in fields.

Requesting all fields

fields does not affect pricing — you’re charged per notification at your chosen data-freshness tier (5 credits at the default), no matter how many field groups you deliver (see Pricing). There’s no cost reason to keep the payload thin; request whatever your workflow needs. There’s no wildcard — to receive the complete record, list every field group your API key is entitled to. fields is a top-level key (a sibling of track), fixed at create time. The delivered record uses the same schema as Person Enrich — identical field-group names and nesting — so the example below mirrors the full Person Enrich fields set.
Person — all fields
Some groups require a field-level entitlement on your key (e.g. certifications). Requesting a group you aren’t entitled to returns a field-permission error, so list only the groups your key can access. The group names match the Enrich API — see the Person Enrich fields reference.

Choosing the entities

entities maps an identifier type to a list of values. A single watch holds up to 10,000 people.

Schedule and limits

The config block controls timing and result caps:

Data freshness

config.refresh_frequency_days sets how fresh the tracked data is kept — how recently each tracked field is refreshed before a run compares it against the last snapshot. It is independent of trigger.every_hours: every_hours is how often the watch checks and notifies; refresh_frequency_days is how up-to-date the data it checks against is, which is what decides how quickly a real change is detected. Fresher data is priced higher — the per-notification charge is tiered by the freshness you choose: The accepted range is 1–30. A value between two tiers is billed at the fresher (more expensive) tier. Only fields backed by a refresh asset can be kept fresh this way; if a tracked field has no backing asset, the create is rejected — drop that field or the cadence.

Delivery channels

Add one or more channels to notifications and every change fans out to all of them.
At least one notification channel is required today. Every delivery is also recorded, so beyond the live push you can re-read a watch’s past runs from the run-history endpoint.

Quick start

Watch one person and get a webhook whenever they start a new job. This request and response are real.

Edit the watched list any time

Unlike a saved search, an Entity Watcher’s list is mutable — add or remove people as your pipeline or contact list changes, without recreating the watch. PATCH the watch with a new entities object:
Newly added people are baselined silently on the next run — they establish their “before” snapshot first, so adding someone never fires a spurious notification. You can also PATCH status (active / paused), config, and notifications. A watch’s track and fields are fixed once created; to change what you watch for, create a new watch.

Manage your watches

Full request/response for each, verified live.
Returns an array of your person watches. Filter with status, page with limit and offset.
last_run_at is null until the baseline run completes, then carries the most recent run’s timestamp.
A paused watch stops running until you resume it. Send { "status": "active" } to resume.
Request
Returns 204 No Content. Deletion is terminal — the watch cannot be resumed.

What a notification looks like

Every fired person carries two things: the changes array (exactly what moved, independent of fields) and a record (the person’s current data projected to the fields you requested — the same shape the Enrich API returns, so with default fields it’s just basic_profile + social_handles). The person’s identity travels inside the record as crustdata_person_id. Two ways to receive these, each with a different envelope:

Pushed to your webhook

When a watch fires, we POST this body to each channel. The example below is from a watch created with fields: ["basic_profile", "experience"]:
Webhook POST body
For an entity watch, results is a flat list — one object per fired person, each with its own changes and record. A changed scalar reports as { "field": …, "type": "changed", "from": …, "to": … }; an added array as { "field": …, "type": "added", "new_elements": [ … ] }.

Pulled from run history

To re-read a past run — or audit exactly what was delivered — the run-history endpoint returns the same content under a different envelope.
GET /watcher/watches/{id}/runs/{run_id}/summary

Smoke-test your webhook

Before a real change ever fires, push one sample notification through a watch to verify your receiver end-to-end — signature check, JSON parsing, routing — without waiting for a person to actually move. The test delivers the exact envelope a real run sends (same shape, same signature headers), flagged with metadata.test: true. It runs no diff, records no snapshot, costs no credits, and persists nothing.
All three body fields are optional: This request and response are real:
Your receiver gets this POST. It is the shape of a real notification — only metadata.test: true and the sample values ("sample_text", 1234, …) mark it as a test. The changes array below is abbreviated to two entries; the full array mirrors your watch’s track:
Request headers
Webhook POST body
The sample record carries placeholder values with the exact shape of a real notification (every field in the fields groups you requested is present), so your parser, signature check, and routing see production structure — just not real person data. A changed scalar reports { "type": "changed", "value": …, "from": …, "to": … } (value is the threshold target for >/< operators, null for a plain changed); an added array reports { "type": "added", "new_elements": [ … ] }.
Pass "count": 3 to receive three sample people in one envelope — distinct crustdata_person_ids and summary.delivered: 3 — so you can exercise how your receiver iterates results.
The signature is computed exactly as for a real notification — HMAC-SHA256 over <timestamp>.<raw-body> keyed by your API key, delivered as x-crustdata-signature: t=<unix>,v1=<hex>. A test that verifies here verifies real deliveries.

Rate limits

Watch-management requests are rate-limited to 10 requests per minute per API key. This bounds bursty create/update loops; steady use is unaffected.

Person Discovery Watcher

Need to find new matching people instead of watching a known list? Use the Person Discovery Watcher.

Person field reference

Browse the full set of trackable field paths for people.