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

# Signals API: Detected changes in AI visibility

> Query Scrunch's nightly detection sweep for statistically-tested level changes and trends in AI visibility metrics, with stable signal identity.

## Overview

The Signals API exposes Scrunch's nightly detection sweep as a queryable feed. Each **signal** is a statistically-tested movement — a level change or trend — in one of your brand's AI visibility metrics on a specific slice of your data (a platform, topic, or the account as a whole).

Use it to feed alerts into your own tools, drive weekly briefs, or automate follow-up on the same "what changed" events surfaced in the Scrunch dashboard.

***

## What the Signals API includes

* Detected level changes and trends on core metrics (`presence_rate`, `position_top_rate`, `cited_domain_rate`)
* A confidence tier for each signal (`high`, `confident`, `worth_a_look`, `provisional`), with noise-floor tiers available on request
* Human-readable narrative: what happened, why it matters, and what to do
* A **stable fingerprint** for each underlying issue — the same signal keeps its fingerprint across nightly re-detections, so reactions and downstream state survive
* Reactions (`useful`, `not_useful`, `dismissed`, `actioned`) with optional free-text reasons, listable brand-wide for team-level reporting
* Anchor dates (distinct detection days) for building date pickers and polling loops

Signals are dedup-to-latest per identity: over a multi-day range you see one row per underlying issue, not one row per re-detection.

***

## When to use the Signals API

Use the Signals API when you need to:

* Post "what changed this week" summaries to Slack, email, or a stakeholder digest
* Route detected drops on a specific platform, topic, or metric into an incident tracker
* Export the team's reactions on signals for reporting or feedback loops
* Build a custom alerting dashboard that mirrors the Scrunch Signals feed

For ad-hoc "how is metric X trending" questions, use the [Query API](/api-reference/query/overview) — Signals only surfaces movements that pass detection thresholds.

***

## Endpoints

| Method | Path                                       | Purpose                                             |
| ------ | ------------------------------------------ | --------------------------------------------------- |
| GET    | `/{brand_id}/signals`                      | List detected signals with filters.                 |
| GET    | `/{brand_id}/signals/{signal_id}`          | Get one signal with its full narrative.             |
| GET    | `/{brand_id}/signals/anchors`              | List recent detection dates that produced signals.  |
| GET    | `/{brand_id}/signals/reactions`            | List reactions across the brand's signals.          |
| PUT    | `/{brand_id}/signals/{signal_id}/reaction` | Set (or replace) the caller's reaction on a signal. |
| DELETE | `/{brand_id}/signals/{signal_id}/reaction` | Clear the caller's reaction.                        |

All endpoints require a bearer token with the `query` scope. Reaction writes additionally require a **user token (JWT)** — API keys have no user identity to attribute a reaction to and receive `403`. See [Authentication](/getting-started/authentication).

***

## Filters

The list endpoint accepts:

| Filter                      | Description                                                                                                                                                                                                                                      |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `scope`                     | Slice granularity: `account`, `account_platform`, `topic`, or `topic_platform`.                                                                                                                                                                  |
| `platform`                  | AI platform (e.g. `OpenAI`). Multi-platform signals carry the `(multi)` sentinel; the response `slice.platforms` lists the real platforms.                                                                                                       |
| `metric`                    | Metric the signal fired on (`presence_rate`, `position_top_rate`, `cited_domain_rate`).                                                                                                                                                          |
| `alert_type`                | `level_change` (step shift) or `trend` (sustained drift).                                                                                                                                                                                        |
| `direction`                 | `up`, `down`, or `none`.                                                                                                                                                                                                                         |
| `subject_kind`              | `brand` or `competitor`.                                                                                                                                                                                                                         |
| `tier`                      | Restrict to a single confidence tier. Omitting it returns the default user-facing tiers (`high`, `confident`, `worth_a_look`, `provisional`); pass an explicit tier to include noise-floor tiers (`low_confidence`, `underpowered`, `untested`). |
| `anchor_from` / `anchor_to` | Inclusive `detected_for_date` bounds (`YYYY-MM-DD`).                                                                                                                                                                                             |
| `sort`                      | `score_desc` (default — engine priority), `delta_desc` (largest absolute change), or `detected_desc` (newest detection).                                                                                                                         |
| `limit` / `offset`          | Page size (default `50`, max `200`) and offset.                                                                                                                                                                                                  |

The reactions list endpoint supports `fingerprint` (all reactions on one signal identity) and `reaction` (all reactions of a given value).

***

## Example: list recent high-confidence signals

```bash theme={null}
curl -X GET \
  "https://api.scrunchai.com/v1/1234/signals?tier=high&sort=detected_desc&limit=20" \
  -H "Authorization: Bearer $SCRUNCH_API_TOKEN"
```

