Alerts
An alert tells you that the AI assistants got something wrong about the brand, or that something moved: a price hallucination (a model quotes a price that is not the catalogue’s), a wrong availability (a model says in stock / out of stock against the store), a visibility drop (the share fell between two weekly samplings) a new competitor cited on several queries, an invented spec (a model gives the product a characteristic its identity sheet does not state — claimed.specs lists them), or, for a physical establishment, wrong establishment facts (wrong_local_fact: a model states an address or a phone number that is not the one declared on the market — claimed.address / claimed.phone, claimed.wrong says which), or an SEO regression (seo_regression: between two SEO crawls of the market, a key page went into error, got a noindex, lost its Product schema, title tag, meta description or canonical, or robots.txt blocks a search bot — claimed.signal, claimed.pages[{url, before, after}], claimed.count, claimed.bots; one alert per signal and market, with no model nor query, resolved by itself on the crawl that finds the pages healthy again). The first two, wrong_local_fact and the blocking SEO regressions (error, noindex, schema, robots) are critical: a shopper may buy — or not — on a false promise.
Skoup opens one alert per incident: if the same wrong price comes back the next week, the open alert gathers the new occurrences instead of opening another one. Each alert carries its proof — the claim (claimed) against the catalogue (truth), per model.
Statuses of an alert
newseentreatedresolvedignoredThe loop is: you fix the cause (the price in the store, the stock, the product page), you treat the alert, then you verify it. Verifying re-asks the alert’s query to every model: if the claim is gone, the alert becomes resolved; if it is still there, it stays treated with persisted: true.
I want to…
| I want to… | Call |
|---|---|
| List the open critical alerts | GET /v1/brands/{brand}/alerts?status=open&severity=critical |
| List the price hallucinations on France | GET /v1/brands/{brand}/alerts?kind=price_hallucination&market=FR |
| Read one alert and its proof | GET /v1/brands/{brand}/alerts/{alert} |
| Say the fix is made | POST /v1/brands/{brand}/alerts/{alert}/treat |
| Ignore an alert | POST /v1/brands/{brand}/alerts/{alert}/dismiss |
| Check the fix now | POST /v1/brands/{brand}/alerts/{alert}/verify — quota Skoup-Quota-Verifications |
status=open means new, seen and treated. Scopes: alerts:read, alerts:write. Events: alert.created, alert.updated (status change, with previous_attributes), alert.resolved.
Scenario: warn a client when a price hallucination appears
Subscribe an endpoint to alert.created, keep only price hallucinations, and send the proof to your client.
if (event.type === 'alert.created' && event.data.object.kind === 'price_hallucination') {
const alert = event.data.object
await notifyClient(alert.brand, {
// `product` is null when the AI priced the brand without naming an offer.
subject: alert.product?.title ?? 'the brand',
// A range: from `price` to `price_max`; `period` says what the price buys.
claimed: [alert.claimed?.price, alert.claimed?.price_max].filter(Boolean),
truth: [alert.truth?.min, alert.truth?.max],
period: alert.truth?.period,
currency: alert.truth?.currency,
})
}Once the price is fixed in the store, close the loop:
curl -X POST https://api.skoup.ai/v1/brands/br_034PJIMnN3EOTCU3TMUdLS/alerts/alrt_031CQlTxUKUOFYaHSd3kBY/treat \
-H "Authorization: Bearer skoup_test_4eC39HqLyjWDarjtT1zdp7dc"
curl -X POST https://api.skoup.ai/v1/brands/br_034PJIMnN3EOTCU3TMUdLS/alerts/alrt_031CQlTxUKUOFYaHSd3kBY/verify \
-H "Authorization: Bearer skoup_test_4eC39HqLyjWDarjtT1zdp7dc"verify answers 202 when a re-sampling starts (200 when one is already running); the verdict arrives later as alert.resolved, or alert.updated with persisted: true.
Pitfalls
- An SEO regression has no model nor query.
first_seen_atis the end of the crawl that saw it,queryandmodelarenull,occurrencesis 0: the proof is inclaimed.pages.POST …/verifydoes not apply; it resolves on the next crawl (alert.resolved). - A quoted price is not always one number.
claimed.price_kindisexact,range(frompricetoprice_max),fromorapproximate;claimed.period(month,year,once) andclaimed.unit(flat,per_user) say what it buys. A range that overlaps the real pricing opens no alert; a yearly price is only compared when the offer declares itsprice_annual. - A price alert may be about no product. When the AI prices the brand without naming an offer,
claimed.scopeisbrand,productisnullandtruthis the envelope of every active offer (truth.min–truth.max,truth.offers). There is one per market, never one per plan. - Only a wrong exact price on a named offer is
critical. A range, an approximation or a brand price that misses the pricing ismedium; the severity of an open alert may rise, never fall. truth.open_ended: an add-on on quote leaves the pricing open upwards; only a price undertruth.minis then wrong.- Verify only a treated alert, otherwise the call answers
422. - Verifications are rationed: 10 per brand per rolling 24 hours, and one per query per hour. When the quota is spent,
verifyanswers422 quota_exceeded; readSkoup-Quota-Verifications(limit,remaining,reset) before retrying. - Transitions are one-way: a resolved or ignored alert cannot be reopened — if the incident comes back, a new alert opens.
- An invented spec comes after the others: it is read in the Perception judgement, which ends after the sampling. It only exists for a product with an identity sheet, and its verification waits for that judgement too.
- Establishment facts only exist for a
localbrand with a tracked establishment that declares an address or a phone; one alert per establishment (location:id,name,city); hours are reported (claimed.hours), never judged. close_reason: rejudged: Skoup closed the alert itself because the claim no longer counts as wrong (a range that holds the pricing, for instance). It isignored, comes throughalert.updated— neveralert.resolved— and cannot be restored.- Test mode:
treatandverifyanswer like in live mode on the sandbox, but nothing is kept and no re-sampling runs.