All documentation

Advisors

Alerts

A daily watch on your visibility that raises an alert when something moves enough to be worth reading, shows why it fired, and lets the team acknowledge, dismiss, snooze or ask for an AI reading of the saved evidence.

Visibility alerts over the last 90 days: the most recent alert, a source watchlist citation loss, beside the alert and affected-prompt counts, above the dated list of source watchlist, emerging brand, competitor surge and rank slide alerts

What this is

Alerts is a daily comparison the watchdog runs for every active project, no model involved: adjacent seven-day windows, with a row written when one of four visibility changes crosses its threshold, one of four brand-signal changes does, or an opted-in source watchlist records a citation gain or loss (api/services/watchdog.py#detect_alerts_for_project, api/services/brand_signals.py#detect_brand_signals). The same run also raises a fact contradiction alert when an AI answer contradicts one of your approved facts in Fact check; that alert reads findings an AI model has already made (see Fact contradiction alerts below). Every kind runs in-app on every plan, except two brand-signal checks and fact contradictions, which need a specific plan feature (see Plan gates for brand-signal alerts and Fact contradiction alerts below); email delivery of any kind follows its own separate plan gate (see Delivery below). Owners and editors can acknowledge, dismiss or snooze any alert with a shared team note (see React to an alert below), and on Starter and above can ask for an AI reading of one alert's saved evidence (see Explain this alert below).

Visibility changes and thresholds

Four visibility kinds, each with its own floor and threshold (api/services/watchdog.py#build_aggregate_alerts, #build_prompt_alerts):

  • Visibility drop: your own visibility across the whole project fell at least 20% relative to the week before, counted only when that prior week's own visibility was itself at least 10% (#OWN_DROP_THRESHOLD, #OWN_DROP_BASELINE_MIN).
  • Competitor surge: evaluated only while your own visibility is under 20%. A tracked competitor whose own visibility reached at least 10% rose at least 30% relative to its own prior week (#OWN_VIS_WEAK_CEILING, #COMPETITOR_MIN_VISIBILITY, #COMPETITOR_SURGE_THRESHOLD). The comparison pools only the engines measured in both weeks (see below), so an engine added or dropped between them cannot cause a surge, and the 20-answer floor below applies to that pooled set in each week. A competitor at exactly 0% the week before on those engines has no relative change to measure, so instead of a percentage it raises a surge marked newly cited, under the same conditions otherwise: you under 20%, the competitor at 10% or more now, the same floors. "Cited" because a competitor's visibility here counts answers citing its domain, not answers naming it. Its change reads "Newly cited (0% the week before)" on the alert, never a percentage, and neither the email digest nor an AI explanation is given one (#NEWLY_NAMED). The engine a relative surge names as driving it is never one where the competitor was at 0% the week before. A newly cited surge, where every engine it compares was at 0%, names the engine citing the competitor most now instead. In the per-engine breakdown, an engine the competitor rose on from 0% reads "Newly cited" rather than a percentage (#build_aggregate_alerts, #_provider_breakdown_context, #_newly_named_driver). The alert's message gives the competitor's figures on the engines that ran in both weeks and your own visibility across all engines, and says so.
  • Prompt drop: one prompt's own visibility fell at least 25% relative to its prior week, counted only when that prompt's prior-week visibility was itself at least 15% (#PROMPT_DROP_THRESHOLD, #PROMPT_DROP_BASELINE_MIN).
  • Rank slide: a prompt still cites you about as often as before (it did not also cross the drop threshold above), but your average citation rank on it was in the top 5 the week before and slid by at least 2 full positions (#RANK_SLIDE_TOP_N, #RANK_SLIDE_MIN_POSITIONS).

All four visibility kinds compare only the engines measured in both weeks, minus any engine in outage (see below). An engine counts as measured in a week only when it ran (an answer, or a Google run that showed no AI answer) on at least 80% of the week's answered days, the days on which any engine has an answer (6 of 7 when every day was answered) (api/services/collection_health.py#WINDOW_PRESENCE_MIN_PERCENT): an engine that ran in one week only, such as a Google engine in its first week, or on a few days of one, such as ChatGPT (app) in the week after it was added (one launch day in the week before) or the week it was switched off, sits on neither side of any comparison, and a newly added engine such as Gemini (app) sits out the same way until it has run on enough days of both weeks, so adding or removing an engine cannot by itself raise or hide an alert. Every figure in the alert message comes from those engines (api/services/collection_health.py#engine_windows, api/services/watchdog.py#detect_alerts_for_project).

Volume floors for visibility alerts

Before the thresholds above are evaluated at all, the project needs at least 5 days of measured history in the prior week, and at least 20 completed answers, project-wide, in both the prior week and the current one; short of either floor the whole project is skipped for that day, whatever the raw percentages would say (#BASELINE_MIN_DAYS, #MIN_WINDOW_EXECUTIONS, #detect_alerts_for_project). A prompt-level check adds a second, its own: that one prompt itself needs at least 20 answers in both windows, not just the project as a whole (#_has_window_volume, #build_prompt_alerts).

Adding a persona audience or a new country to a tracked prompt changes the populations these checks read, not just what the prompt covers going forward: once its new (country, audience) target starts running, its answers fold into the same project-wide and per-prompt counts visibility drop and prompt drop compare window over window, and into the same family counts brand share shift reads. An alert (or the lack of one) shortly after such a change can reflect the wider mix of what is now being measured rather than a genuine swing in how you are doing (api/services/watchdog.py#_load_domain_metrics, api/services/watchdog.py#_load_prompt_metrics, api/services/brand_signals.py#build_population).

Brand-signal changes and thresholds

Four more kinds compare the same shape of adjacent seven-day windows as above, but ending one day earlier (yesterday, not today), and read brand-mention, sentiment and social-citation data instead of citation and rank data (api/services/brand_signals.py#build_population). Like competitor surge, prompt drop and rank slide above, each of these four kinds can raise more than one alert in a single run, one per subject that crosses its own line: brand share shift raises at most one for your own family rising, one for your own family falling, plus one per qualifying competitor rising; emerging brand raises one per qualifying untracked name; sentiment shift raises at most one for your own negative share rising, one for it falling, one per qualifying reason of yours, and up to three more shared between a qualifying competitor's negative share moving and a qualifying competitor's weakness reason rising; and competitor-only social citation raises one per qualifying competitor. Subject keys never collide across directions, so a snoozed drop for your brand can never suppress a later rise for the same brand, and vice versa. Each kind's own cap on how many alerts fire per run is described below.

  • Brand share shift covers three different measures, not one shared pool. Your own share is the fraction of all analysed answers that name your brand; it can fire as a drop, falling at least 20% relative to its baseline week, counted only when that baseline share was itself at least 10% (#OWN_DROP_BASELINE_MIN, #OWN_DROP_RELATIVE_THRESHOLD, #_detect_own_drop), or as the mirror-image rise, your own share reaching at least 10% in the current week and rising at least 20% relative to a nonzero baseline (a baseline of exactly zero answers is skipped, since a relative change against no baseline at all would be a fabricated figure) (#OWN_RISE_CURRENT_MIN, #OWN_RISE_RELATIVE_THRESHOLD, #_own_rise_alert); the two use different subject keys, so a snoozed drop cannot suppress a later rise. A competitor's share is its fraction of every (answer, family) pair among your tracked, active brand families, yours and every competitor's counted together; it reached at least 10% in the current week and rose at least 30% relative to its own baseline, one alert per qualifying competitor, uncapped (#COMPETITOR_RISE_CURRENT_MIN, #COMPETITOR_RISE_RELATIVE_THRESHOLD, #_detect_competitor_rises, #detect_brand_share_shift). A competitor with zero tracked pairs at all in its baseline week, or whose root brand entity was created on or after that baseline week began, is skipped rather than compared against a fabricated near-zero baseline; a genuinely new competitor like that is what emerging brand below covers instead (#_detect_competitor_rises). Pausing a competitor during the baseline week does not trigger that skip: a brand entity's creation date never moves when it is paused or resumed, so a competitor created well before the baseline week is still compared even if it was paused for part of it. What a pause does change is which of its mentions can be tracked at all: the roster the model matches brand names against excludes a paused competitor entirely (api/services/brand_mentions.py#build_entity_roster, api/services/brand_families.py#active_entity_condition), so mentions recorded while it was paused keep no entity match and count as untracked evidence, permanently, not corrected once it is resumed. A rise measured across a pause can therefore partly reflect the pause ending rather than a real change in how often the brand is named. Adding an alias for that exact name, or creating it as a sub-brand, reattributes its past untracked mentions to the entity from then on (api/services/brand_families.py#reattribute_untracked).
  • Emerging brand: a name nobody tracks, whose most frequently recorded kind is company, was named in at least 3 answers across at least 2 distinct prompts in the current window, with none at all in the baseline window (#EMERGING_MIN_CURRENT_ANSWERS, #EMERGING_MIN_CURRENT_PROMPTS, #EMERGING_MAX_BASELINE_ANSWERS, #detect_emerging_brands). At most 5 alerts fire per run even when more names qualify: the ones named in the most current-window answers take the slots, each with up to 3 example quotes (#EMERGING_MAX_ALERTS, #EMERGING_MAX_EXAMPLES).
  • Sentiment shift covers five checks in total, three about your own brand and two more about a competitor's, all reading classified or reason-analysed mentions the same way. The share of your classified brand mentions carrying negative sentiment can fire as a rise, at least 10 percentage points above the baseline week (#NEGATIVE_SHARE_RISE_MIN_POINTS, #_detect_negative_share), or as the mirror-image fall, at least 10 points below it (#NEGATIVE_SHARE_FALL_MIN_POINTS, #_detect_negative_share_fall); each window still needs at least 30 classified mentions of your brand to count at all, and the two directions use different subject keys, so a snoozed rise cannot suppress a later fall. Your third own-brand check is a weakness reason: one of the Brand reasons labels cited as a weakness for your brand more often, in at least 5 current-window answers, at a rate at least 10 percentage points above its baseline rate, up to 3 reasons per run, the ones with the largest rise (#WEAKNESS_MIN_CURRENT_COUNT, #WEAKNESS_RISE_MIN_POINTS, #WEAKNESS_MAX_ALERTS, #_detect_weakness_reasons). The remaining two checks run the same two measures against every active competitor instead of your own brand: a competitor's classified-negative-mention share moving at least 10 percentage points in either direction (#COMPETITOR_NEGATIVE_MOVE_MIN_POINTS, #_detect_competitor_negative_moves), or one of its own weakness rates rising the same way your own does, at least 5 current-window answers, a 10-point rise, and at least 30 reason-analysed answers for that competitor in both windows, the same floor your own weakness check needs (#COMPETITOR_WEAKNESS_RISE_MIN_POINTS, #_detect_competitor_weakness_rises). Those last two share one combined cap of 3 alerts per run across every competitor and every reason together, the largest moves first, not 3 each (#COMPETITOR_SENTIMENT_MAX_ALERTS). The competitor weakness-rate check needs the same Brand reasons entitlement as your own; the competitor negative-share move runs on every plan, like your own negative-share rise and fall.
  • Competitor-only social citation: a competitor-labelled social account with zero citations at all in the baseline window was cited in at least 2 current-window answers, on prompts where none of your own accounts were cited, using the same account identity Social sources shows (#SOCIAL_MIN_CURRENT_ANSWERS, #detect_social_competitor_only). At most five alerts fire per run even when more competitors qualify, each listing its own qualifying accounts, five at most, the ones cited in the most answers; the message always states the true number of accounts that qualified, even when the list itself is shorter (#SOCIAL_MAX_ALERTS, #SOCIAL_MAX_ACCOUNTS_PER_ALERT).

Every one of these checks is the same kind of comparison as the four above: crossing a threshold means the numbers moved between two seven-day windows, not why. An emerging brand's name is whatever the model itself extracted, not a name checked against any registry, so a new spelling or short form of a brand you already track can appear as new until you add it as an alias or a competitor. A weakness-reason alert only ever counts answers analysed for reasons since Brand reasons shipped; older analysed answers are excluded from its count, not treated as reason-free.

Provider set and floor for brand-signal alerts

All four kinds share one provider set and one pair of windows, worked out once per project per run before any of the checks above (#build_population). The current window is the 7 days ending yesterday and the baseline window is the 7 days immediately before it, one day earlier throughout than the domain-visibility windows above, which run through today.

The provider set is every engine measured in both windows by the same rule (it ran on at least 80% of each window's answered days, 6 of 7 when every day was answered) (api/services/collection_health.py#WINDOW_PRESENCE_MIN_PERCENT), minus any engine whose failure rate over the current window alone exceeds 50% (api/services/collection_health.py#providers_in_outage, api/services/watchdog.py#OUTAGE_FAILURE_RATIO). An engine that started or stopped answering within the baseline or current window, as an app engine such as ChatGPT (app) or Gemini (app) does when it is added, is therefore left out until it has run on most days of both, so a brand only that engine names cannot read as emerging in the week after its launch. The failure rate is failed runs out of completed runs, runs where Google showed no AI answer and failed runs: a run with no AI answer is a request that succeeded, so a Google engine that mostly shows no AI answer is not taken for one in outage (api/services/collection_health.py#engine_execution_counts). The visibility checks above leave out an engine in outage by the same rule (api/services/watchdog.py#_excluded_provider_ids). Runs with no AI answer never enter any count or share here, which read completed answers only. Every count and share above is restricted to that set. Within it, each window separately needs at least 30 analysed answers, mention extraction finished, and at least 5 distinct execution dates; short of either, on either window, none of the four checks run for that project that day (api/services/brand_metrics.py#MIN_OBSERVATIONS, api/services/brand_signals.py#MIN_EXECUTION_DATES). Your own share's denominator is exactly this same analysed-answer count, so it is already covered by that floor; a competitor's rise, the negative-sentiment share and a weakness reason's rate each use a denominator of their own, tracked-brand mentions, classified mentions, and reason-analysed answers respectively, and recheck the same 30-observation floor against it (#_detect_competitor_rises, #_detect_negative_share, #_detect_weakness_reasons).

Competitor-only social citation is skipped entirely for a run with more distinct social URLs in range than it can count reliably, rather than risk an undercount (api/services/social_sources/service.py#_social_url_pool).

Scheduled watchdog runs

The configured systemd timer schedules 04:00 UTC runs of api.cli run-watchdog, which enqueues one watchdog job for every project that is active and whose owner is active, with no opt-in setting and no plan check at this step (discoveredby-cli@run-watchdog.timer, api/cli.py#cmd_run_watchdog, api/tasks.py#_enqueue_watchdog_for_all_projects). Each job runs as its own background task and calls the same detection and persistence logic described above and below (#run_visibility_watchdog_task, api/services/watchdog.py#run_watchdog_for_project). The only other path that reaches it is an admin running it manually for one project or every project from the internal admin panel, not something a regular user can do (api/routers/admin.py#admin_trigger_watchdog, #admin_trigger_watchdog_all).

Visibility repeats are suppressed

A change that would fire the same kind again for the same project, the same prompt (or none) and the same competitor (or none) within the last 7 days is dropped before it is ever written, so a drop that stays down does not post a fresh alert every single day (api/services/watchdog.py#_is_duplicate, #DEDUPE_WINDOW_DAYS). A row the team has snoozed counts as a match for as long as the snooze lasts, however old it is by then, so a nuisance repeat snoozed for 30 days does not reappear on day 8 just because the plain 7-day window lapsed; once the snooze ends, the 7-day window is what is left.

Brand-signal repeats are suppressed

The same suppression above also covers these four kinds, keyed on subject instead of prompt and competitor domain: the same kind, for the same project and the same subject, a brand family rising or falling, an untracked name, negative sentiment rising or falling, a reason label for your brand or for a named competitor, or a competitor's social citations, is dropped if it already fired within the last 7 days, or is still snoozed (api/services/watchdog.py#_is_duplicate, #DEDUPE_WINDOW_DAYS). Two different competitors surging, or two different reasons rising, in the same run do not suppress each other, and a rise never suppresses a fall for the same brand, since the two directions carry different subject keys. The four kinds above this section never set a subject and keep deduping exactly as they did before this shipped.

Plan gates for brand-signal alerts

Brand share shift (both your own directions and every competitor rise), emerging brand, and the negative-sentiment checks of sentiment shift, your own rise and fall and a competitor's move, run for every project on every plan, in-app, the same as the four visibility kinds above. Two checks stay behind Brand reasons: the weakness-reason half of sentiment shift for your own brand, and the matching check for a competitor's weakness rate. Competitor-only social citation stays behind Social sources. All three are standard on Starter, Growth and Pro, and off on Free and Trial; on a plan without one, that check is simply skipped for the project rather than shown with nothing found (api/services/brand_signals.py#detect_brand_signals, api/services/entitlements.py#get_entitlements). Explaining an alert (see Explain this alert below) is gated the same way, on Starter and above, but that gate belongs to the request, not to detection: every alert is still detected and shown regardless of whether its owner can generate a reading of it. This is separate from the email-copy gate under Delivery below, which applies the same way across all nine alert categories.

Why it fired is stored, not recomputed

Everything behind "why it fired", the exact before and after figures, the engine-by-engine breakdown, whether competitors moved too, is built once, at the moment the alert is detected, and saved on the row itself (#run_watchdog_for_project). The detail page you open later reads that saved record; it does not re-run the comparison against today's data (api/routers/alerts.py#get_alert, #_to_read).

React to an alert

Owners and editors can set an alert's status to open, acknowledged or dismissed, and attach a short team note; viewers cannot (api/routers/alerts.py#update_alert_status). This is one shared reaction per alert, visible to the whole project team, not a personal overlay. Status and note are replaced every call, so a request that wants to keep the current note has to resend it, which is what the alert page's own Save button does: it resubmits the note and snooze fields without changing status, alongside separate Acknowledge, Dismiss and Reopen buttons that do change it (api/schemas/visibility_alert.py#AlertStatusUpdate). A note is at most 500 characters and refuses control and invisible formatting characters; newline and the zero-width joiner used inside composite emoji are the two exceptions, which is why a handful of flag emoji built from invisible tag characters, such as England, Scotland or Wales, are refused while an ordinary two-letter country flag is not (api/schemas/visibility_alert.py#STATUS_NOTE_MAX_LEN).

The snooze is handled separately from status and note: omit it and the alert's current snooze is left exactly as it is, send an explicit Clear snooze and it is removed, or choose 7, 14 or 30 days and it is set that many days from today, replacing whatever was there before. The page's snooze choice defaults to Keep current snooze (until the stored date) when one is active, or No snooze when none is, precisely so that saving a note does not have to also decide the snooze (api/schemas/visibility_alert.py#AlertStatusUpdate). Setting status to open while a snooze is still active is stored as acknowledged instead, since an alert cannot stay unactioned while also being silenced; dismissing is unaffected by an active snooze. Every status change is recorded in the project's activity log without the note text itself. The list defaults to showing active alerts, open plus acknowledged; dismissed alerts and the full history are each available through the same status filter (api/routers/alerts.py#list_project_alerts).

Explain this alert

On Starter and above, owners and editors can open an alert and choose to generate a plain-language reading of its own saved evidence: the same title, message, delta, windows, provider breakdown and quotes already on the page, sent to a model with instructions to describe what changed and never to guess why (api/services/alert_narration.py#explain_alert, api/services/alert_narration.py#build_evidence). Viewers cannot request one, but can read an existing narration on an alert like any other project member. The result is cached per alert: once one narration completes, reopening the same alert shows it again for every project member without a new model call, for as long as that narration stays among the project's newest 500 (across every alert in the project, not just this one); once older requests push it past that count it is pruned, and reopening the alert then generates a fresh one (api/services/alert_narration.py#explain_alert, api/services/alert_narration.py#latest_narration, api/services/alert_narration.py#MAX_ATTEMPTS). A request is limited to one per project per minute, and a lease keeps a second request from starting while one is still running (api/services/alert_narration.py#RATE_LIMIT_SECONDS, #LEASE_MINUTES). The saved reading is a summary of at most 600 characters, with up to 3 observations of up to 300 characters each; the model may only refer to a quote already on the alert by its id, never by copying its text, and any id it invents is dropped before the response is saved, so the quote text you see always comes from the alert's own saved record, never from the model (api/schemas/alert_narration.py#NarrationOutput, api/schemas/alert_narration.py#NarrationObservation). Treat the result the same way: it is an AI reading of the evidence already saved on the alert, not a verified fact, and it never establishes what caused a change, only what the saved numbers show. A legacy alert with no saved quotes, such as a plain visibility drop, still gets a narration; it simply has nothing to cite.

The boundary with notifications

Notifications is the account's inbox, and visibility digests and source watchlist notices are two of the eight kinds that can land in it, not the only ones. When the watchdog persists one or more fresh visibility alerts for a project, it writes exactly one notification per project member, summarizing every alert from that run together, and links each of those alert rows back to the notification created for the project's owner (api/services/watchdog.py#run_watchdog_for_project). A day with three alerts still produces one notification, not three. The reverse does not hold at all: most notifications are not about an alert; see Notifications for the other six.

Delivery

Every fresh alert appears on this screen. Visibility alerts contribute to their in-app digest; source watchlist notices follow the list’s subscription settings. Owner email delivery follows the project's email preferences and current alert-email entitlement. Daily or weekly digests contain selected saved visibility alert types and subscribed source-watchlist events from the last seven days since the previous successful digest, excluding anything already dismissed by then, with up to five most recent highlights and a total count. Delivery occurs on watchdog runs; no new alert is required to deliver a pending digest. Turning email off leaves alert detection and in-app notifications unchanged (api/services/email_preferences.py#deliver_digest). An email names each engine as the app does, as in "By engine: ChatGPT (app) +12%" or a prompt "driven by Gemini (API)", never by its internal slug (api/services/email_preferences.py#digest_entry). Competitor-only social citation is also gated at detection itself, in-app included, not only at email time, so its category can go entirely unused on a plan without Social sources. The reason part of Sentiment shifts carries the same detection-time gate, on Brand reasons, but that same checkbox's negative-sentiment half still delivers on every plan; see Plan gates for brand-signal alerts above. Fact contradiction is gated at detection too, on Fact check (see Fact contradiction alerts below).

What's on the screen

The list comes back ordered by the day each alert was detected, so it opens on the most recent alert rather than the biggest (api/routers/alerts.py#list_project_alerts, frontend/src/routes/(app)/alerts/+page.svelte#latest). Visibility alerts carry an impact_score, and those four kinds build it the same way: the size of the move, times the visibility it moved from, times the log of how many answers ran in the current week, plus a small constant on the two kinds that are not about a single prompt (api/services/watchdog.py#_impact_score, #build_prompt_alerts). What supplies the size differs, positions for a rank slide and a relative visibility change for the other three, so the score ranks a day's findings during detection. A newly cited competitor surge has no relative change, so it is scored on its current visibility in place of the size of the move times its baseline. For a rise from a baseline of at least 1%, that product is exactly the gain in visibility; below 1% the move is measured against a 1% floor, so the product is smaller than the gain. From 0%, the gain is the current visibility itself (#build_aggregate_alerts, #_relative_delta). Email highlights use recency; the score is not a number to read on its own.

The four brand-signal kinds also carry an impact_score, built from the same delta-times-visibility-times-log shape as above, but not all with the same visibility figure. Your own drop uses the baseline ratio, exactly like the four kinds above. A competitor's rise, the negative-sentiment share and a weakness reason's rate instead use whichever of baseline or current is larger, so a rise from a near-zero baseline is not scored as if nothing moved. Emerging brand and competitor-only social citation, having no baseline to move from at all, score simply on how many current-window answers qualified instead (api/services/brand_signals.py#_detect_own_drop, #_detect_competitor_rises, #_detect_negative_share, #_detect_weakness_reasons, #detect_emerging_brands, #detect_social_competitor_only).

Left alone, the list opens on the last 28 days. The date range filter at the top of the page switches it to 7, 28 or 90 days, carried in the URL as ?days=; any other value in the URL reads as 28 (frontend/src/lib/filters.js#readDays). The API returns up to 200 alerts for the window, and the screen says so once a list comes back full (frontend/src/routes/(app)/alerts/+page.server.js#ROW_CAP, api/routers/alerts.py#list_project_alerts). Below the hero, alerts group by the day they were detected. Opening one of the four visibility kinds shows the before and after pair, the two date ranges being compared, which engine moved most, and, for a rank slide, the rank movement itself; a visibility drop or a prompt drop additionally shows whether competitors moved too or the shift looks specific to you (frontend/src/routes/(app)/alerts/[id]/+page.svelte#movement, #category). The four brand-signal kinds open on their own layout instead; see Brand-signal alert detail below.

Brand-signal alert detail

Each of the four brand-signal kinds opens on its own hero instead of the shared before/after card. Brand share shift and sentiment shift both show a ratio or rate moving from one bold number to another, with the exact count behind each one (frontend/src/routes/(app)/alerts/[id]/+page.svelte#pctWithCount); sentiment shift adds up to 3 quoted excerpts backing the number (api/services/brand_signals.py#SENTIMENT_MAX_QUOTES). Emerging brand shows the current-window answer, prompt and occurrence counts, plus up to 3 example quotes (#EMERGING_MAX_EXAMPLES). Competitor-only social citation shows a table of that competitor's own qualifying social accounts, with the answers and prompts behind each one. Brand share shift is the only one of the four with a by-engine table; the other three do not break down by provider (frontend/src/routes/(app)/alerts/[id]/+page.svelte#providerRows). Every one of the four also carries a See what to do next link to the screen its evidence came from, Competitors, Sentiment, Sentiment reasons or Social sources (#nextStepLink).

Emerging brand alert detail showing the current-window answer, prompt and occurrence counts, and example quotes

Source watchlist alerts

Source watchlists can separately subscribe to gains/losses for exact domains or URLs. These compare adjacent seven-day periods ending yesterday within each recorded channel, with at least 30 completed outputs and five observed dates per period. A gain means zero previous appearances followed by one or more; a loss is the reverse. Missing or thin data is skipped. Repeated source/direction events within a list/channel are suppressed for seven days (api/services/source_watchlist_alerts.py#evaluate_subscriptions).

The saved detail shows each affected source, before/after counts, full-channel denominators, dates and recorded model/surface. These are observed citations, not proof of cause or complete capture. Each gain/loss alert gets its own team notification when that list enables in-app delivery. Email-only subscriptions still create a saved alert. Deleting the list stops future evaluation and email, while saved evidence stays available to current project members.

Saved source watchlist alert evidence

Fact contradiction alerts

When the project owner's plan includes Fact check, each watchdog run also raises one Fact contradiction alert for each active fact with at least one contradiction of its current wording still open in review, found in checks made in the 26 hours before the run, from a fact study or a checked prompt; on a plan without it, nothing is detected (api/services/fact_check/alerts.py#detect_fact_contradictions, #ALERT_WINDOW_HOURS). This is not a comparison of two windows and has no volume floor: one contradiction is enough. The contradiction itself is an AI model's judgement of an engine's answer, with the quote located in the answer by our code; see How a finding is made. The same fact does not raise another alert within 7 days, or while its alert is snoozed (api/services/watchdog.py#_is_duplicate, #DEDUPE_WINDOW_DAYS).

The alert shows the fact's current wording, how many answers contradicted it and from which engines (a count that can differ from the Open tab in Fact check, which also lists contradictions of an earlier wording), and a Review in Fact check link that opens Fact check with its review queue filtered to that fact (frontend/src/routes/(app)/alerts/[id]/+page.svelte#factReviewHref, frontend/src/routes/(app)/sentiment/facts/+page.server.js#readQuery). Reviewing the findings in Fact check does not change the alert; react to the alert here as with any other.

  • Notifications: the account-wide inbox an alert lands in, and the six report and recommendation kinds that land there too
  • Competitors: the comparison a competitor surge alert, and a brand share shift or emerging brand alert, draws on
  • Prompts: where a prompt drop or rank slide links back to
  • Brand reasons: the weakness labels a sentiment shift's reason half draws on
  • Social sources: the account identity a competitor-only social citation alert draws on
  • Fact check: the findings a fact contradiction alert draws on

Last verified 2026-09-29

Start monitoring your AI visibility.

See how AI search engines talk about your brand.

Free to start. No credit card required.