---
title: "Create an alert"
method: POST
path: "/v1/projects/{projectId}/alerts"
tags: ["Alerts", "tier:standard"]
---

# Create an alert

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

## Request

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

Create an alert that watches a single entity and emails the configured address whenever the entity is mentioned on a podcast episode. To cover several entities, create one alert per entity. Alerts are not covered by zero data retention: the alert's definition and its matched results are stored as part of the alerts feature.

## Path parameters

- `projectId` string, required: Project ID.

## Headers

- `Idempotency-Key` string

## Request body

- CreateAlertBody
  - `delivery_cadence` 'REALTIME' | 'DAILY' | 'WEEKLY': How often matches are delivered: REALTIME (a separate email per match), DAILY (one bundled email each morning at 13:00 UTC), or WEEKLY (one bundled email Monday 13:00 UTC). Defaults to REALTIME.
  - `description` string
  - `entities` AlertEntity[], nullable: The entity to watch — exactly one for ENTITY_MENTION and PODCAST_SPEAKER alerts; an alert watches a single entity, so create a separate alert per entity. Omit for KEYWORD_MENTION alerts. v1 matches across podcast episodes only.
    - `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.
  - `is_active` boolean: Whether the alert produces matches. Defaults to true. Disabling stops new matches without losing the alert — re-enable to resume.
  - `keyword` string: KEYWORD_MENTION alerts only, and required for them: the phrase to watch, e.g. Lightfield. It matches the way a double-quoted keyword search phrase does — the words adjacent and in order, ignoring case and punctuation, on whole words — within topic-discussion and interview segments, never in ad reads, intros, or outros. Set description to say what the phrase means (e.g. 'Lightfield, the AI-native CRM') — it is how same-name mentions of something else get filtered out. A phrase that matched more than 700 podcast episodes in the past week is rejected as too broad.
  - `kind` 'ENTITY_MENTION' | 'PODCAST_SPEAKER' | 'KEYWORD_MENTION': What signal the alert watches for. Defaults to ENTITY_MENTION (fires on any mention of a watched entity). PODCAST_SPEAKER fires only when a watched entity is themself an identified speaker on the episode, in one of the alert's speaker roles: by default GUEST, PANELIST, CORRESPONDENT, AUDIENCE, and SOUNDBITE_SPEAKER (an excerpted clip of them played on the episode); raw STT/LLM labels like CALLER, LISTENER, REPORTER are normalized into these buckets on write. HOST is excluded by default because hosting the show isn't an appearance. speaker_roles replaces the default set. KEYWORD_MENTION fires whenever the keyword is spoken in a podcast episode — use it for brands, products, or terms the entity resolvers don't know; it takes keyword instead of entities.
  - `notifications` AlertNotificationChannel[], nullable, required: Delivery channels for match notifications. Mix of EMAIL and SLACK destinations is allowed. SLACK destinations require a previously-installed Slack workspace connection (see POST /v1/projects/{projectId}/slack/connections/initiate) and a channel id from the connection's channel listing.
    - `created_by` Creator
      - `id` string, required: Creator's user ID
      - `name` string, required: Creator's display name
    - `email` string, email: Required when type=EMAIL.
    - `id` string
    - `slack_channel_id` string: Required when type=SLACK. Slack vendor channel id (e.g. C01234XYZ for public channels, G... for private channels). Must be a channel returned by GET /v1/projects/{projectId}/slack/connections/{id}/channels.
    - `slack_channel_name` string: Optional. Display label for the destination channel. The server overwrites this from the cached slack_channels row on save, so clients do not need to supply it.
    - `slack_connection_id` string: Required when type=SLACK. The Slack workspace connection that owns the channel. Any active connection belonging to the alert's project is accepted, whichever surface installed it, so an alert created in one app can be managed from the other.
    - `type` 'EMAIL' | 'SLACK' | 'WEBHOOK', required: Delivery channel type. EMAIL writes to email; SLACK posts a message to a channel in a connected Slack workspace; WEBHOOK POSTs a signed JSON payload to a configured webhook connection.
    - `webhook_connection_id` string: Required when type=WEBHOOK. The webhook connection that receives the signed payload.
  - `title` string, required: Human-readable title

## Response `200`

OK

- Alert
  - `created_at` string, date-time, required
  - `created_by` Creator
    - `id` string, required: Creator's user ID
    - `name` string, required: Creator's display name
  - `delivery_cadence` string, required: How often matches are delivered: REALTIME (a separate email per match), DAILY (one bundled email each morning at 13:00 UTC), or WEEKLY (one bundled email Monday 13:00 UTC). Defaults to REALTIME.
  - `description` string
  - `entities` AlertEntity[], nullable, required
    - `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.
  - `id` string, required
  - `is_active` boolean, required: Whether the alert produces matches. Disabled alerts retain history but accumulate no new matches until re-enabled.
  - `keyword` string: KEYWORD_MENTION alerts only: the phrase the alert watches (see the create request's keyword for how it matches). Absent on entity alerts.
  - `kind` 'ENTITY_MENTION' | 'PODCAST_SPEAKER' | 'KEYWORD_MENTION', required: What signal the alert watches for. ENTITY_MENTION fires whenever a watched entity is mentioned in a podcast episode. PODCAST_SPEAKER fires only when a watched entity is themself an identified speaker on the episode, in one of the alert's speaker roles. By default those are GUEST, PANELIST, CORRESPONDENT, AUDIENCE (which absorbs callers, listeners, and other audience-side roles after server-side canonicalization), and SOUNDBITE_SPEAKER (an excerpted clip of them played on the episode); HOST is excluded by default because hosting the show isn't an appearance. speaker_roles replaces the default set. KEYWORD_MENTION fires whenever the alert's keyword is spoken in a podcast episode, for names the entity resolvers don't know; it watches a keyword instead of entities. Kind is fixed at creation.
  - `notifications` AlertNotificationChannel[], nullable, required
    - `created_by` Creator
      - `id` string, required: Creator's user ID
      - `name` string, required: Creator's display name
    - `email` string, email: Required when type=EMAIL.
    - `id` string
    - `slack_channel_id` string: Required when type=SLACK. Slack vendor channel id (e.g. C01234XYZ for public channels, G... for private channels). Must be a channel returned by GET /v1/projects/{projectId}/slack/connections/{id}/channels.
    - `slack_channel_name` string: Optional. Display label for the destination channel. The server overwrites this from the cached slack_channels row on save, so clients do not need to supply it.
    - `slack_connection_id` string: Required when type=SLACK. The Slack workspace connection that owns the channel. Any active connection belonging to the alert's project is accepted, whichever surface installed it, so an alert created in one app can be managed from the other.
    - `type` 'EMAIL' | 'SLACK' | 'WEBHOOK', required: Delivery channel type. EMAIL writes to email; SLACK posts a message to a channel in a connected Slack workspace; WEBHOOK POSTs a signed JSON payload to a configured webhook connection.
    - `webhook_connection_id` string: Required when type=WEBHOOK. The webhook connection that receives the signed payload.
  - `project_id` string, required
  - `title` string, required
  - `updated_at` string, date-time, 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)
