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

# ICP signals

> Get notified when something happens at any company matching your ICP — not just the accounts you already track.

Most signal endpoints answer "what happened at *this* company", which means you have to know
the company first. ICP signals invert that: you describe the market you sell into once, and
TAMTAM tells you which companies in it just did something worth a call — including companies
you have never heard of and have not added to your Key Accounts.

## How it fits together

<Steps>
  <Step title="Define an ICP">
    An ICP is your market, expressed as criteria: HQ countries, headcount band, industries,
    plus optional research questions such as "does this company use Microsoft 365?".
    Create one with [Create an ICP criteria](/api-reference/icp-criteria/create-an-icp-criteria).
  </Step>

  <Step title="Watch it">
    [Create or update an ICP signal watch](/api-reference/icp-signal-watches/create-or-update-an-icp-signal-watch)
    tells us which signal types to detect for that ICP, and optionally which news topics to
    restrict to. Safe to call repeatedly — it creates the watch the first time and reconfigures
    it afterwards.
  </Step>

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

## Signal types

| `signal_type` | Fires when                                                                      | Driven by                                       |
| ------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- |
| `news`        | Something reported happens to a company in your ICP (see the topic table below) | Business news across each of your ICP's markets |
| `job_posting` | A company in your ICP is hiring for roles matching your job-posting criteria    | Your ICP's job-offers criterion                 |

### News topics

`news` covers a fixed, curated set of event types. Each event carries its topic in
`payload.topic`, so you can route on it without parsing the headline. The same values are what
you pass in `news_topics` when creating a watch:

| Topic            | Event                                          |
| ---------------- | ---------------------------------------------- |
| `m_and_a`        | The company is acquired, or merges             |
| `rebranding`     | The company changes its name or brand identity |
| `funding_round`  | The company raises investment                  |
| `product_launch` | The company launches a product or offer        |
| `relocation`     | The company moves, or opens a new office       |

By default a watch searches **all** of them. Send `news_topics` to narrow it:

```bash theme={null}
curl -X PUT -H "Authorization: $TAMTAM_API_KEY" \
  "https://public-api.tamtam.ai/api/v2/icp-signal-watches/$ICP_CRITERIA_ID" \
  -d '{"signal_types":["news"],"news_topics":["m_and_a","funding_round"]}'
```

Omit `news_topics`, or send `[]`, to go back to every topic. An unrecognised topic is rejected
with a 422 listing the supported set, rather than quietly ignored — a typo in a topic name and a
market with no news look identical once a run is over.

<Tip>
  Narrowing is worth doing even if you would happily read every topic. A run spends a fixed
  article budget across (markets × topics), so a watch on one topic reads that topic about five
  times as deeply as a watch on all five — same cost, more of the thing you actually want.
</Tip>

The list is curated rather than free-text on purpose. Each topic pairs a per-language search
query with instructions telling the extractor which company an article of that kind is *about* —
for a funding round the subject is the company raising, never the investor; for a launch it is
the company behind the product, not the product. A free-text topic would search fine and then
misattribute or miss the company, which is the failure that is hard to notice from the outside.

<Note>
  Searches run in the language of each of your ICP's markets, currently English, French, Dutch
  and German. A market whose language is not covered is still searched, in English, which finds
  the subset its local press published in English. Tell us which markets matter to you and we
  will add them.
</Note>

If the event you care about is not in that table, it will not appear — no topic is inferred from
your ICP. Ask and we will look at adding it: a topic is a query plus an extraction rule per
language, not a new pipeline.

`job_posting` needs your ICP to carry a **job-offers criterion** — the titles, description
keywords or technologies that make a role interesting to you. Without one there is nothing to
look for: nearly every company is hiring, so we would return your entire ICP every day. If you
subscribe to `job_posting` and see no events, check that criterion first.

## Polling

The feed is a stream you walk forward, not a list you re-read.

