---
title: "Preview match frequency for an alert before creating it"
method: POST
path: "/v1/projects/{projectId}/alerts/preview"
tags: ["Alerts", "tier:standard"]
---

# Preview match frequency for an alert before creating it

`POST /v1/projects/{projectId}/alerts/preview`

## Request

- Base URL: `https://api.particle.pro`
- URL: `https://api.particle.pro/v1/projects/{projectId}/alerts/preview`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Kicks off an asynchronous sweep over the past N days (default 7, max 30) and returns a preview_id. Poll GET /v1/alerts/preview/{previewId} until status is completed (or failed). The result includes the total match count, a per-day breakdown, and a small sample of the most-recent matches with full payload so the UI can show "if you create this alert, here's what it would have caught."

## Path parameters

- `projectId` string, required: Project ID.

## Query parameters

- `window_days` integer: Number of days back to sweep. Defaults to 7; capped at 30.

## Request body

- PreviewAlertBody
  - `entities` AlertEntity[], nullable: Same as CreateAlertBody.entities — exactly one for entity kinds, omitted for KEYWORD_MENTION.
    - `entity_id` string, required
    - `entity_type` 'PERSON' | 'COMPANY' | 'KNOWLEDGE_GRAPH_ENTITY', required
    - `image_url` string: Image URL for the watched entity (headshot for PERSON, logo for COMPANY, image for KGE). Omitted from the response when no image is available.
    - `name` string: Display name of the watched entity. Omitted from the response when the underlying record cannot be resolved (e.g. deleted).
  - `filters` AlertFilters
    - `languages` string[], nullable: Primary language tags the source episode must be in. Accepts a 2-3 letter primary tag (e.g. 'en', 'de', 'sma') and tolerates an optional 2-4 char region or script subtag for readability (e.g. 'pt-BR', 'zh-Hant'); matching is on the PRIMARY tag only, so 'pt-BR' surfaces every Portuguese episode regardless of region and 'pt-BR' + 'pt-PT' collapse to one. Empty or missing means all languages are surfaced. Codes are compared case-insensitively; the API echoes back the canonical primary-tag form.
    - `relevance` 'EVERYTHING' | 'RELEVANT': EVERYTHING (the default) returns both on-target and incidental matches — the watched entity is correctly identified in both, only the depth of discussion differs. RELEVANT narrows to on-target only: matches where the watched entity is the subject being discussed, dropping passing mentions. Wrong-entity matches (knowledge-graph name collisions, e.g. the podcast 'Truckie' for an alert on the town 'Truckee') are globally suppressed before any filter runs.
    - `source_popularity` 'ANY' | 'POPULAR': ANY (the default) keeps matches from every source. POPULAR keeps only matches whose source podcast scores in the top 5% by chart-popularity percentile (Podcast.Popularity >= 0.95; cume_dist over current chart entries, multi-region weighted). Podcasts that aren't currently charting drop out.
    - `speaker_roles` string[], nullable: PODCAST_SPEAKER alerts only. REPLACES (not intersects with) the default appearance set the alert matches against. Default when omitted is GUEST, PANELIST, CORRESPONDENT, AUDIENCE, SOUNDBITE_SPEAKER — HOST is intentionally excluded because hosting the show isn't an appearance. Setting ['HOST'] flips that — only HOST appearances match. Setting ['GUEST'] alone narrows further. Sending this on an ENTITY_MENTION alert returns 422 — the wire layer can't tell at validation time, so the handler boundary rejects it once the parent kind is known. Input casing is not significant — 'guest' and 'GUEST' are the same value — and the filter is persisted and echoed back in canonical uppercase form. Raw STT/LLM labels like CALLER or REPORTER are not accepted because podcast_episode_speakers.role canonicalizes to the buckets above at write time.
  - `keyword` string: Same as CreateAlertBody.keyword — required for KEYWORD_MENTION, rejected otherwise. A phrase that matched more than 700 podcast episodes in the past week is rejected with 422, as on create.
  - `kind` 'ENTITY_MENTION' | 'PODCAST_SPEAKER' | 'KEYWORD_MENTION': Same as CreateAlertBody.kind. Defaults to ENTITY_MENTION.

## Response `200`

OK

- PreviewAlertResponseBody
  - `preview_id` string, required: Deterministic preview ID — repeating the same body within the TTL returns the same ID. Poll GET /v1/alerts/preview/{previewId} when status is 'in_progress'.
  - `status` 'in_progress' | 'completed', required: 'in_progress' means a sweep is running (or was just started); 'completed' means an earlier identical preview is still cached and the result is ready to fetch via GET. A 'failed' prior preview is treated as a cache miss and a fresh sweep is started — callers never see 'failed' on this endpoint.
  - `window_days` integer, required

## Other responses

- `default`: Error

---

[API](https://skmtc.dev/particle/apis/particle-api.md) · [All operations](https://skmtc.dev/particle/apis/particle-api/llms.txt) · [OpenAPI document](https://skmtc.dev/particle/apis/particle-api/revisions/416ba82ab9d7?raw)
