{"openapi":"3.0.0","paths":{"/v1/reporting/looker":{"get":{"operationId":"ReportingController_looker","parameters":[{"name":"brandId","required":false,"in":"query","description":"Optional: scope the table to a single brand","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","description":"json (default) or csv — an RFC 4180 table for Power BI / Tableau / Excel","schema":{"enum":["json","csv"],"type":"string"}}],"responses":{"200":{"description":"One row per tracked prompt: run count, mention count and rate, the sentiment split, how many runs had an answer with no text, and the rate over the runs that had text. COMPATIBILITY: `mentionRate` (CSV `mention_rate_pct`) keeps counting every run, answers with no text included; the corrected, authoritative rate is `mentionRateMeasured` (CSV `mention_rate_measured_pct`), with `runsWithoutText` (CSV `runs_without_text`) beside it. `?format=csv` returns the same table as an RFC 4180 CSV attachment instead, whose columns are brand, prompt, intent, runs, mentioned, mention_rate_pct, positive, neutral, negative, runs_without_text, mention_rate_measured_pct (the last two appended, none moved).","content":{"application/json":{"schema":{"type":"object","required":["brand","generatedAt","rows"],"properties":{"brand":{"type":"string","description":"The resolved brand's name."},"generatedAt":{"type":"string","format":"date-time"},"rows":{"type":"array","items":{"type":"object","required":["prompt","intent","runs","mentioned","mentionRate","positive","neutral","negative","runsWithoutText","mentionRateMeasured"],"properties":{"prompt":{"type":"string"},"intent":{"type":"string"},"runs":{"type":"number","description":"Scan runs recorded for this prompt."},"mentioned":{"type":"number","description":"Runs in which the brand was mentioned."},"mentionRate":{"type":"number","description":"Compatibility value: mentioned / runs, as a percentage, counting every run, answers with no text included; the corrected rate is `mentionRateMeasured`. 0 when there were no runs — a measured zero over an empty denominator is not reported as a rate."},"positive":{"type":"number"},"neutral":{"type":"number"},"negative":{"type":"number"},"runsWithoutText":{"type":"number","description":"Of `runs`, the runs whose answer had no text (empty or whitespace only) and would otherwise count as a run without a mention. Such an answer establishes neither presence nor absence. Counted beside `runs`, `mentioned` and `mentionRate`, which still count every run (compatibility), so figures for earlier scans are unchanged in those fields. CSV: `runs_without_text`."},"mentionRateMeasured":{"type":"number","nullable":true,"description":"The corrected rate, and the authoritative one: `mentioned` over the runs that had text (`runs` minus `runsWithoutText`), as a percentage. Null, not 0, when no run had text. CSV: `mention_rate_measured_pct`, blank when null."}}}}}}},"text/csv":{"schema":{"type":"string","description":"RFC 4180 table, one header row then one row per prompt."}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"Flat one-row-per-prompt table for BI tools (Power BI, Tableau, Looker Studio)","tags":["reporting"]}},"/v1/reporting/kai-insights":{"get":{"operationId":"ReportingController_kaiInsights","parameters":[{"name":"brandId","required":false,"in":"query","description":"Optional: scope the feed to a single brand","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","description":"json (default) or csv — an RFC 4180 table for Power BI / Tableau / Excel","schema":{"enum":["json","csv"],"type":"string"}}],"responses":{"200":{"description":"One row per K+AI insight: kind, severity, status, confidence, title, recommendation, pipe-joined categories and creation time. `?format=csv` returns the same table as an RFC 4180 CSV attachment.","content":{"application/json":{"schema":{"type":"object","required":["brand","generatedAt","rows"],"properties":{"brand":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"},"rows":{"type":"array","items":{"type":"object","required":["kind","severity","status","confidence","title","recommendation","categories","createdAt"],"properties":{"kind":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"confidence":{"type":"number"},"title":{"type":"string"},"recommendation":{"type":"string"},"categories":{"type":"string","description":"Pipe-joined, so the CSV stays one cell per row. The JSON carries the same string rather than an array, so the two formats cannot disagree."},"createdAt":{"type":"string","format":"date-time"}}}}}}},"text/csv":{"schema":{"type":"string","description":"RFC 4180 table, one header row then one row per insight."}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"K+AI insight feed as a flat table for BI tools (one row per insight)","tags":["reporting"]}},"/v1/public/brands":{"get":{"operationId":"PublicApiController_brands","parameters":[],"responses":{"200":{"description":"The brands this token may see. A brand-pinned token returns only its pinned brand. A token for all brands returns the brands the person who created it can see: every brand while they hold an organisation-wide role, and none once they have left the organisation. It can still read any brand of the organisation by its id.","content":{"application/json":{"schema":{"type":"object","required":["generatedAt","brands"],"properties":{"generatedAt":{"type":"string","format":"date-time","description":"When this response was assembled, ISO 8601 UTC."},"brands":{"type":"array","items":{"type":"object","required":["id","name","primaryDomain","status","industry"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"primaryDomain":{"type":"string","nullable":true,"description":"Normalised apex domain, or null when the brand has none recorded."},"status":{"type":"string","enum":["onboarding","active","paused"]},"industry":{"type":"string","nullable":true}}}}}}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"List your brands and their IDs","tags":["public-api"]}},"/v1/public/brands/{brandId}/visibility":{"get":{"operationId":"PublicApiController_visibility","parameters":[{"name":"brandId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Per-engine AI Presence for the brand. All thirteen top-level fields are declared. Several carry a STATE DISCRIMINANT and change shape with it — those keep their contents open rather than pinning one state's fields as universal.","content":{"application/json":{"schema":{"type":"object","required":["shareOfVoice","recommendationRate","prominence","accuracy","citations","aiDemand","aiVolatility","categoryBenchmark","byEngine","sourceMix","sovReliability","promptsTracked","engineHealthRead"],"properties":{"shareOfVoice":{"type":"object","required":["value","provenance","note","basis","movement"],"description":"The headline. `value` IS A NUMBER AND MAY BE 0: on a brand with no stored answer the producer averages entitled engines counting an unanswered one as zero, so 0 here means \"measured as zero on this basis\", and `basis` is what says how much was actually behind it. The same shape is served for a brand whose every stored answer had no text (no snapshot is written for it): `basis.measured` 0 then means not measured, not 0%. (An earlier version of this contract said `value` is null rather than 0 when nothing has been measured — that was true of `prominence`, not of this field.)","properties":{"value":{"type":"number"},"provenance":{"type":"string"},"note":{"type":"string","description":"Which of the two averaging contracts produced the value — the recorded-eligible-set one, or the older entitled-engine one that counts an unanswered engine as zero."},"basis":{"type":"object","required":["recorded","measured","eligible","basisVersion","aggregationGrain","coverage"],"description":"The population behind the value. `recorded` says whether the newer eligible-set contract produced it; `measured` of `eligible` is the sample, and `coverage` splits the measured set into fresh and carried, with `missing` a third category for eligible engines absent from both.","properties":{"recorded":{"type":"boolean"},"measured":{"type":"number"},"eligible":{"type":"number","nullable":true,"description":"Null when no eligible set was recorded, so the denominator is unprovable rather than zero."},"basisVersion":{"type":"string","nullable":true,"description":"Null on an unrecorded basis — the figure then came from the older fixed-denominator arithmetic."},"aggregationGrain":{"type":"string","enum":["latest_per_engine"],"description":"Named so this figure is never diffed against a per-period series, which is a different grain."},"answersWithoutText":{"type":"number","nullable":true,"description":"Of the answers behind this figure's newest snapshots, how many had no text (empty or whitespace only). Such an answer does not count as an absence and is left out of the denominator; this is how many were. Null where it does not apply or cannot be read: a snapshot written before `answer_text_v4` counted such answers, and a snapshot with no scan to read. Never 0 for those."},"coverage":{"type":"object","required":["recorded","fresh","carried","missing"],"properties":{"recorded":{"type":"boolean","description":"False means the fresh/carried split could not be proven, and both are null rather than guessed."},"fresh":{"type":"number","nullable":true},"carried":{"type":"number","nullable":true},"missing":{"type":"number","nullable":true,"description":"Eligible engines with no row on either side — neither fresh nor carried. Null when there is no provable eligible set."}}},"observed":{"type":"object","nullable":true,"required":["from","to"],"description":"The span of instants the engines behind this figure were last observed, from the stored snapshot rows (the earliest and the latest, equal when one scan wrote them all). Null when none of those snapshots is dated: a snapshot that was not a live answer is not dated as an observation. Never today's date and never the time of a read.","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}}}},"movement":{"type":"object","required":["prior","cause"],"description":"The prior period, as a movement. `prior` null with `cause` null means there was nothing earlier to compare with — \"no prior scan\", not \"no movement\" and never 0. `prior` null WITH a cause means a seam (model, prompt cohort or engine set) makes the comparison meaningless, and the cause names which one.","properties":{"prior":{"type":"number","nullable":true},"cause":{"type":"string","nullable":true,"description":"Null when the comparison is sound or simply has no earlier end. Non-null names the seam that makes it meaningless."}}}}},"recommendationRate":{"type":"object","required":["value","provenance","note","basis"],"description":"A conservative floor: the share of answers that actively recommend the brand (top, positive, accurate). CARRIES NO `movement` — unlike shareOfVoice, prominence, accuracy and citations, this reading ships no prior, so a caller must not expect the field to be present and null.","properties":{"value":{"type":"number"},"provenance":{"type":"string"},"note":{"type":"string"},"basis":{"type":"object","required":["recorded","measured","eligible","basisVersion","aggregationGrain","coverage"],"description":"The population behind the value. `recorded` says whether the newer eligible-set contract produced it; `measured` of `eligible` is the sample, and `coverage` splits the measured set into fresh and carried, with `missing` a third category for eligible engines absent from both.","properties":{"recorded":{"type":"boolean"},"measured":{"type":"number"},"eligible":{"type":"number","nullable":true,"description":"Null when no eligible set was recorded, so the denominator is unprovable rather than zero."},"basisVersion":{"type":"string","nullable":true,"description":"Null on an unrecorded basis — the figure then came from the older fixed-denominator arithmetic."},"aggregationGrain":{"type":"string","enum":["latest_per_engine"],"description":"Named so this figure is never diffed against a per-period series, which is a different grain."},"answersWithoutText":{"type":"number","nullable":true,"description":"Of the answers behind this figure's newest snapshots, how many had no text (empty or whitespace only). Such an answer does not count as an absence and is left out of the denominator; this is how many were. Null where it does not apply or cannot be read: a snapshot written before `answer_text_v4` counted such answers, and a snapshot with no scan to read. Never 0 for those."},"coverage":{"type":"object","required":["recorded","fresh","carried","missing"],"properties":{"recorded":{"type":"boolean","description":"False means the fresh/carried split could not be proven, and both are null rather than guessed."},"fresh":{"type":"number","nullable":true},"carried":{"type":"number","nullable":true},"missing":{"type":"number","nullable":true,"description":"Eligible engines with no row on either side — neither fresh nor carried. Null when there is no provable eligible set."}}},"observed":{"type":"object","nullable":true,"required":["from","to"],"description":"The span of instants the engines behind this figure were last observed, from the stored snapshot rows (the earliest and the latest, equal when one scan wrote them all). Null when none of those snapshots is dated: a snapshot that was not a live answer is not dated as an observation. Never today's date and never the time of a read.","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}}}}}},"prominence":{"type":"object","required":["value","provenance","note","movement"],"description":"How prominently the brand surfaces when mentioned. `value` IS NULLABLE and is null — never 0 — when nothing has been measured, because an unmeasured prominence has no zero to average toward. `basis` is null while every engine is on one prominence basis; while an engine has not re-scanned since the basis changed (answers that do not name the brand no longer count), `value` averages only the engines already on the new basis and `basis` says how many (`measuredOn` of `of`) with the note `Mixed basis until every engine re-scans`. `unavailable` says why `value` is null: `no_scan`, `no_named_answer` (a scan exists and no answer named the brand) or `rescan_needed` (a snapshot from before the basis).","properties":{"value":{"type":"number","nullable":true},"provenance":{"type":"string"},"note":{"type":"string"},"movement":{"type":"object","required":["prior","cause"],"description":"The prior period, as a movement. `prior` null with `cause` null means there was nothing earlier to compare with — \"no prior scan\", not \"no movement\" and never 0. `prior` null WITH a cause means a seam (model, prompt cohort or engine set) makes the comparison meaningless, and the cause names which one.","properties":{"prior":{"type":"number","nullable":true},"cause":{"type":"string","nullable":true,"description":"Null when the comparison is sound or simply has no earlier end. Non-null names the seam that makes it meaningless."}}},"basis":{"type":"object","nullable":true,"properties":{"mixed":{"type":"boolean"},"measuredOn":{"type":"number"},"of":{"type":"number"},"note":{"type":"string"}}},"unavailable":{"type":"string","nullable":true,"enum":["no_scan","no_named_answer","rescan_needed",null]}}},"accuracy":{"type":"object","required":["ok","total","movement"],"description":"A COUNT PAIR, not a rate: the caller divides. `total` 0 means nothing was graded, and no rate should be synthesised from it.","properties":{"ok":{"type":"number"},"total":{"type":"number"},"movement":{"type":"object","required":["prior","cause"],"description":"The prior period, as a movement. `prior` null with `cause` null means there was nothing earlier to compare with — \"no prior scan\", not \"no movement\" and never 0. `prior` null WITH a cause means a seam (model, prompt cohort or engine set) makes the comparison meaningless, and the cause names which one.","properties":{"prior":{"type":"number","nullable":true},"cause":{"type":"string","nullable":true,"description":"Null when the comparison is sound or simply has no earlier end. Non-null names the seam that makes it meaningless."}}},"flagged":{"type":"object","required":["identified","unidentified"],"description":"The runs counted in `total` but not in `ok` (the flagged ones, `total` minus `ok`), split by whether the run's own stored finding identified an approved Fact: `identified` plus `unidentified` equals `total` minus `ok`. A flagged run with no stored finding, including one graded before findings were stored, is counted in `unidentified`: it was flagged for accuracy review and no conflict with an approved Fact is established for it. Both are counts, and 0 is a count, not a rate; with `total` 0 nothing was graded. Absent on an older API build.","properties":{"identified":{"type":"number","description":"Flagged runs whose stored finding records the approved Fact it conflicts with (its id, its code or its claim)."},"unidentified":{"type":"number","description":"Flagged runs whose stored finding records none."}}}}},"citations":{"type":"object","required":["owned","total","movement"],"description":"A COUNT PAIR, not a share: `owned` of `total` ANSWER REFERENCES (one website cited in one answer; one link per website is kept for each answer), counted over the latest answer per active prompt and model engine. As with accuracy, a 0 total is not a 0 share. `units` (additive) names the same set in three units and dates it; it is never a count of every link an engine cited.","properties":{"owned":{"type":"number"},"total":{"type":"number"},"movement":{"type":"object","required":["prior","cause"],"description":"The prior period, as a movement. `prior` null with `cause` null means there was nothing earlier to compare with — \"no prior scan\", not \"no movement\" and never 0. `prior` null WITH a cause means a seam (model, prompt cohort or engine set) makes the comparison meaningless, and the cause names which one.","properties":{"prior":{"type":"number","nullable":true},"cause":{"type":"string","nullable":true,"description":"Null when the comparison is sound or simply has no earlier end. Non-null names the seam that makes it meaningless."}}},"units":{"type":"object","required":["state","cohort","asOf"],"description":"The three citation units over one named set of answers, dated from the brand's last completed scan. `state` is measured (the counts below are present, and a zero is a measured zero), unmeasured (no scan has completed, no counts) or unavailable (the read did not answer, no counts). `retainedLinks` is a lower bound: one link per website is kept for each answer.","properties":{"state":{"type":"string","enum":["measured","unmeasured","unavailable"]},"cohort":{"type":"string","enum":["lifetime_evidence","lifetime_evidence_active_prompts","latest_per_prompt_engine","one_scan"]},"asOf":{"type":"string","nullable":true},"domains":{"type":"number"},"retainedLinks":{"type":"number"},"answerReferences":{"type":"number"},"ownedReferences":{"type":"number"},"repeatDomains":{"type":"number"},"unknownProvenanceReferences":{"type":"number"}}}}},"aiDemand":{"description":"The AI-Demand reading. It has NO discriminant field: tell the variants apart by presence — `score` means a built index, `cause` means one of the two absences, and `cause`'s value says which.","oneOf":[{"type":"object","description":"A built index. Carries `score`/`band` and NO `value` — the figure is the score, and a `value` here would be a second name for it.","required":["score","band","queries","totalQueries","provenance","basis","volumeBasisNote","expiredQueries","volumeExpiryNote","note"],"properties":{"score":{"type":"number","description":"The composite, 0-100, modelled over MEASURED tracked queries only."},"band":{"type":"string","enum":["high","medium","low"]},"queries":{"type":"number","description":"Queries folded into the score."},"totalQueries":{"type":"number","description":"Queries tracked. `queries` of `totalQueries` is the real sample, so a thin measured set cannot pass as brand-wide coverage."},"provenance":{"type":"string","enum":["modeled"],"description":"MODELLED, never measured: per-prompt AI ask volume is not observable, and this index does not pretend otherwise."},"basis":{"type":"string","enum":["measured","estimated"],"description":"The word over the figure — whether every folded row was graded measured. NOT which arithmetic ran."},"volumeBasisNote":{"type":"string","nullable":true},"expiredQueries":{"type":"number","description":"Tracked queries whose volume was measured and has since passed its retention window."},"volumeExpiryNote":{"type":"string","nullable":true},"note":{"type":"string"},"volumeMarket":{"type":"object","description":"Which market the keyword volume behind this reading was measured for and on which days. `current` is what is counted for the brand's current market; `other` is held for another market (or none recorded) and is NOT evidence about the brand's current market. Absent on a build from before this field, and where nothing is tracked.","required":["brand","gradedFor","tracked","current","other"],"properties":{"brand":{"type":"object","description":"The market a figure was measured for (`keywordMarket`, null when never recorded) against the brand's current market (`brandMarket`, null when the brand has none on record). `state` is `same`, `differs`, `keyword-market-not-recorded` or `brand-market-not-recorded`; an unknown is never `same`.","required":["state","keywordMarket","brandMarket"],"properties":{"state":{"type":"string","enum":["same","differs","keyword-market-not-recorded","brand-market-not-recorded"]},"keywordMarket":{"type":"object","nullable":true,"required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"brandMarket":{"type":"object","nullable":true,"required":["country","name"],"properties":{"country":{"type":"string"},"name":{"type":"string"}}}}},"gradedFor":{"type":"object","nullable":true,"required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}},"description":"The market the counted figures answer; null where Ansyra measures no market for this brand."},"tracked":{"type":"number","description":"Keywords tracked."},"current":{"type":"object","required":["count","measuredFrom","measuredTo"],"properties":{"count":{"type":"number"},"measuredFrom":{"type":"string","nullable":true,"description":"UTC day, YYYY-MM-DD."},"measuredTo":{"type":"string","nullable":true,"description":"UTC day, YYYY-MM-DD."}}},"other":{"type":"array","items":{"type":"object","required":["comparison","count","measuredFrom","measuredTo"],"properties":{"comparison":{"type":"object","description":"The market a figure was measured for (`keywordMarket`, null when never recorded) against the brand's current market (`brandMarket`, null when the brand has none on record). `state` is `same`, `differs`, `keyword-market-not-recorded` or `brand-market-not-recorded`; an unknown is never `same`.","required":["state","keywordMarket","brandMarket"],"properties":{"state":{"type":"string","enum":["same","differs","keyword-market-not-recorded","brand-market-not-recorded"]},"keywordMarket":{"type":"object","nullable":true,"required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"brandMarket":{"type":"object","nullable":true,"required":["country","name"],"properties":{"country":{"type":"string"},"name":{"type":"string"}}}}},"count":{"type":"number"},"measuredFrom":{"type":"string","nullable":true},"measuredTo":{"type":"string","nullable":true}}}}}}}},{"type":"object","description":"The index could be read and is not built yet. The counts are NUMBERS read off the rows, and the customer has something to do about it.","required":["value","provenance","cause","totalQueries","expiredQueries","note"],"properties":{"value":{"type":"number","nullable":true,"description":"Always null on this reading: there is no figure, and 0 would be the fabrication this shape exists to prevent."},"provenance":{"type":"string","enum":["not_measured"]},"cause":{"type":"string","enum":["no_queries","no_signals"],"description":"What is missing: tracked keywords, or an AI-answerability signal over them."},"totalQueries":{"type":"number","description":"A real count. Non-nullable here, which is what separates this reading from the unreadable one."},"expiredQueries":{"type":"number"},"note":{"type":"string"},"volumeMarket":{"type":"object","description":"Which market the keyword volume behind this reading was measured for and on which days. `current` is what is counted for the brand's current market; `other` is held for another market (or none recorded) and is NOT evidence about the brand's current market. Absent on a build from before this field, and where nothing is tracked.","required":["brand","gradedFor","tracked","current","other"],"properties":{"brand":{"type":"object","description":"The market a figure was measured for (`keywordMarket`, null when never recorded) against the brand's current market (`brandMarket`, null when the brand has none on record). `state` is `same`, `differs`, `keyword-market-not-recorded` or `brand-market-not-recorded`; an unknown is never `same`.","required":["state","keywordMarket","brandMarket"],"properties":{"state":{"type":"string","enum":["same","differs","keyword-market-not-recorded","brand-market-not-recorded"]},"keywordMarket":{"type":"object","nullable":true,"required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"brandMarket":{"type":"object","nullable":true,"required":["country","name"],"properties":{"country":{"type":"string"},"name":{"type":"string"}}}}},"gradedFor":{"type":"object","nullable":true,"required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}},"description":"The market the counted figures answer; null where Ansyra measures no market for this brand."},"tracked":{"type":"number","description":"Keywords tracked."},"current":{"type":"object","required":["count","measuredFrom","measuredTo"],"properties":{"count":{"type":"number"},"measuredFrom":{"type":"string","nullable":true,"description":"UTC day, YYYY-MM-DD."},"measuredTo":{"type":"string","nullable":true,"description":"UTC day, YYYY-MM-DD."}}},"other":{"type":"array","items":{"type":"object","required":["comparison","count","measuredFrom","measuredTo"],"properties":{"comparison":{"type":"object","description":"The market a figure was measured for (`keywordMarket`, null when never recorded) against the brand's current market (`brandMarket`, null when the brand has none on record). `state` is `same`, `differs`, `keyword-market-not-recorded` or `brand-market-not-recorded`; an unknown is never `same`.","required":["state","keywordMarket","brandMarket"],"properties":{"state":{"type":"string","enum":["same","differs","keyword-market-not-recorded","brand-market-not-recorded"]},"keywordMarket":{"type":"object","nullable":true,"required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"brandMarket":{"type":"object","nullable":true,"required":["country","name"],"properties":{"country":{"type":"string"},"name":{"type":"string"}}}}},"count":{"type":"number"},"measuredFrom":{"type":"string","nullable":true},"measuredTo":{"type":"string","nullable":true}}}}}}}},{"type":"object","description":"The read FAILED on our side. Same keys as the reading above and a different value in every one of them: nothing here is a measurement, and there is nothing for the customer to connect or add.","required":["value","provenance","cause","totalQueries","expiredQueries","note"],"properties":{"value":{"type":"number","nullable":true},"provenance":{"type":"string","enum":["not_measured"]},"cause":{"type":"string","enum":["read_failed","not_wired","unreadable_answer"],"description":"WHICH fault, for an operator. The three read alike to a customer and differently to us, and this vocabulary is disjoint from the absence one above on purpose."},"totalQueries":{"type":"number","nullable":true,"enum":[null],"description":"ALWAYS null: this arm never reached the rows. Never 0, which would be a count we do not have."},"expiredQueries":{"type":"number","nullable":true,"enum":[null],"description":"Always null, for the same reason as totalQueries."},"note":{"type":"string"}}}]},"aiVolatility":{"description":"Cited-source turnover for the brand's industry cohort. Read `state` first: it selects the variant.","oneOf":[{"type":"object","required":["state","measures","industry","turnover","band","bands","orgs","cells","engineBasis","windowDays","k","minCells","maxOrgCellShare"],"properties":{"state":{"type":"string","enum":["ready"]},"measures":{"type":"string","enum":["cited_source_turnover"],"description":"The quantity, named in the payload so a reader never infers it from a label."},"industry":{"type":"string"},"turnover":{"type":"number","description":"The index: the MEDIAN per-cell change rate, 0..1, 3 dp."},"band":{"type":"string"},"bands":{"type":"array","description":"The band edges, shipped with the value so the label always has a visible basis.","items":{"type":"object","required":["key","label","min","max"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"min":{"type":"number"},"max":{"type":"number"}}}},"orgs":{"type":"number","description":"Distinct organisations behind the counted cells, SELF-INCLUSIVE."},"cells":{"type":"number"},"engineBasis":{"type":"array","items":{"type":"string"}},"windowDays":{"type":"number"},"k":{"type":"number"},"minCells":{"type":"number"},"maxOrgCellShare":{"type":"number","description":"The disclosed dominance RULE, not the measured concentration: publishing the real share would say something about how the cohort is composed."}}},{"type":"object","description":"A cohort exists but has not cleared the organisation, cell-count or concentration floor. Carries no turnover.","required":["state","industry","reason","orgs","cells","k","minCells","windowDays"],"properties":{"state":{"type":"string","enum":["insufficient"]},"industry":{"type":"string"},"reason":{"type":"string","enum":["orgs","cells","concentrated"]},"orgs":{"type":"number"},"cells":{"type":"number"},"k":{"type":"number"},"minCells":{"type":"number"},"windowDays":{"type":"number"}}},{"type":"object","description":"A corpus was read and no measurable change was found in it.","required":["state","industry","windowDays","scansPerBrand"],"properties":{"state":{"type":"string","enum":["no_transitions"]},"industry":{"type":"string"},"windowDays":{"type":"number"},"scansPerBrand":{"type":"number"}}},{"type":"object","description":"No scans in the window at all — a different fact from no change within one.","required":["state","industry","windowDays"],"properties":{"state":{"type":"string","enum":["no_corpus"]},"industry":{"type":"string"},"windowDays":{"type":"number"}}},{"type":"object","required":["state","k"],"properties":{"state":{"type":"string","enum":["no_industry"]},"k":{"type":"number"}}},{"type":"object","description":"The read itself failed. A fact about this load, not about the industry, which is why it is never cached.","required":["state"],"properties":{"state":{"type":"string","enum":["unavailable"]}}}]},"categoryBenchmark":{"description":"The cross-tenant category benchmark. Read `state` first: it selects the variant. `ready` carries the composed aggregate, `insufficient` the honest progress count and a reason, and `no_industry` says the brand has no industry set.","oneOf":[{"type":"object","description":"Composed and k-anonymous. Exposes an aggregate only: a mean of per-engine medians and a self-inclusive organisation count.","required":["state","industry","k","medianSov","peerCount","engineBasis","basisComplete","ownOnBasis"],"properties":{"state":{"type":"string","enum":["ready"]},"industry":{"type":"string"},"k":{"type":"number","description":"The anonymity threshold the cohort cleared."},"market":{"type":"string","description":"The measurement market the cohort was scoped to; a ready benchmark from a current server always carries it."},"medianSov":{"type":"number","description":"Mean of the per-engine peer medians over the qualifying basis."},"peerCount":{"type":"number","description":"The MINIMUM distinct-organisation count across qualifying cells, and SELF-INCLUSIVE — the reading organisation is in it, so copy must say \"tracked organizations\", never \"peers\"."},"engineBasis":{"type":"array","items":{"type":"string"},"description":"The engines the median was composed over, in entitled order."},"basisComplete":{"type":"boolean","description":"True only when every entitled engine qualified; false means the named subset."},"ownOnBasis":{"type":"number","nullable":true,"description":"The reader's own value on the SAME basis, null when it has no measurement on some basis engine — a mean over a different subset would be a different quantity."}}},{"type":"object","description":"Composition refused. Carries the progress count and which floor is unmet, and no aggregate of any kind.","required":["state","industry","peers","k","reason"],"properties":{"state":{"type":"string","enum":["insufficient"]},"industry":{"type":"string"},"peers":{"type":"number","description":"SELF-INCLUSIVE organisations tracked so far — the count that moves toward the unlock, not a peer count."},"k":{"type":"number"},"reason":{"type":"string","enum":["peers","sample","no_market"],"description":"`peers` = too few organisations; `sample` = organisations cleared k but per-cell depth has not; `no_market` = the brand has no market, so it cannot be placed in a market cohort."}}},{"type":"object","description":"The brand has no industry set, which is its own fix and not a thin cohort.","required":["state","k"],"properties":{"state":{"type":"string","enum":["no_industry"]},"k":{"type":"number"}}}]},"byEngine":{"type":"array","description":"ONE CELL PER ENGINE THE PRODUCT KNOWS, not one per engine that answered — `inPlan` says whether the plan entitles it, and a cell exists either way. Different shape from the `engines[]` cells on /queries/{id}, which are per-ANSWERING-engine.","items":{"type":"object","required":["engine","label","inPlan","sov","recommendationRate","promptCount","prominence","accuracy","sentiment","provenance","source","engineFailing","stability"],"properties":{"engine":{"type":"string"},"label":{"type":"string"},"inPlan":{"type":"boolean"},"sov":{"type":"number"},"evidence":{"type":"string","enum":["fresh","carried","missing","unattributed"],"description":"`fresh`: this engine's latest stored measurement was written by the brand's latest completed scan. `carried`: it was written by an earlier scan and still stands, and is not the latest scan's result. `missing`: the engine has no stored measurement at all, so `sov` is a zero-fill and not 0%. `unattributed`: a measurement exists but which scan wrote it was not recorded (it predates scan attribution, or the brand has no completed scan), so it is not said to be fresh or carried."},"observedAt":{"type":"string","format":"date-time","nullable":true,"description":"The stored instant of this engine's latest measurement (the time the scan runner wrote its snapshot), never the time of this read. Null for a `missing` engine, for a snapshot that was not a live answer, and where the row carries no readable instant; a date is never invented."},"recommendationRate":{"type":"number"},"promptCount":{"type":"number","nullable":true},"recDecided":{"type":"number","nullable":true,"description":"Runs of this engine whose brand mention this contract could READ, behind `recommendationRate`. An answer with no text is not one (since `answer_text_v4`; see `answersWithoutText`). 0 means nothing was decidable, so the rate is a floor that claims nothing. `null` means the evidence is unavailable, NOT zero."},"answersWithoutText":{"type":"number","nullable":true,"description":"Of this engine's answers behind its latest snapshot, how many had no text (empty or whitespace only) and were left out of `sov` and `recDecided`. Null where it does not apply or cannot be read (a snapshot written before `answer_text_v4`, or with no scan to read), never 0."},"prominence":{"type":"number","nullable":true,"description":"How prominently the brand surfaces in this engine's answers that name it. Null, never 0, when none of them did."},"prominenceBasis":{"type":"object","nullable":true,"required":["answers","basisVersion","note"],"description":"Which basis THIS engine's `prominence` is on, set only while the brand's engines are not all on one prominence basis (the same condition as `prominence.basis`, whose level then averages only the engines on the new basis) and null otherwise, and null for an engine with no snapshot. `answers` is `named` when the engine's latest snapshot averages the answers that name the brand, and `all` when it was written before that basis (`prominence_mentioned_v5`) and averages every scored answer. `basisVersion` is that snapshot's own stamp (null when it recorded none). `note` is the caption to print beside the figure: `named answers`, `all answers (older basis)`, or, for an engine on the new basis whose `prominence` is null because no answer named the brand, `Not established: no answer named the brand yet`.","properties":{"answers":{"type":"string","enum":["named","all"]},"basisVersion":{"type":"string","nullable":true},"note":{"type":"string"}}},"accuracy":{"type":"object","required":["ok","total"],"properties":{"ok":{"type":"number"},"total":{"type":"number"}}},"sentiment":{"type":"object","required":["positive","neutral","negative"],"properties":{"positive":{"type":"number"},"neutral":{"type":"number"},"negative":{"type":"number"}}},"provenance":{"type":"string"},"source":{"type":"string","nullable":true},"engineFailing":{"type":"boolean","nullable":true,"description":"Whether this engine's most recent scan attempt failed, from the platform-wide engine health record. `true`: the record holds a failure more recent than this engine's last success, so the figures in this cell predate it. `false`: the record was read and holds no such failure. `null`: the engine health record could not be read (`engineHealthRead` is `unreadable`), so this cell makes no claim either way; null is not a healthy engine."},"stability":{"type":"object","nullable":true,"required":["rangeLow","rangeHigh","n","state"],"description":"NULL when the sample is insufficient, or when the stability mean disagrees with the headline share by more than the allowed margin — a refusal to report a band the figure beside it contradicts, not a missing value.","properties":{"rangeLow":{"type":"number"},"rangeHigh":{"type":"number"},"n":{"type":"number"},"state":{"type":"string","enum":["stable","unstable"]}}}}}},"sourceMix":{"type":"object","required":["live","mock","seed","total","mockShare"],"properties":{"live":{"type":"number"},"mock":{"type":"number"},"seed":{"type":"number"},"total":{"type":"number"},"mockShare":{"type":"number"}}},"sovReliability":{"type":"object","required":["sample","floor","reliable"],"properties":{"sample":{"type":"number"},"floor":{"type":"number"},"reliable":{"type":"boolean"}}},"promptsTracked":{"type":"number"},"engineHealthRead":{"type":"string","enum":["ok","unreadable"],"description":"Whether the engine health record behind `byEngine[].engineFailing` was read. `unreadable` means every row's `engineFailing` is null."}}}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"AI Presence per engine for a brand","tags":["public-api"]}},"/v1/public/brands/{brandId}/queries":{"get":{"operationId":"PublicApiController_queries","parameters":[{"name":"brandId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Tracked prompts with presence and preference, one element per prompt. Every row field is declared; `engines` holds per-engine cells whose own shape is not described here.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["id","text","intent","category","cluster","status","volume","sov","engines","coverage","entitledEngineCount","flagged","visibility"],"properties":{"id":{"type":"string","format":"uuid"},"text":{"type":"string"},"intent":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"cluster":{"type":"string","nullable":true},"status":{"type":"string"},"volume":{"type":"object","required":["value","provenance","note"],"description":"AI ask volume per prompt is not observable, so `value` is always null and `provenance` always `not_measured`. The field exists to say that in the payload rather than to leave a reader inferring it from an absence.","properties":{"value":{"type":"number","nullable":true},"provenance":{"type":"string"},"note":{"type":"string"}}},"sov":{"type":"object","required":["value","provenance","note"],"description":"Share of the plan's entitled engines whose latest answer mentions the brand. `value` is null — never 0 — when no engine has answered yet or the read was truncated, and `provenance` names which of those it is. COMPATIBILITY: an engine whose latest answer has no text counts as not mentioned here, as it always did; it is not an absence, and `coverage.noText` counts those answers.","properties":{"value":{"type":"number","nullable":true},"provenance":{"type":"string"},"note":{"type":"string"}}},"coverage":{"type":"object","required":["scanned","mentions","cited","total"],"properties":{"scanned":{"type":"number"},"mentions":{"type":"number"},"cited":{"type":"number"},"unreadable":{"type":"number","description":"Of the `scanned` engines, how many answered with text the matcher could not read for this brand's name. When it equals `scanned`, `visibility` is `absent` by arithmetic and not by measurement."},"noText":{"type":"number","description":"Of the `scanned` engines, how many latest answers have no text (empty or whitespace only) and would otherwise read as an answer without a mention. Such an answer establishes neither presence nor absence: when `noText` plus `unreadable` equals `scanned`, `visibility` is `absent` by arithmetic and not by measurement. Counted apart from `unreadable`, and `mentionState`, `visibility` and `sov` keep the values they had before this count existed."},"total":{"type":"number"}}},"engines":{"type":"array","description":"One cell per engine. `scanned` false means this engine never ran the prompt; `sampleUnknown` true means it ran but the sample cannot be read — two different facts that must not collapse into one \"no data\".","items":{"type":"object","required":["engineKey","label","inPlan","scanned","mentioned","cited","accuracyOk","sentiment","rank","total","sampleUnknown"],"properties":{"engineKey":{"type":"string"},"label":{"type":"string"},"inPlan":{"type":"boolean"},"scanned":{"type":"boolean"},"mentioned":{"type":"boolean"},"cited":{"type":"boolean"},"accuracyOk":{"type":"boolean","nullable":true},"sentiment":{"type":"string","nullable":true},"rank":{"type":"number","nullable":true,"description":"The brand's position in this engine's latest answer's list of options. Null unless that answer names the brand (`mentioned` true): a rank is never served beside an answer that does not name it."},"total":{"type":"number","nullable":true,"description":"How many options that list held. Null whenever `rank` is."},"sampleUnknown":{"type":"boolean"},"mentionState":{"type":"string","nullable":true,"enum":["mentioned","not_mentioned","undecidable",null],"description":"`undecidable` means whether this answer names the brand could not be decided: the brand NAME could not be read in it, or the provider did not say the answer finished (cut off, blocked, refused, failed or not stated) and the part received does not name the brand. `mentioned` is false and that false is NOT an absence. `null` means the row was never graded (written before this contract), and `mentioned` is then the only thing it supports. For an answer with no text (empty or whitespace only) this stays `not_mentioned` as a compatibility value and is not an absence; read `noAnswerText`."},"noAnswerText":{"type":"boolean","description":"True when this engine's latest answer has no text (empty or whitespace only) and would otherwise read as an answer without a mention. Such an answer establishes neither presence nor absence, so a `mentioned: false` beside it is NOT an absence. `mentionState` keeps its value for it (`not_mentioned`, or null for a row graded before that field existed)."}}}},"entitledEngineCount":{"type":"number"},"flagged":{"type":"boolean"},"visibility":{"type":"string","description":"Compatibility value: the rollup of the engine cells (`recommended`, `linked`, `unlinked`, `misrepresented` or `absent`). It is `absent` by arithmetic, not by measurement, when `coverage.noText` plus `coverage.unreadable` equals `coverage.scanned`; read those counts beside it."}}}}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"Tracked prompts and how visible you are in each","tags":["public-api"]}},"/v1/public/brands/{brandId}/queries/{queryId}":{"get":{"operationId":"PublicApiController_queryDetail","parameters":[{"name":"brandId","required":true,"in":"path","schema":{"type":"string"}},{"name":"queryId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"One tracked prompt in full: the prompt, a summary of its measurement, per-engine cells and the observation receipt. `engines` cells and the individual observations are not described here.","content":{"application/json":{"schema":{"type":"object","required":["prompt","summary","engines","observationEvidence"],"properties":{"prompt":{"type":"object","required":["id","text","intent","cluster","status"],"properties":{"id":{"type":"string","format":"uuid"},"text":{"type":"string"},"intent":{"type":"string","nullable":true},"cluster":{"type":"string","nullable":true},"status":{"type":"string"}}},"summary":{"type":"object","required":["engines","runs","mentionRate","accuracy","avgProminence","entitledEngineCount"],"properties":{"engines":{"type":"number"},"runs":{"type":"number"},"mentionRate":{"type":"number","nullable":true,"description":"Compatibility value over every run, answers with no text included; the corrected rate is `mentionRateMeasured`. Null — not 0 — when there are no runs to take a rate over."},"noTextRuns":{"type":"number","description":"Of `runs`, how many answers had no text (empty or whitespace only) and would otherwise read as an answer without a mention. Such an answer establishes neither presence nor absence, and is left out of `mentionRateMeasured`."},"mentionRateMeasured":{"type":"number","nullable":true,"description":"The corrected rate, and the authoritative one: mentions over the runs that had text (`runs` minus `noTextRuns`), as a percentage. Null, not 0, when no run had text. Equal to `mentionRate` when `noTextRuns` is 0."},"accuracy":{"type":"object","required":["ok","total"],"properties":{"ok":{"type":"number"},"total":{"type":"number"}}},"avgProminence":{"type":"number","nullable":true,"description":"Mean prominence over the runs whose answer names the brand. Null, never 0, when none did."},"entitledEngineCount":{"type":"number","description":"Today's entitled model-engine count. Since 2 Oct 2026 it is not the denominator of the inspector's share, which is over the engines measured in the pinned scan (`engineCoverage`)."}}},"engines":{"type":"array","description":"Per-engine cells, ONE PER ENGINE THAT ANSWERED. A prompt no engine has answered yields an EMPTY array — not a cell full of nulls — so an empty array here is \"nothing ran\", never \"ran and found nothing\". Both states are exercised over HTTP.","items":{"type":"object","required":["engineKey","label","runs","mentions","ranked","rankMin","rankMax","avgProminence","accurate","accTotal","latest"],"properties":{"engineKey":{"type":"string"},"label":{"type":"string"},"runs":{"type":"number"},"mentions":{"type":"number"},"noTextRuns":{"type":"number","description":"Of this engine's `runs`, how many answers had no text (empty or whitespace only) and would otherwise read as an answer without a mention."},"ranked":{"type":"number","description":"Of `runs`, the runs whose answer names the brand AND places it in a list of options."},"rankMin":{"type":"number","nullable":true,"description":"The best position over the `ranked` runs. Null when `ranked` is 0."},"rankMax":{"type":"number","nullable":true,"description":"The worst position over the `ranked` runs. Null when `ranked` is 0."},"avgProminence":{"type":"number","nullable":true,"description":"Mean prominence over this engine's runs whose answer names the brand. Null, never 0, when none did."},"accurate":{"type":"number"},"accTotal":{"type":"number"},"latest":{"type":"object","nullable":true,"description":"The most recent run for this engine, null when the engine has none. Every field below is always present EXCEPT `observedSearch`, whose key is absent rather than null when the run carries no readable search capture.","required":["brandMentioned","capturedAt","citations","mentionRank","mentionTotal","prominence","rawAnswer","runIndex","sentiment","source","accuracyOk","accuracyDetail","accuracyUnverified"],"properties":{"brandMentioned":{"type":"boolean","description":"Whether the brand's NAME was read in this model answer. PURELY LEXICAL on this surface: a citation of the brand's own domain does NOT set it — that is `citations[].isOwned`. Beware the same word elsewhere in the product: on Google AI Overview and Google AI Mode, `brandMentioned` is documented as \"cited OR named\" and a citation alone sets it, so the two are NOT interchangeable and this one must not be compared with those. `false` has two readings and `mentionState` separates them: `not_mentioned` is a read answer with no name in it, `undecidable` is an answer whose name could not be read at all."},"capturedAt":{"type":"string","format":"date-time","description":"The run's capture moment, the instant this scan stored the answer. One scan per read is pinned upstream, so this is an exact moment and never a range across scans. For an answer this scan REUSED from the shared commons (`answerReused` true) it is the instant the scan took the answer, not when the answer was observed: read `observedAt`."},"observedAt":{"type":"string","format":"date-time","nullable":true,"description":"When the answer was observed, from its receipt: for a reused answer the instant it was first recorded in the shared commons (not when the provider generated it, which no provider reports, and never the reuse instant), otherwise the same instant as `capturedAt`. Null only for a reused answer whose original instant was not recorded; a date is never invented."},"answerReused":{"type":"boolean","nullable":true,"description":"True when this scan reused the answer from the shared commons, false when it asked the engine, null when no receipt records how the answer was obtained."},"citations":{"type":"array","description":"EVERY citation on the run, owned or not — the read does not filter them, which is why each row carries `isOwned` for the caller to split on. Empty when the run cited nothing.","items":{"type":"object","required":["domain","url","position","isOwned"],"properties":{"domain":{"type":"string"},"url":{"type":"string"},"position":{"type":"number","nullable":true,"description":"Null when the answer gave no orderable position."},"isOwned":{"type":"boolean","description":"Whether the cited domain is the brand's own."}}}},"mentionRank":{"type":"number","nullable":true,"description":"Null unless this answer names the brand (`brandMentioned` true), as are `mentionTotal` and `prominence`: none of the three is served beside an answer that does not name it."},"mentionTotal":{"type":"number","nullable":true},"prominence":{"type":"number","nullable":true},"noAnswerText":{"type":"boolean","description":"True when this answer has no text (empty or whitespace only) and would otherwise read as an answer without a mention. Such an answer establishes neither presence nor absence, so `brandMentioned: false` beside it is NOT an absence."},"rawAnswer":{"type":"string","nullable":true,"description":"NULL when the answer could not be stored. An unstorable answer is encoded rather than dropped, so a null here is a storage fact and not an empty answer. Served DECODED, not in its stored form."},"runIndex":{"type":"number"},"sentiment":{"type":"string","nullable":true},"source":{"type":"string"},"accuracyOk":{"type":"boolean","nullable":true,"description":"Null means NOT GRADED — never \"graded and wrong\"."},"accuracyDetail":{"type":"object","nullable":true,"description":"Present only for a contradiction that named a fact this read could map back; null otherwise, which is not evidence of accuracy.","required":["factCode","factClaim","said","correct"],"properties":{"factCode":{"type":"string","nullable":true},"factClaim":{"type":"string"},"said":{"type":"string","nullable":true},"correct":{"type":"string","nullable":true},"factId":{"type":"string","nullable":true,"description":"Id of the approved fact named. Absent on rows graded before 2026-10-02."},"factVersion":{"type":"number","nullable":true,"description":"Version of that fact the judge compared against; null when the fact has no version rows. Absent on rows graded before 2026-10-02."}}},"accuracyUnverified":{"type":"array","nullable":true,"items":{"type":"string"},"description":"Checkable brand claims no approved fact covers. NULL means NOT ASSESSED and an EMPTY ARRAY means assessed and none found — two different facts that must not be collapsed."},"observedSearch":{"description":"What this engine searched in order to answer. Read `state` first. The KEY IS ABSENT (never null) when the run has no readable capture.","oneOf":[{"type":"object","description":"The engine reported its search text.","required":["state","queries","reportedCount","searchCalls"],"properties":{"state":{"type":"string","enum":["reported_text"]},"queries":{"type":"array","items":{"type":"string"}},"reportedCount":{"type":"number","nullable":true},"searchCalls":{"type":"number","nullable":true}}},{"type":"object","description":"The engine reported that it searched, but not what — a count without text, and never an empty query list, which would read as a measured zero.","required":["state","reportedCount","searchCalls"],"properties":{"state":{"type":"string","enum":["reported_count_only"]},"reportedCount":{"type":"number","nullable":true},"searchCalls":{"type":"number","nullable":true}}},{"type":"object","description":"No capture to report, and the four states say WHY: the engine does not support it, did not report it, no longer retains it, or the row predates the capture.","required":["state"],"properties":{"state":{"type":"string","enum":["not_reported","not_supported","not_retained","legacy_unknown"]}}}]}}}}}},"observationEvidence":{"type":"object","required":["selectedScanId","measurementState","observations"],"description":"The observation receipt. All three fields are non-optional on `PromptMeasurementView`, and their presence is confirmed on the serialized response rather than inferred from that type.","properties":{"selectedScanId":{"type":"string","nullable":true,"description":"After expiry this comes from the retained receipt's stored scanId — a durable fact even once the Scan row is gone."},"measurementState":{"type":"string"},"observations":{"type":"array","items":{"type":"object","required":["engineKey","scanRunId","sampleOrdinal","evidenceAvailable","receiptRecorded"],"properties":{"engineKey":{"type":"string"},"scanRunId":{"type":"string"},"sampleOrdinal":{"type":"number"},"evidenceAvailable":{"type":"boolean","description":"Whether the evidence survives. A receipt can be RECORDED while its evidence has expired, which is why these two booleans are separate."},"receiptRecorded":{"type":"boolean"},"evidence":{"type":"object","description":"The retained payload. THE KEY IS ABSENT, never null, once the payload has expired — a missing key is a statement about TODAY and says nothing about whether the measurement succeeded, which `evidenceAvailable` and the measurement state carry instead.","required":["promptAsExecuted","rawAnswer","citations"],"properties":{"promptAsExecuted":{"type":"string","nullable":true},"rawAnswer":{"type":"string","nullable":true,"description":"Served decoded, as with the per-run answer."},"citations":{"type":"array","items":{"type":"string"},"description":"CITED URLS as plain strings — not the per-run citation objects, which carry a domain, a position and an ownership flag."}}}}}}}},"basis":{"type":"object","required":["kind"],"description":"Which scan this read is pinned to. `newest` is the ordinary live read: the newest scan holding runs for the prompt whose status is `complete` or `partial`, where `partial` is a scan STILL IN PROGRESS (stamped after every engine but the last, not a completeness word; `engineCoverage.scanStatus` and `engineCoverage.pending` say so), or, when the prompt has no such scan, the newest scan of any status that holds runs for it. `newest` with `requestedScanId` and `reason` is a STATED fallback from a requested scan that is not this brand's (`not_found`) or never ran this prompt (`no_rows_for_prompt`); `pinned_scan` names the scan the read was pinned to, its status and its creation time. `unavailable` (row 248) is a requested scan whose answers are NOT served and which no newer answer replaces: `expired` (the scan was removed by retention and a retained receipt names it) or `restricted` (the scan belongs to a different brand of the organisation). This operation cannot request a scan, so it answers `newest` with no request.","properties":{"kind":{"type":"string","enum":["newest","pinned_scan","unavailable"]},"requestedScanId":{"type":"string","description":"Only with `kind: newest` or `kind: unavailable` when a scan was requested and could not be pinned."},"reason":{"type":"string","enum":["not_found","no_rows_for_prompt","expired","restricted"],"description":"Only beside `requestedScanId`: `not_found` and `no_rows_for_prompt` with `kind: newest`, `expired` and `restricted` with `kind: unavailable`."},"scanId":{"type":"string","description":"Only with `kind: pinned_scan`."},"scanStatus":{"type":"string","description":"Only with `kind: pinned_scan`: the pinned scan's status as stored (for example complete, or partial for a scan still in progress)."},"scanCreatedAt":{"type":"string","format":"date-time","description":"Only with `kind: pinned_scan`."}}},"engineCoverage":{"type":"object","required":["measured","plan","cohort","pending","failed","scanStatus","basisVersion","perEngine"],"description":"The engine coverage of the scan this read is pinned to: how many engines answered in it, against today's plan and against the engine count the scan was created for.","properties":{"measured":{"type":"number","description":"Engines with an answer for this prompt in the pinned scan, the same engines `engines` lists. Never filtered by today's plan."},"plan":{"type":"number","description":"Today's entitled model-engine count: context for `measured`, not its denominator."},"cohort":{"type":"number","nullable":true,"description":"How many engines the pinned scan was created for, from the scan's own record and never clipped to today's plan, so a scan from before a plan change keeps its count. Null when no scan is pinned, or when the recorded count cannot be read as engines this view reads."},"pending":{"type":"number","nullable":true,"description":"Engines the scan was created for that have not answered yet while it is in progress; 0 once it has finished. Null when no scan is pinned or `cohort` is null."},"failed":{"type":"number","nullable":true,"description":"Engines the scan was created for that gave no answer, once it has finished. NULL while it is in progress: a failed engine writes no row, so it cannot be told from one not yet run until the scan ends. Also null when no scan is pinned or `cohort` is null."},"scanStatus":{"type":"string","nullable":true,"enum":["queued","running","partial","complete","failed",null],"description":"The pinned scan's stored status. `queued`, `running` and `partial` are IN PROGRESS; `complete` and `failed` have finished. Null when the read is pinned to no scan."},"basisVersion":{"type":"string","enum":["measured_engines_v1"],"description":"The counting contract. `measured_engines_v1`: the inspector's share is over the engines measured in this scan (since 2 Oct 2026); before that it divided by the plan's engine count, so earlier figures are not comparable."},"perEngine":{"type":"array","description":"Every model engine this view reads, with its state in the pinned scan. No state is carried forward from another scan: every answer in this payload comes from the pinned one.","items":{"type":"object","required":["engineKey","label","inPlan","observation"],"properties":{"engineKey":{"type":"string"},"label":{"type":"string"},"inPlan":{"type":"boolean","description":"Whether today's plan includes this engine. Independent of `observation`, which is never filtered by the plan."},"observation":{"type":"string","enum":["measured","pending","failed","not_in_scan","unattributed"],"description":"`measured`: this engine's answer is in the scan. `pending`: the scan is in progress, was created for this engine, and it has not answered yet. `failed`: the scan has finished, was created for this engine, and holds no answer from it. `not_in_scan`: the scan was not created for this engine. `unattributed`: no answer here, and the scan's record (a count of its engines, not a list) cannot say whether this engine failed, is pending, or was never in the scan. `pending`, `failed` and `not_in_scan` are stated only where the scan's engine count proves them."}}}}}}}}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"One prompt in full: engines, citations, competitors","tags":["public-api"]}},"/v1/public/brands/{brandId}/competitors":{"get":{"operationId":"PublicApiController_competitors","parameters":[{"name":"brandId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Competitor AI Presence. All thirteen top-level fields are declared. `recommendationRate` is removed from every competitor row — asserted directly in public-api-boundary-transforms.spec.ts, because \"contents unspecified except one forbidden field\" is not expressible in this validator's subset.","content":{"application/json":{"schema":{"type":"object","required":["you","category","competitors","roster","cap","suggestions","suggestionsBasis","suggestionDetails","basis","commonBasis","engineMatrix","provenance","note"],"properties":{"you":{"type":"object","required":["name","sov"],"description":"The reading brand's own row on the competitor scale. Two fields exactly — the producer builds it as `{ name, sov }` and carries no further detail here.","properties":{"name":{"type":"string"},"sov":{"type":"number","nullable":true}}},"category":{"type":"array","description":"The brand and its rivals on one scale, the brand itself flagged `me`.","items":{"type":"object","required":["name","sov","onBasis","share","mentions","me"],"properties":{"name":{"type":"string"},"sov":{"type":"number","nullable":true},"onBasis":{"type":"number","nullable":true,"description":"The mean on the COMMON basis, null when the row holds no qualifying cell — not a zero."},"share":{"type":"number","nullable":true},"mentions":{"type":"number"},"me":{"type":"boolean"}}}},"competitors":{"type":"array","description":"Ranked rival rows — only rivals holding at least one measurement appear, so an unmeasured rival is absent from this list while still present in `roster`. Never carries `recommendationRate`: that field was citationShare relabelled and is stripped at this boundary.","items":{"type":"object","required":["id","name","domains","domainType","sov","onBasis","comparison","notComparable","citationShare","citationTracked","topRankRate","topPositiveRate","topPositiveGraded","beatsYouOn","trend"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"domainType":{"type":"string","nullable":true,"description":"The primary domain's classified type; null when the rival has no domain."},"sov":{"type":"number","nullable":true,"description":"The rival's absolute presence over the entitled set. Present even when the pairwise reading is refused — the row stays, only the comparison goes."},"onBasis":{"type":"number","nullable":true,"description":"The rival's mean on the COMMON basis shared across every eligible rival, null when it holds no qualifying cell. A different quantity from `sov`, and never a zero."},"comparison":{"type":"object","nullable":true,"description":"You versus this rival on the engines you were BOTH measured on, in one occasion and under one contract. Null exactly when `notComparable` is set.","required":["engineBasis","you","them","basisComplete"],"properties":{"engineBasis":{"type":"array","items":{"type":"string"},"description":"The engines both sides were measured on, in entitled order. Never empty."},"you":{"type":"number"},"them":{"type":"number"},"basisComplete":{"type":"boolean","description":"True only when the pair basis is the WHOLE entitled set; false means the comparison covers the named subset."}}},"notComparable":{"type":"string","nullable":true,"enum":["no-entitled-engines","no-own-measurement","no-rival-measurement","no-shared-engine","no-shared-occasion",null],"description":"Why the comparison was withheld, null when one was composed. Each token names its OWN absence because a reader acts on them differently: `no-own-measurement` is a scan away, `no-shared-engine` is a plan or coverage question, and `no-shared-occasion` resolves itself on the next full scan. They are never merged."},"citationShare":{"type":"number","nullable":true},"citationTracked":{"type":"boolean","description":"False when the rival has no domain, so its citation share is not trackable rather than measured as zero."},"topRankRate":{"type":"number","nullable":true,"description":"Share of answers where this rival's first mention precedes every other tracked entity. Mention order ONLY — rival accuracy is not graded, so this is NOT a recommendation rate. Null when any entitled engine never recorded it."},"topPositiveRate":{"type":"number","nullable":true,"description":"The judge-graded-positive subset of topRankRate, so always <= it. Null when leads existed but none were graded — a 0 there would be a fabricated measured zero."},"topPositiveGraded":{"type":"number","nullable":true,"description":"How many leads were graded; null whenever topPositiveRate is."},"beatsYouOn":{"type":"number","description":"Count of entitled engines where the rival's presence exceeds the brand's."},"trend":{"type":"array","items":{"type":"number"},"description":"Up to the last eight per-occasion means, oldest first."}}}},"roster":{"type":"array","description":"Every tracked rival, measured or not — the roster is the list, not the ranking.","items":{"type":"object","required":["id","name","domains","domainType","battlecardNotes","source"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"domainType":{"type":"string","nullable":true},"battlecardNotes":{"type":"string","nullable":true},"source":{"type":"string"}}}},"suggestions":{"type":"array","items":{"type":"string"},"description":"Autocomplete names for the add box: the brand's business FOCUS's curated players plus the directory companies filed under that focus, minus the brand and anything already tracked, and nothing from any wider category (15 Oct 2026). PLAIN STRINGS, not objects. Empty when the brand has no focus or nobody relevant is known."},"suggestionsBasis":{"type":"string","enum":["focus","no_focus"],"description":"Why `suggestions` is what it is: `no_focus` (the brand has no business focus under its industry, so nobody is offered) or `focus` (built for the focus; empty means nobody relevant is known)."},"suggestionDetails":{"type":"array","description":"The same suggestions, each with the directory website and company it carries where the directory holds them, and where it came from.","items":{"type":"object","required":["name","domain","companyId","source"],"properties":{"name":{"type":"string"},"domain":{"type":"string","nullable":true},"companyId":{"type":"string","nullable":true},"source":{"type":"string","enum":["catalogue","directory"],"description":"`catalogue`: the focus's curated list; `directory`: a company-master row filed under the focus."}}}},"cap":{"type":"number"},"basis":{"type":"object","required":["engines"],"description":"The entitled engine count that every share, sum and ordering on this payload was computed over — one field, published so \"all engines\" can never be inferred.","properties":{"engines":{"type":"number"}}},"commonBasis":{"type":"object","required":["engines","complete","you","ranked","unmeasured","notComparable"],"description":"The shared basis a ranking may be read on. `ranked` false means the rows are NOT comparable on one scale, and `you` is null when the brand has no qualifying cell — neither is a zero.","properties":{"engines":{"type":"array","items":{"type":"string"},"description":"Engine keys the brand and every ELIGIBLE rival share, in entitled order. `CommonBasis.engineBasis` on the wire."},"complete":{"type":"boolean","description":"True when the shared basis is the WHOLE entitled set. `CommonBasis.basisComplete` on the wire."},"you":{"type":"number","nullable":true,"description":"The brand's mean on that basis, NULL when the basis is empty — not a zero."},"ranked":{"type":"boolean"},"unmeasured":{"type":"array","items":{"type":"string"},"description":"Rival IDS left out because they hold no measurement on any entitled engine."},"notComparable":{"type":"array","items":{"type":"string"},"description":"Rival IDS left out because they hold measurements but share no observation with the brand. A different exclusion from `unmeasured`, and the two are never merged."}}},"engineMatrix":{"type":"object","required":["engines","metrics","rows"],"properties":{"engines":{"type":"array","items":{"type":"object","required":["key","label","inPlan"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"inPlan":{"type":"boolean"}}}},"metrics":{"type":"array","items":{"type":"object","required":["key","label","unit"],"properties":{"key":{"type":"string"},"label":{"type":"string"},"unit":{"type":"string"}}}},"rows":{"type":"array","items":{"type":"object","required":["name","me","overall","perEngine"],"properties":{"name":{"type":"string"},"me":{"type":"boolean"},"overall":{"type":"object","required":["sov","mentions"],"description":"The row's totals across the basis. Two fields exactly. A rival with no measurement carries `mentions: 0` from a sum over nothing, so read it beside `perEngine` rather than as evidence of absence.","properties":{"sov":{"type":"number","nullable":true},"mentions":{"type":"number","nullable":true}}},"perEngine":{"type":"object","description":"A MAP keyed by engine id. Every value is `{ sov, mentions }`, both nullable because an engine the rival was never measured on has no figure rather than a zero. An unmeasured rival maps to an empty object.","additionalProperties":{"type":"object","required":["sov","mentions"],"properties":{"sov":{"type":"number","nullable":true},"mentions":{"type":"number","nullable":true}}}}}}}}},"provenance":{"type":"string"},"note":{"type":"string"}}}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"Competitor AI Presence for a brand","tags":["public-api"]}},"/v1/public/brands/{brandId}/opportunities":{"get":{"operationId":"PublicApiController_opportunities","parameters":[{"name":"brandId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Ranked visibility opportunities, one element per tracked keyword. Every row field is declared, `volumeRange` included — it is a two-number band, not an opaque one.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["id","text","cluster","intent","difficulty","searchVolume","volumeRange","keywordMarket","volumeMeasuredAt","volumeSource","serpPosition","opportunity","opportunityUnscored","factors","confidence","tracked"],"properties":{"id":{"type":"string","format":"uuid"},"text":{"type":"string"},"cluster":{"type":"string","nullable":true},"intent":{"type":"string"},"difficulty":{"type":"number","nullable":true,"description":"Keyword difficulty. ALWAYS null on this payload today: the producer emits a literal null rather than reconstructing a figure it no longer measures."},"searchVolume":{"type":"object","required":["value","provenance"],"properties":{"value":{"type":"number","nullable":true},"provenance":{"type":"string","description":"How the volume was obtained. `measured` is what makes it count toward confidence."}}},"volumeRange":{"type":"object","nullable":true,"description":"The banded Google Ads volume, `{ min, max }`. NULL when the volume is not Ads-sourced — an exact volume has no band — so null means not banded rather than a missing band.","required":["min","max"],"properties":{"min":{"type":"number"},"max":{"type":"number"}}},"keywordMarket":{"type":"object","description":"The market this row's volume was measured for (`keywordMarket`, null when never recorded) against the brand's current market (`brandMarket`, null when the brand has none on record). `state` is `same`, `differs`, `keyword-market-not-recorded` or `brand-market-not-recorded`; an unknown is never `same`. A `differs` row's figure is NOT evidence about the brand's current market.","required":["state","keywordMarket","brandMarket"],"properties":{"state":{"type":"string","enum":["same","differs","keyword-market-not-recorded","brand-market-not-recorded"]},"keywordMarket":{"type":"object","nullable":true,"required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"brandMarket":{"type":"object","nullable":true,"required":["country","name"],"properties":{"country":{"type":"string"},"name":{"type":"string"}}}}},"volumeMeasuredAt":{"type":"string","nullable":true,"description":"The UTC day the volume was measured (YYYY-MM-DD), null when no measurement is stamped. The only period a volume carries."},"volumeSource":{"type":"string","nullable":true},"serpPosition":{"type":"number","nullable":true},"opportunity":{"type":"number","nullable":true,"description":"The score. Null when the row could not be scored — `opportunityUnscored` names the reason, and a null here is never to be read as a zero score."},"opportunityUnscored":{"type":"string"},"factors":{"type":"object","required":["demand","headroom","intent","winnability"],"properties":{"demand":{"type":"number","nullable":true},"headroom":{"type":"number"},"intent":{"type":"number"},"winnability":{"type":"number"}}},"confidence":{"type":"string"},"tracked":{"type":"boolean"}}}}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"Ranked visibility opportunities for a brand","tags":["public-api"]}},"/v1/public/brands/{brandId}/performance":{"get":{"operationId":"PublicApiController_performance","parameters":[{"name":"brandId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Performance overview and recent movement. Partial contract: `recentMovement` rows are fully declared because this boundary shapes them; `overview` is a pass-through and is deliberately not described. `aiSovBefore`/`aiSovAfter` appear only when the outcome states a result.","content":{"application/json":{"schema":{"type":"object","required":["overview","recentMovement"],"properties":{"overview":{"type":"object","required":["contentTracked","measured","stabilising","aiSovBefore","aiSovAfter","movement"],"description":"The performance service's overview. `measured` and `stabilising` are counts of assets, not percentages: an asset re-measured too few times to settle is counted apart rather than averaged in.","properties":{"contentTracked":{"type":"number"},"measured":{"type":"number"},"stabilising":{"type":"number"},"notComparable":{"type":"number","description":"How many of `measured` are targeted assets whose before-side did not record how it was measured (\"Not comparable: measurement basis not recorded\"). They were re-measured, so they stay in `measured`, and their figures are in none of `aiSovBefore`, `aiSovAfter` and `movement`, which are means over the rest. Absent on a payload older than 24 Oct 2026."},"aiSovBefore":{"type":"object","required":["value","provenance","note"],"description":"Cohort mean BEFORE publishing. `value` is null when no asset has both a before and an after, or every one that does is counted in `notComparable`: genuinely absent, not a measured 0; a real 0 (the cohort exists and rounds to zero) is still 0.","properties":{"value":{"type":"number","nullable":true},"provenance":{"type":"string"},"note":{"type":"string"}}},"aiSovAfter":{"type":"object","required":["value","provenance","note"],"description":"Cohort mean AFTER publishing, on the same null-preserving contract as the before side. Three fields exactly — this is the overview's own aggregate, NOT the per-record reading elsewhere in the product that also ships sample counts and a settle time.","properties":{"value":{"type":"number","nullable":true},"provenance":{"type":"string"},"note":{"type":"string"}}},"movement":{"type":"object","required":["value","provenance","note"],"description":"After minus before, derived from the two DISPLAYED (rounded) figures so it always equals what the pair above shows — deriving it from the raw means can round to a number that visibly contradicts them. `value` is null when either side is absent: an absence of comparability, never a movement of zero.","properties":{"value":{"type":"number","nullable":true},"provenance":{"type":"string"},"note":{"type":"string"}}}}},"recentMovement":{"type":"array","items":{"type":"object","required":["contentId","title","publishedAt","targetQuery","rankBefore","rankAfter","outcome"],"properties":{"contentId":{"type":"string"},"title":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"targetQuery":{"type":"string","nullable":true},"rankBefore":{"type":"number","nullable":true},"rankAfter":{"type":"number","nullable":true},"outcome":{"description":"The canonical outcome verdict. Read `state` first: it selects the variant and says whether the aiSov pair is present.","oneOf":[{"type":"object","description":"Movement clearing the observed spread, upward.","required":["state","delta"],"properties":{"state":{"type":"string","enum":["observed_improvement"]},"delta":{"type":"number"}}},{"type":"object","description":"Movement clearing the observed spread, downward.","required":["state","delta"],"properties":{"state":{"type":"string","enum":["declined"]},"delta":{"type":"number"}}},{"type":"object","description":"A settled bracket whose delta does not clear the threshold. A RESULT, so the pair is present — and distinct from `measuring`, which is silence.","required":["state"],"properties":{"state":{"type":"string","enum":["no-measurable-change"]}}},{"type":"object","description":"Not enough folded samples yet. NOT a measured no-change, and the pair is withheld.","required":["state"],"properties":{"state":{"type":"string","enum":["measuring"]}}},{"type":"object","description":"No before-side reading exists, so there is nothing to compare against.","required":["state"],"properties":{"state":{"type":"string","enum":["no-baseline"]}}},{"type":"object","description":"A methodology seam makes the bracket meaningless, and `cause` names which one rather than leaving a withheld figure unexplained.","required":["state","cause"],"properties":{"state":{"type":"string","enum":["not-comparable"]},"cause":{"type":"string","description":"The token naming why the two ends cannot be subtracted: a model re-base (`model:*`), `outcome:no-baseline-basis`, `outcome:no-after-basis`, `outcome:capture-mode-changed`, `outcome:baseline-mixed-basis` (the baseline was captured while engines were on different bases), or `outcome:basis-not-recorded` (a targeted bracket whose before did not record how it was measured: \"Not comparable: measurement basis not recorded\", no further cause). The set is open: a consumer that meets a token it does not know treats the row as not comparable and shows no difference."}}}]},"aiSovBefore":{"type":"object","description":"The publish-time baseline. Three fields. Present only when the outcome states a result; the KEY IS ABSENT otherwise, never null.","required":["value","provenance","note"],"properties":{"value":{"type":"number"},"provenance":{"type":"string"},"note":{"type":"string","description":"Says where the baseline came from — a live publish's own fold, or a promoted draft whose reading can predate publication by days."}}},"aiSovAfter":{"type":"object","description":"The post-publish reading, with the sampling detail the before side has no equivalent of. Present only when the outcome states a result; the KEY IS ABSENT otherwise, never null.","required":["value","samples","min","max","stabilising","settleAt","provenance","note"],"properties":{"value":{"type":"number"},"samples":{"type":"number","description":"How many post-publish re-scans were folded into the mean."},"min":{"type":"number","nullable":true},"max":{"type":"number","nullable":true,"description":"With `min`, the observed spread — which sets the threshold a delta must clear to count as movement at all."},"stabilising":{"type":"boolean","description":"True while `samples` is below `settleAt`: the mean is not yet settled, so a single-draw after is not a final verdict."},"settleAt":{"type":"number","description":"The sample count at which the after mean is treated as settled, published so `stabilising` has a visible basis."},"provenance":{"type":"string"},"note":{"type":"string"}}}}}}}}}}},"401":{"description":"The token is absent, invalid, revoked, expired, or lacks the `read` scope."},"403":{"description":"A brand-pinned token addressed a different brand, or the organisation is gone."},"404":{"description":"The brand is not in this token's organisation."},"423":{"description":"The organisation is suspended or being deleted; reads are refused until it is active again."},"429":{"description":"The plan's monthly API allowance is exhausted. The rejected request is not counted; the allowance resets on the 1st."}},"security":[{"bearer":[]}],"summary":"Visibility trend and recent movement","tags":["public-api"]}}},"info":{"title":"Ansyra Public API","description":"REST access to your AI-search visibility data. The endpoints in this reference only read (GET). Authenticate with an API token (`Authorization: Bearer <token>`) minted in Settings → Connections. Every endpoint requires the token's `read` scope and is metered against your plan's monthly request quota (HTTP 429 over the cap). A token without the actions permission cannot run scans or create briefs, but it can still submit AI-crawler measurements to your Ansyra account through POST /v1/crawlers/ingest. That route is not in this reference.","version":"1.0.0","contact":{}},"tags":[],"servers":[{"url":"/v1","description":"This Ansyra instance"}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"API token","type":"http","description":"Your Ansyra API token"}},"schemas":{}}}