<Note>
  Prefer not to poll? [Webhooks](/guides/webhooks) push these same events to your own URL within
  minutes. Build the poller anyway: the cursor is how you catch up on anything a push missed, and
  it stays the source of truth.
</Note>

```bash theme={null}
# First call: no cursor.
curl -H "Authorization: $TAMTAM_API_KEY" \
  "https://public-api.tamtam.ai/api/v2/icp-signals?limit=100"
```

```json theme={null}
{
  "events": [
    {
      "id": "9b7d5f2e-1c3a-4f8b-9e6d-2a1b3c4d5e6f",
      "icp_criteria_id": "4f1c2a90-6b3e-4d8a-9c7f-1e2d3b4a5c6d",
      "signal_type": "news",
      "company_linkedin_id": "104924588",
      "headline": "Target Co was acquired by Acquirer SA",
      "occurred_at": "2026-08-10T00:00:00Z",
      "detected_at": "2026-08-11T09:14:22Z",
      "payload": {
        "topic": "m_and_a",
        "article_url": "https://example.com/target-co-acquired",
        "country_code": "FR"
      }
    }
  ],
  "next_cursor": "v1:eyJ0IjoiMjAyNi0wOC0xMVQwOToxNDoyMloiLCJpIjoiOWI3ZDVmMmUtMWMzYS00ZjhiLTllNmQtMmExYjNjNGQ1ZTZmIn0",
  "has_more": false
}
```

Store `next_cursor` and pass it back on the next call. It comes back on every non-empty page,
including the last, because the feed has no end — only a current position. `has_more: false`
means you are caught up, not finished.

<Warning>
  **Delivery is at-least-once.** Under retries or overlapping detection runs you can be handed
  an event you have already seen. Every event has a stable `id`; record the ones you have
  processed and skip repeats. Do not treat arrival as proof of first arrival.
</Warning>

Hourly is a sensible cadence. Polling every minute will mostly return empty pages: news takes
hours to days to surface, and detection runs on a daily sweep.

## Two things about timing

`occurred_at` is when the event happened in the world; `detected_at` is when we found it. They
can differ by days, and the feed is ordered by `detected_at`. So as you page forward,
`occurred_at` can move *backwards* between consecutive events — an article published last
Tuesday can be detected after one published yesterday. Order on `detected_at` if you need a
stable sequence; use `occurred_at` to tell the seller how fresh the news is.

News `occurred_at` carries **day precision** only: it is the publication date, so it arrives as
midnight UTC.

## What an event means

An event means the company cleared your **whole** ICP at the time we detected it, research
questions included. `icp_fit` records which criteria matched, snapshotted then — so it still
explains the alert months later, after you have edited the ICP.

Two consequences of checking properly rather than guessing:

* A company we have never researched is held back until its research completes, so its first
  signal can arrive a cycle after the article did. We would rather be a day late than tell you
  a company matches when it does not.
* An ICP whose criteria we cannot evaluate for a given company will not produce events for it.
  If a watch is quieter than you expect, `icp_fit` on the events you *did* get shows which
  criteria are doing the filtering.

## Working with an event

`company_linkedin_id` is the join key to the rest of the API:

* [Get company news](/api-reference/companies/get-company-news) and
  [job postings](/api-reference/companies/get-company-job-postings) for the fuller picture
* [Search people](/api-reference/people/search-people) to find who to contact
* [Add to Key Accounts](/api-reference/key-accounts/add-key-accounts) to start tracking it
  properly

`source_ref_id`, when present, is the id of the stored article behind a `news` event.

## Credits

Creating and reading watches is free, and so is polling the feed. Detection itself consumes
credits when it has to research a company against your ICP's research questions — the same
cost as evaluating that company's ICP fit anywhere else, charged once per company rather than
once per signal. See [Credits](/guides/credits).

## Shadow mode

A watch created with `is_enabled: false` keeps detecting and recording events without them
being delivered onward. Useful for seeing what an ICP would produce, and at what volume,
before wiring it into a workflow that emails your sales team.