**Response:**

```json theme={null}
{
  "total": 4,
  "offset": 0,
  "limit": 20,
  "items": [
    {
      "id": 90211,
      "detected_for_date": "2026-07-14",
      "fingerprint": "b1e0c7a4f9e5...",
      "subject_kind": "brand",
      "alert_type": "level_change",
      "scope": "account_platform",
      "metric": "presence_rate",
      "platform": "OpenAI",
      "direction": "down",
      "tier": "high",
      "current_value": 0.31,
      "baseline_value": 0.47,
      "delta_absolute": -0.16,
      "score": 0.82,
      "narrative_what": "Brand presence on OpenAI dropped 16 pts week-over-week.",
      "narrative": {
        "what_happened": "Presence on OpenAI fell from 47% to 31% over the last 7 days.",
        "why_it_matters": "OpenAI drives the largest share of your tracked AI traffic.",
        "what_to_do": "Investigate top prompts where the brand disappeared and check citation coverage."
      },
      "slice": {
        "platforms": ["OpenAI"],
        "topic_labels": [],
        "geo_country": null
      },
      "window_current_start": "2026-07-08",
      "window_current_end": "2026-07-14",
      "baseline_definition": "Previous 28 days",
      "created_at": "2026-07-15T02:14:00Z"
    }
  ]
}
```

***

## Example: get one signal's full narrative

```bash theme={null}
curl -X GET \
  "https://api.scrunchai.com/v1/1234/signals/90211" \
  -H "Authorization: Bearer $SCRUNCH_API_TOKEN"
```

`getSignal` returns `404` for IDs that exist but aren't user-facing (non-fired detections or cluster-child rows) — the same population rule as the list endpoint.

***

## Example: react to a signal

Reactions are keyed on the signal's stable `fingerprint`, so a thumbs-up survives the nightly re-detection of the same underlying issue. This call requires a **user token (JWT)**.

```bash theme={null}
curl -X PUT \
  "https://api.scrunchai.com/v1/1234/signals/90211/reaction" \
  -H "Authorization: Bearer $SCRUNCH_USER_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "reaction": "actioned", "reason": "Filed ticket ENG-4421." }'
```

```json theme={null}
{
  "user_id": 55,
  "insight_id": 90211,
  "fingerprint": "b1e0c7a4f9e5...",
  "reaction": "actioned",
  "reason": "Filed ticket ENG-4421.",
  "surface": "api",
  "created_at": "2026-07-15T09:02:11Z",
  "updated_at": "2026-07-15T09:02:11Z"
}
```

Clear a reaction with `DELETE /v1/{brand_id}/signals/{signal_id}/reaction`.

***

## Example: list team reactions

The reactions list is **brand-wide** — it returns every user's reactions, not just the caller's — so an integration can export the team's aggregate feedback.

```bash theme={null}
curl -X GET \
  "https://api.scrunchai.com/v1/1234/signals/reactions?reaction=useful&limit=100" \
  -H "Authorization: Bearer $SCRUNCH_API_TOKEN"
```

Filter by `fingerprint` to pull every reaction on one signal identity across re-detections.

***

## Example: poll for new detection dates

```bash theme={null}
curl -X GET \
  "https://api.scrunchai.com/v1/1234/signals/anchors?limit=14" \
  -H "Authorization: Bearer $SCRUNCH_API_TOKEN"
```

```json theme={null}
{
  "dates": ["2026-07-14", "2026-07-13", "2026-07-12"]
}
```

Every returned date has at least one user-facing signal. Use it to drive a date picker or as a cheap check before pulling the full list.

***

## Signal identity and dedupe

Every signal carries a `fingerprint` — a hash over its slice and metric — that stays stable across the nightly re-detection of the same underlying issue. Two implications:

* **List responses dedupe to the latest detection.** Over a multi-day `anchor_from`/`anchor_to` window you see one row per fingerprint, not one per day.
* **Reactions follow the fingerprint, not the row ID.** A `useful` set today still applies to tomorrow's re-detection of the same signal.

Persist the `fingerprint` (not the numeric `id`) if you need to correlate signals across runs in your own system.

***

## Best practices

* Poll `/signals/anchors` first to check whether a new detection day exists before pulling the full list.
* Sort by `detected_desc` for "what's new" digests, `score_desc` for "what matters most", and `delta_desc` when ranking by raw movement.
* Store the `fingerprint` alongside your internal record so reactions and follow-up state survive re-detections.
* Use the reactions list to close the loop on "which alerts drove action" — filter by `reaction=actioned` for a running list of resolved signals.
* For automated triage from an AI assistant, the same feed is available through the [Scrunch MCP server](/mcp/overview) via the `list_signals` and `get_signal` tools.
