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

# Engagement watches

> Collect the people who engage with posts about your category, or with your competitors' posts, as leads.

Someone who likes a post about your category, or a competitor's post, is shopping in that
category right now. That is a sharper buying signal than most firmographics, and it names a
person rather than a company.

An engagement watch is a standing subscription: you describe what to watch once, and TAMTAM
keeps collecting the people who engage with it.

## How it fits together

<Steps>
  <Step title="Create a watch">
    [Create an engagement watch](/api-reference/engagement-watches/create-an-engagement-watch)
    with either a keyword query or a competitor's LinkedIn company ID.
  </Step>

  <Step title="Point it at your personas">
    Pass `persona_ids` from [List personas](/api-reference/personas/list-personas) to say who
    counts as a lead. Sharpen a persona later and every watch using it sharpens too.
  </Step>

  <Step title="Poll the feed">
    [List engagement signals](/api-reference/engagement-watches/list-engagement-signals) returns
    who has engaged. Save `next_cursor` and pass it back to pick up where you left off.
  </Step>
</Steps>

## The two kinds of watch

| `kind`    | You provide                            | It collects                               |
| --------- | -------------------------------------- | ----------------------------------------- |
| `topic`   | `keywords` — a LinkedIn boolean query  | People who liked posts matching the query |
| `company` | `company_linkedin_id` — one competitor | People who liked that company's own posts |

A topic query is passed to LinkedIn exactly as you write it, boolean operators included:

```json theme={null}
{
  "kind": "topic",
  "name": "Lean manufacturing",
  "keywords": "\"lean manufacturing\" OR \"visual management\"",
  "persona_ids": ["3f1c...", "9a2d..."],
  "countries": ["FR", "BE"]
}
```

A watch's `kind` and its target are fixed once created. Repointing one would leave the people
it has already collected describing something it no longer watches, so create a second watch
instead.

## Who counts as a lead

`persona_ids` decides. Each engager's job title is matched against those personas using the
same rules the rest of TAMTAM uses, including their seniority bands and country constraints.

Two behaviours are worth knowing because they are deliberate:

* **Engagers who match nothing are still collected**, with an empty `matched_persona_ids`.
  Widening a persona later therefore surfaces people you had already collected, rather than
  only affecting future sweeps.
* **Filtering happens when you read.** Pass `persona_id` to
  [List engagement signals](/api-reference/engagement-watches/list-engagement-signals) for
  matched people only. Reading unfiltered is how you tell *nobody relevant engaged* from *my
  personas are too narrow* — if every engager comes back unmatched, the personas are usually
  the problem.

A watch with no `persona_ids` filters nobody and collects everyone who engaged.

## A new watch starts from now, not from history

A watch collects people who engage **after you create it**, plus whatever is still inside
LinkedIn's most recent window at its first sweep. It cannot reach back further, because
LinkedIn's content search only serves recent posts — there is no way to ask it for last
quarter.

So a brand-new watch looks thin for a day or two and then fills in. That is the one place our
provider's limits are visible to you, and it is worth knowing before you conclude a query is
wrong: judge a watch on its second week, not its first afternoon.

## Polling the feed

Identical to [ICP signals](/guides/icp-signals): call with no cursor, process the events, save
`next_cursor`, pass it back next time.

```bash theme={null}
curl -H "Authorization: Bearer $TAMTAM_API_KEY" \
  "https://api.tamtam.ai/v2/engagement-signals?cursor=$SAVED_CURSOR"
```

`next_cursor` comes back on every non-empty page including the last, and `has_more: false`
means you are caught up rather than finished.

**Delivery is at-least-once.** Every event carries a stable `id`. Store the ids you have
processed and skip repeats; do not assume an event arrives exactly once.

Events are ordered by `detected_at`, oldest first. `occurred_at` is when the engagement
happened — LinkedIn does not timestamp a reaction, so it is when we saw it, and it is not a
field to sort or deduplicate on.

## What an engager gives you

`job_title` and `company_name` come from LinkedIn as free text, alongside the reaction. They
are **not** resolved records: `company_name` carries no LinkedIn company ID, and neither field
is guaranteed present. `country_code` appears only when we actually observed it — it is never
filled in from the watch's own `countries` filter, so its absence means unknown rather than
"outside your filter".

To turn an engager into something richer, take `profile_url` to
[Enrich people](/api-reference/people/enrich-people).

## Controlling cost

You are billed **1 credit per person collected**, on the same line as enrichment and research
(`MaxCompanyEnrichments`). Managing watches and reading the feed are free, however often you
poll. See [Credits](/guides/credits#engagement-watches).

Your ceiling per sweep is `max_posts_per_sweep` × `max_engagers_per_post`, and nothing else.
Both default conservatively. Raise them once a watch is producing what you expect rather than
up front, and prefer several narrow watches over one broad one: a precise query at a low cap
returns better leads than a vague query at a high one.

People who match none of your personas are collected too, and cost the same. That is what makes
widening a persona later surface the people already waiting — narrow the caps rather than the
collection if you want to spend less.

A sweep re-reads a fixed recent window rather than everything since your last poll, so it
always overlaps with the previous run. That overlap costs you nothing — repeated people are
recognised and not re-collected — and it is what makes a missed or failed sweep harmless.

**If a watch goes quiet, ask the watch why.**
[List engagement watches](/api-reference/engagement-watches/list-engagement-watches) reports
what each one's last run did:

| `last_sweep_outcome` | Means                                                                                                                                  |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| absent               | It has never run yet. A watch created minutes ago has not swept.                                                                       |
| `completed`          | It ran. `last_sweep_engagers_collected` says what it found, and `0` means nobody engaged — the query, not the plumbing.                |
| `out_of_credits`     | Skipped before spending, because the account could not pay for a single person. Top up and the next sweep resumes.                     |
| `failed`             | It ran and something went wrong, usually upstream. Self-correcting: the next sweep re-reads an overlapping window, so nothing is lost. |

That distinction is the whole reason the field exists — an empty feed looks identical whether
nobody engaged, the account ran dry, or the watch is simply new.

To stop a watch without losing what it has collected, set `is_enabled: false`. A disabled
watch is not swept and costs nothing.
[Deleting](/api-reference/engagement-watches/delete-an-engagement-watch) it discards every
person it ever surfaced.
