> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crustdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Company Entity Watcher

> Watch a list of companies you supply and get notified the moment a profile changes — a headcount move, a funding round, a news mention, a rebrand. You supply the list; the watcher delivers the diff.

A **Company Entity Watcher** monitors **a list of companies you supply** and notifies you when *their* profiles change. Provide the companies (by domain, name, profile URL, or Crustdata company ID) and describe what to watch for. On each scheduled run, the watcher diffs every company against its last snapshot and delivers only what changed. The list is **yours to [edit any time](#edit-the-watched-list-any-time)** — add or remove companies without recreating the watch.

Where a [Company Discovery Watcher](/watcher-docs/company/discovery) **finds new** companies that match a filter, an Entity Watcher **tracks a known set** — a book of accounts, a competitor list, a portfolio — and surfaces *movement* within it:

* A tracked company **changes headcount** or **crosses a headcount threshold**.
* A tracked company **appears in the news**, **raises funding**, or **adds an investor**.
* A tracked company **rebrands**, **relocates its HQ**, or **shifts industry**.

Companies have their own create endpoint:

```
POST https://api.crustdata.com/watch/company
```

<Note>
  Every request needs the `x-api-version: 2025-11-01` header and a Bearer
  token. Replace `YOUR_API_KEY` with your key. This endpoint is **open to all
  API customers** — no per-endpoint grant needed.
</Note>

<Callout icon="coins" color="#5345e4">
  <strong>Pricing — you pay only for notifications.</strong> Every watch's first
  run is a <strong>free baseline</strong>: it records each company's starting
  values internally for diffing (never delivered) and never fires. After that you
  pay <strong>5 credits per notification</strong> — 5 credits for each watched
  company that changed on a run. A run that surfaces nothing costs nothing, and the
  price is the same whether you check hourly or monthly. If your balance is too low,
  the run is suspended rather than partially delivered. See
  [Pricing](/general/pricing) for the full breakdown.
</Callout>

## How a company entity watcher runs

<Steps>
  <Step title="Create the watch">
    `POST` your `entities` (companies 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`.
  </Step>

  <Step title="Baseline run (free)">
    On the first run, the watcher records an internal **snapshot** of the fields
    you're tracking — just enough to diff next time. With no prior snapshot to
    compare, the baseline **never fires and is never charged**, and isn't delivered;
    it only establishes "before". To read a company's current profile on demand, use
    the Enrich APIs.
  </Step>

  <Step title="Recurring runs">
    On your schedule (`every_hours`), the watcher re-checks each company, diffs it
    against its last snapshot, and delivers a notification for every company whose
    `track` condition just became true. You're charged 5 credits per notification.
  </Step>
</Steps>

<Note>
  An Entity Watcher fires only 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 you about a change it already reported. To read a list's current state
  on demand instead of watching for change, use the
  [Company Enrich](/company-docs/enrichment/introduction) API.
</Note>

## What you can track

The `track` describes the change that triggers a notification. It's a tree of
condition **leaves**, optionally combined with `and` / `or` groups.

A leaf has the shape:

```json theme={"theme":"vitesse-black"}
{ "field": "<dot-path into the profile>", "type": "<operator>", "value": "<optional>" }
```

| Operator                                | Applies to                                            | Fires when                                                               |
| --------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------ |
| `changed`                               | a **scalar** field (e.g. `headcount.total`)           | the value becomes different from the last snapshot                       |
| `added`                                 | an **array** field (e.g. `news`, `funding.investors`) | a new element appears in the array                                       |
| `>`, `<`, `=`, `!=`, `=>` (≥), `=<` (≤) | a scalar, or a predicate on an array element          | the comparison flips from false to true (e.g. `headcount.total => 1000`) |

<Note>
  Crustdata writes "greater-than-or-equal" as **`=>`** and "less-than-or-equal" as
  **`=<`** — not `>=` / `<=`. Those reversed forms return `unknown operator`.
</Note>

`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 for several signals at once. An `or` group fires if **any** child fires; an
`and` group fires only when **all** its conditions hold and at least one just
became true.

### Common company signals

These are examples — a company profile has **500+ addressable fields**, and you can track any of them (any scalar with `changed`, any array with `added`).

| Signal                        | `field`                                  | `type`              |
| ----------------------------- | ---------------------------------------- | ------------------- |
| Headcount changed             | `headcount.total`                        | `changed`           |
| Crossed a headcount threshold | `headcount.total`                        | `=>` (with `value`) |
| In the news                   | `news`                                   | `added`             |
| Raised funding                | `funding.total_investment_usd`           | `changed`           |
| New funding round             | `funding.last_round_type`                | `changed`           |
| New investor                  | `funding.investors`                      | `added`             |
| Made an acquisition           | `funding.acquisitions`                   | `added`             |
| Funding milestone             | `funding.milestones`                     | `added`             |
| Follower count changed        | `followers.count`                        | `changed`           |
| Hiring activity               | `hiring.openings_count`                  | `changed`           |
| Industry changed              | `taxonomy.professional_network_industry` | `changed`           |
| HQ relocated                  | `locations.country`                      | `changed`           |
| Name change / rebrand         | `basic_info.name`                        | `changed`           |

<Note>
  `field` paths address the same profile structure the Enrich API returns, so you
  can browse every trackable field in the
  [Company Enrich](/company-docs/enrichment/reference) reference. `changed` requires
  a **scalar** path, `added` requires an **array** path — mixing them returns a
  `400` (e.g. `"changed"` on the array `news` is rejected; use `"added"`).
</Note>

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

<CodeGroup>
  ```json Company momentum theme={"theme":"vitesse-black"}
  {
    "op": "or",
    "conditions": [
      { "field": "headcount.total", "type": "changed" },
      { "field": "funding.total_investment_usd", "type": "changed" },
      { "field": "news", "type": "added" }
    ]
  }
  ```

  ```json Funding activity theme={"theme":"vitesse-black"}
  {
    "op": "or",
    "conditions": [
      { "field": "funding.last_round_type", "type": "changed" },
      { "field": "funding.investors", "type": "added" },
      { "field": "funding.acquisitions", "type": "added" }
    ]
  }
  ```

  ```json Scaling past 1,000 theme={"theme":"vitesse-black"}
  {
    "op": "and",
    "conditions": [
      { "field": "headcount.total", "type": "=>", "value": 1000 },
      { "field": "hiring.openings_count", "type": "changed" }
    ]
  }
  ```
</CodeGroup>

Drop any of these into the `track` field of a create request. Groups can nest, so you can mix `and`/`or` — e.g. *"a headcount change **and** the company crossed 1,000 employees."*

## `track` and `fields` are different

These are two independent knobs, and the distinction matters:

| Key      | Controls                           | Answers                                                   |
| -------- | ---------------------------------- | --------------------------------------------------------- |
| `track`  | **When** the watch fires           | *"What change do I want to be notified about?"*           |
| `fields` | **What** the notification delivers | *"What data about the company do I want in the payload?"* |

Set them independently. You can track one field but deliver many (watch for a
headcount change, yet receive full funding history and news in the payload), or
track many and deliver few. `track` decides *whether* you get a notification;
`fields` decides *what's inside* it.

<Warning>
  **`fields` defaults to a minimal projection.** Omit `fields` and the delivered
  `record` contains only `basic_info`.

  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`.
</Warning>

### Requesting all fields

`fields` does **not** affect pricing — an entity watch is a flat **5 credits per
notification** no matter how many field groups you deliver (see
[Pricing](/general/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 (sibling of `track`), fixed at
create time.

```json Company — all fields theme={"theme":"vitesse-black"}
{
  "entities": { "domains": ["stripe.com"] },
  "track": { "field": "headcount.total", "type": "changed" },
  "fields": [
    "basic_info",
    "headcount",
    "funding",
    "locations",
    "taxonomy",
    "revenue",
    "hiring",
    "followers",
    "seo",
    "competitors",
    "social_profiles",
    "web_traffic",
    "employee_reviews",
    "people",
    "news"
  ]
}
```

<Note>
  Some groups require a field-level entitlement on your key (e.g. `technographics`).
  Requesting one you aren't entitled to returns a field-permission error, so list
  only the groups your key can access. Group names match the Enrich API — see the
  [Company Enrich](/company-docs/enrichment/reference) `fields` reference.
</Note>

## Choosing the companies

`entities` is an object mapping an **identifier type** to a list of values. You can
mix identifier types in one watch. A single watch holds up to **10,000 companies**.

| Accepted identifier keys            |
| ----------------------------------- |
| `domains`                           |
| `names`                             |
| `professional_network_profile_urls` |
| `crustdata_company_ids`             |

```json theme={"theme":"vitesse-black"}
{ "entities": { "domains": ["netflix.com", "stripe.com"] } }
```

## Schedule and limits

The `config` block controls timing and result caps:

| Field                 | Required | Meaning                                                                                                                                                                |
| --------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trigger.type`        | yes      | Must be `"interval"`.                                                                                                                                                  |
| `trigger.every_hours` | yes      | How often the watch runs, in hours (integer ≥ 1). Because you pay per notification, a higher frequency only makes changes surface sooner — it doesn't raise the price. |
| `max_results_per_run` | no       | Cap on notifications per run (1–1000, default 25).                                                                                                                     |
| `preferred_hour`      | no       | Hour of day (0–23) to prefer for the run.                                                                                                                              |
| `expires_at`          | no       | Auto-expire date, `YYYY-MM-DD`.                                                                                                                                        |

## Delivery channels

Add one or more channels to `notifications` and every change fans out to all of them.

| Channel     | Shape                                                                      |
| ----------- | -------------------------------------------------------------------------- |
| Webhook     | `{ "type": "webhook", "url": "https://…", "headers": { … } }`              |
| Slack       | `{ "type": "slack", "webhook_url": "https://hooks.slack.com/services/…" }` |
| Google Chat | `{ "type": "google_chat", "url": "https://chat.googleapis.com/…" }`        |

<Note>
  At least one notification channel is required. Every delivery is also recorded,
  so beyond the live push you can re-read a watch's past runs from the
  [run-history endpoint](#pulled-from-run-history).
</Note>

## Quick start

Watch two companies and get a Slack message whenever either changes headcount **or**
appears in the news. This request and response are real:

<CodeGroup>
  ```bash Request theme={"theme":"vitesse-black"}
  curl --request POST \
    --url https://api.crustdata.com/watch/company \
    --header 'authorization: Bearer YOUR_API_KEY' \
    --header 'content-type: application/json' \
    --header 'x-api-version: 2025-11-01' \
    --data '{
      "entities": { "domains": ["netflix.com", "stripe.com"] },
      "track": {
        "op": "or",
        "conditions": [
          { "field": "headcount.total", "type": "changed" },
          { "field": "news", "type": "added" }
        ]
      },
      "config": {
        "trigger": { "type": "interval", "every_hours": 168 },
        "max_results_per_run": 100
      },
      "notifications": [
        { "type": "slack", "webhook_url": "https://hooks.slack.com/services/T00/B00/xxxxxxxx" }
      ]
    }'
  ```

  ```json Response theme={"theme":"vitesse-black"}
  {
    "id": 46941,
    "kind": "entity",
    "dataset": "company",
    "api_version": "2025-11-01",
    "config_version": "2025-11-01",
    "status": "active",
    "entities": { "domains": ["netflix.com", "stripe.com"] },
    "track": {
      "op": "or",
      "conditions": [
        { "field": "headcount.total", "type": "changed" },
        { "field": "news", "type": "added" }
      ]
    },
    "fields": null,
    "config": {
      "trigger": { "type": "interval", "every_hours": 168 },
      "max_results_per_run": 100,
      "expires_at": null,
      "preferred_hour": null
    },
    "notifications": [
      { "type": "slack", "webhook_url": "https://hooks.slack.com/services/T00/B00/xxxxxxxx" }
    ],
    "created_at": "2026-07-16T03:16:38.351849Z",
    "last_run_at": null
  }
  ```
</CodeGroup>

## Edit the watched list any time

Unlike a saved search, an Entity Watcher's list is **mutable** — add or remove
companies as your account book or portfolio changes, without recreating the watch.
`PATCH` the watch with a new `entities` object:

<CodeGroup>
  ```bash Request theme={"theme":"vitesse-black"}
  curl --request PATCH \
    --url https://api.crustdata.com/watch/company/46941 \
    --header 'authorization: Bearer YOUR_API_KEY' \
    --header 'content-type: application/json' \
    --header 'x-api-version: 2025-11-01' \
    --data '{
      "entities": {
        "domains": ["netflix.com", "stripe.com", "airbnb.com"]
      }
    }'
  ```

  ```json Response theme={"theme":"vitesse-black"}
  {
    "id": 46941,
    "status": "active",
    "entities": {
      "domains": ["netflix.com", "stripe.com", "airbnb.com"]
    }
  }
  ```
</CodeGroup>

Newly added companies are **baselined silently** on the next run — they establish
their "before" snapshot first, so adding one 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

| Action               | Request                                                   |
| -------------------- | --------------------------------------------------------- |
| List your watches    | `GET /watch/company?status=active&limit=50&offset=0`      |
| Get one              | `GET /watch/company/{id}`                                 |
| Pause / resume       | `PATCH /watch/company/{id}` with `{ "status": "paused" }` |
| Add/remove companies | `PATCH /watch/company/{id}` with a new `entities`         |
| Delete               | `DELETE /watch/company/{id}` → `204`                      |

Full request/response for each, verified live:

<AccordionGroup>
  <Accordion title="List your watches — GET /watch/company">
    <CodeGroup>
      ```bash Request theme={"theme":"vitesse-black"}
      curl --request GET \
        --url 'https://api.crustdata.com/watch/company?status=active&limit=50&offset=0' \
        --header 'authorization: Bearer YOUR_API_KEY' \
        --header 'x-api-version: 2025-11-01'
      ```

      ```json Response theme={"theme":"vitesse-black"}
      [
        {
          "id": 46941,
          "kind": "entity",
          "dataset": "company",
          "status": "active",
          "entities": { "domains": ["netflix.com", "stripe.com"] },
          "track": { "type": "changed", "field": "headcount.total" },
          "config": { "trigger": { "type": "interval", "every_hours": 168 }, "max_results_per_run": 25 },
          "notifications": [{ "type": "slack", "webhook_url": "https://hooks.slack.com/services/T00/B00/xxxxxxxx" }],
          "created_at": "2026-07-16T13:39:18.247195Z",
          "last_run_at": "2026-07-16T13:39:18.598964Z"
        }
      ]
      ```
    </CodeGroup>

    Returns an array of your company watches. Filter with `status`, page with `limit` and `offset`.
  </Accordion>

  <Accordion title="Get one watch — GET /watch/company/{id}">
    <CodeGroup>
      ```bash Request theme={"theme":"vitesse-black"}
      curl --request GET \
        --url https://api.crustdata.com/watch/company/46941 \
        --header 'authorization: Bearer YOUR_API_KEY' \
        --header 'x-api-version: 2025-11-01'
      ```

      ```json Response theme={"theme":"vitesse-black"}
      {
        "id": 46941,
        "kind": "entity",
        "dataset": "company",
        "api_version": "2025-11-01",
        "config_version": "2025-11-01",
        "status": "active",
        "entities": { "domains": ["netflix.com", "stripe.com"] },
        "track": { "type": "changed", "field": "headcount.total" },
        "fields": null,
        "config": {
          "trigger": { "type": "interval", "every_hours": 168 },
          "max_results_per_run": 25,
          "expires_at": null,
          "preferred_hour": null
        },
        "notifications": [{ "type": "slack", "webhook_url": "https://hooks.slack.com/services/T00/B00/xxxxxxxx" }],
        "created_at": "2026-07-16T13:39:18.247195Z",
        "last_run_at": "2026-07-16T13:39:18.598964Z"
      }
      ```
    </CodeGroup>

    `last_run_at` is `null` until the baseline run completes, then carries the timestamp of the most recent run.
  </Accordion>

  <Accordion title="Pause or resume — PATCH status">
    <CodeGroup>
      ```bash Request theme={"theme":"vitesse-black"}
      curl --request PATCH \
        --url https://api.crustdata.com/watch/company/46941 \
        --header 'authorization: Bearer YOUR_API_KEY' \
        --header 'content-type: application/json' \
        --header 'x-api-version: 2025-11-01' \
        --data '{ "status": "paused" }'
      ```

      ```json Response theme={"theme":"vitesse-black"}
      { "id": 46941, "status": "paused" }
      ```
    </CodeGroup>

    A paused watch stops running until you resume it. Send `{ "status": "active" }` to resume.
  </Accordion>

  <Accordion title="Delete a watch — DELETE /watch/company/{id}">
    ```bash Request theme={"theme":"vitesse-black"}
    curl --request DELETE \
      --url https://api.crustdata.com/watch/company/46941 \
      --header 'authorization: Bearer YOUR_API_KEY' \
      --header 'x-api-version: 2025-11-01'
    ```

    Returns `204 No Content`. Deletion is terminal — the watch cannot be resumed.
  </Accordion>
</AccordionGroup>

## What a notification looks like

Every fired company carries two things: the **`changes`** array (exactly what moved,
independent of `fields`) and a **`record`** (the company's current data projected to
the `fields` you requested — the same shape the Enrich API returns, so with the
default `fields` it's just `basic_info`). The company's identity travels **inside**
the record as `crustdata_company_id`.

There are two ways to receive these, and they use **different envelopes**:

### 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_info", "headcount", "news"]`:

```json Webhook POST body theme={"theme":"vitesse-black"}
{
  "metadata": {
    "watch_id": 46941,
    "kind": "entity",
    "dataset": "company",
    "api_version": "2025-11-01",
    "run_id": 64210,
    "notification_id": "ntf_64210",
    "delivered_at": "2026-07-16T03:20:00.000000Z",
    "summary": { "delivered": 1, "total_count": 1, "max_results_per_run": 100, "truncated": false }
  },
  "results": [
    {
      "changes": [
        {
          "field": "headcount.total",
          "type": "changed",
          "from": 8200,
          "to": 8460
        }
      ],
      "record": {
        "basic_info": { "name": "…", "domain": "…" },
        "headcount": { "total": 8460 },
        "news": [ { "title": "…", "url": "…", "published_at": "…" } ],
        "crustdata_company_id": 631480
      }
    }
  ]
}
```

For an entity watch, `results` is a **flat list** — one object per fired company, each
with its own `changes` and `record`. A `changed` scalar reports as
`{ "field": …, "type": "changed", "from": …, "to": … }`; an `added` array reports 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:

```json GET /watcher/watches/{id}/runs/{run_id}/summary theme={"theme":"vitesse-black"}
{
  "id": 64210,
  "status": "SUCCESS",
  "new_records_count": 1,
  "notifications": [
    {
      "sent_at": "2026-07-16T03:20:00Z",
      "http_status": 200,
      "payload": {
        "subscription_id": 46941,
        "event_type": "indb:entity:company",
        "timestamp": "2026-07-16T03:20:00Z",
        "notifications": [
          {
            "uid": "company_631480_64210",
            "changes": [ { "field": "headcount.total", "type": "changed", "from": 8200, "to": 8460 } ],
            "record": { "basic_info": { … }, "headcount": { … }, "news": [ … ], "crustdata_company_id": 631480 }
          }
        ]
      }
    }
  ]
}
```

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

<CardGroup cols={2}>
  <Card title="Company Discovery Watcher" icon="radar" href="/watcher-docs/company/discovery">
    Need to *find* new matching companies instead of watching a known list? Use the
    Company Discovery Watcher.
  </Card>

  <Card title="Company field reference" icon="building" href="/company-docs/enrichment/reference">
    Browse the full set of trackable `field` paths for companies.
  </Card>
</CardGroup>
