The same numbers as the dashboard. Each engine's score is the percentage of the brand's tracked prompts in which that engine named the brand in the latest weekly cycle.
AI answers move from week to week even when nothing real changed. Each score therefore comes with a drift band built from its trailing history, and isSignificant is true only when the score leaves that band. Build alerts on isSignificant, not on raw score changes.
Scores update once a week, when the brand's cycle runs. Polling more often returns the same data.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
brandId | path | string (UUID) | A brand id from GET /api/v1/brands. |
Request
curl https://corecited.com/api/v1/brands/7d1c2b9e-4a3f-4e61-9b2a-0c5d8e7f6a14/visibility \
-H "Authorization: Bearer $CORECITED_API_KEY"// Node 18+ (global fetch). Run server-side: the key must never reach a browser.
const res = await fetch("https://corecited.com/api/v1/brands/7d1c2b9e-4a3f-4e61-9b2a-0c5d8e7f6a14/visibility", {
headers: { Authorization: `Bearer ${process.env.CORECITED_API_KEY}` },
});
const body = await res.json();
if (!res.ok) {
// Branch on body.error.code, never on the message text.
throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);
}
const { data } = body;
console.log(data);import os
import requests
res = requests.get(
"https://corecited.com/api/v1/brands/7d1c2b9e-4a3f-4e61-9b2a-0c5d8e7f6a14/visibility",
headers={"Authorization": f"Bearer {os.environ['CORECITED_API_KEY']}"},
timeout=30,
)
body = res.json()
if not res.ok:
# Branch on body["error"]["code"], never on the message text.
raise RuntimeError(f"{res.status_code} {body['error']['code']}: {body['error']['message']}")
data = body["data"]
print(data)Response
200 OK with the fields below inside data. Numbers are JSON numbers, dates are strings, and a field listed as null-able is always present, holding null when there is no value.
| Field | Type | Description |
|---|---|---|
brand | object | The brand this response is about. |
brand.id | string | The brand's id. Use it in every /brands/{brandId}/… path. |
brand.name | string | The brand name as configured in CoreCited. |
brand.domain | string | The brand's website exactly as entered during setup. This is often a full URL such as https://www.example.com/, not a bare host. |
overallVisibility | number | The mean of the latest per-engine scores, one decimal. 0 when nothing has run yet. |
perEngine | object[] | The latest score for each engine that has one. |
perEngine[].slug | string | Engine slug: chatgpt, claude, gemini, perplexity, ai_overview, ai_mode, deepseek, grok. Which ones appear depends on the plan and on what has run. |
perEngine[].isWebGrounded | boolean | True when the engine searches the web before answering (Perplexity, Google's AI surfaces). Only these return citations. |
perEngine[].score | number | Visibility in the latest cycle: the percentage of this brand's tracked prompts in which the engine named the brand. 0–100, one decimal. |
perEngine[].date | string | The cycle this score belongs to. YYYY-MM-DD. |
perEngine[].bandLow | number | null | Lower edge of the normal drift band, in percentage points. null while warming up. |
perEngine[].bandHigh | number | null | Upper edge of the normal drift band, in percentage points. null while warming up. |
perEngine[].isSignificant | boolean | True when this score falls OUTSIDE the drift band, a real change rather than ordinary week-to-week noise. Always false while warming up. |
perEngine[].isWarmingUp | boolean | True until 4 prior weekly scores exist for this engine. Until then there is no band and nothing is flagged. |
perEngine[].noAnswerRate | number | null | Percentage of recent runs where the surface served no answer at all (common for Google AI Overviews). Those runs are excluded from the score. null when it never happened. |
trend | object (map) | Score history keyed by engine slug, oldest point first, one point per engine per day. Drawn from the 120 most recent stored scores across all engines combined, so with more engines each series is shorter. |
trend.{slug}[].date | string | Cycle date, YYYY-MM-DD. |
trend.{slug}[].score | number | Visibility that cycle, 0–100. |
alerts | object[] | Engines whose latest score is a significant change (isSignificant = true). Empty in a normal week. |
alerts[].slug | string | Engine slug. |
alerts[].score | number | The score that fell outside its band. |
signals | object | Sentiment and position of the brand across successful runs in the last 14 days. |
signals.netSentiment | number | null | Net sentiment, −100 to +100: (positive − negative) ÷ all brand mentions × 100, rounded. null with no mentions. |
signals.positive | number | Brand mentions classified positive. |
signals.neutral | number | Brand mentions classified neutral. |
signals.negative | number | Brand mentions classified negative. |
signals.avgPosition | number | null | Mean position of the brand in answers that name it (1 = named first). Lower is better. null with no mentions. |
signals.mentions | number | Brand mentions counted. |
Example
{
"data": {
"brand": {
"id": "7d1c2b9e-4a3f-4e61-9b2a-0c5d8e7f6a14",
"name": "Acme Resume",
"domain": "https://www.acmeresume.com/"
},
"overallVisibility": 44,
"perEngine": [
{
"slug": "chatgpt",
"isWebGrounded": false,
"score": 58,
"date": "2026-09-21",
"bandLow": 31.2,
"bandHigh": 52.8,
"isSignificant": true,
"isWarmingUp": false,
"noAnswerRate": null
},
{
"slug": "ai_overview",
"isWebGrounded": true,
"score": 30,
"date": "2026-09-21",
"bandLow": null,
"bandHigh": null,
"isSignificant": false,
"isWarmingUp": true,
"noAnswerRate": 40
}
],
"trend": {
"chatgpt": [
{
"date": "2026-09-14",
"score": 40
},
{
"date": "2026-09-21",
"score": 58
}
],
"ai_overview": [
{
"date": "2026-09-21",
"score": 30
}
]
},
"alerts": [
{
"slug": "chatgpt",
"score": 58
}
],
"signals": {
"netSentiment": 53,
"positive": 9,
"neutral": 5,
"negative": 1,
"avgPosition": 2.4,
"mentions": 15
}
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | missing_key | No key was sent in Authorization or x-api-key. |
| 401 | invalid_key | The key is malformed, unknown, or has been revoked. |
| 402 | plan_required | The workspace is not on a plan that includes API access. This is checked on every request, so it also happens after a downgrade. |
| 404 | not_found | The brand id is not a UUID, does not exist, or belongs to another workspace. These cases are deliberately indistinguishable. |
| 429 | rate_limited | The workspace used up its requests for the current minute. All keys share one limit. |
| 500 | internal | Something failed on our side. The response carries no detail by design. |
Every error has the same body. See Errors for what to do about each one.
