GET/api/v1/brands/{brandId}/competitors
Share of voice is estimated from a finite number of answers, so every percentage comes with a 95% confidence interval.
When two intervals overlap, the gap between those names is noise. tiedWithBrand and leaders say this directly, so you don't have to compute it. Do not present a ranking closer than noiseFloorPp as a real ranking.
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/competitors \
-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/competitors", {
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/competitors",
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. |
shareOfVoice | object[] | Your brand and each competitor, most mentions first. |
shareOfVoice[].name | string | Your brand or a tracked competitor. |
shareOfVoice[].isBrand | boolean | True for your own brand's row. |
shareOfVoice[].mentions | number | Times this name was mentioned in successful runs over the last 14 days. |
shareOfVoice[].sovPct | number | Share of voice: this name's mentions ÷ all mentions counted, as a percentage. |
shareOfVoice[].lowPct | number | Lower bound of the 95% Wilson confidence interval for sovPct. |
shareOfVoice[].highPct | number | Upper bound of the 95% Wilson confidence interval for sovPct. |
shareOfVoice[].halfWidthPp | number | Half the interval's width, in percentage points. |
shareOfVoice[].tiedWithBrand | boolean | True when this row's interval overlaps yours, so the gap between you is noise. Always false on your own row. |
shareOfVoice[].tiedForLead | boolean | True when this row is statistically tied with the top row. |
totalMentions | number | All mentions counted, the denominator of sovPct. |
noiseFloorPp | number | The smallest gap in percentage points that means anything at this sample size. Do not rank names closer than this. |
leaders | string[] | Names statistically tied for the lead. More than one means there is no real #1. |
Example
200 OK
{
"data": {
"brand": {
"id": "7d1c2b9e-4a3f-4e61-9b2a-0c5d8e7f6a14",
"name": "Acme Resume",
"domain": "https://www.acmeresume.com/"
},
"shareOfVoice": [
{
"name": "Zety",
"isBrand": false,
"mentions": 41,
"sovPct": 43.2,
"lowPct": 33.7,
"highPct": 53.2,
"halfWidthPp": 9.8,
"tiedWithBrand": false,
"tiedForLead": true
},
{
"name": "Enhancv",
"isBrand": false,
"mentions": 33,
"sovPct": 34.7,
"lowPct": 25.9,
"highPct": 44.7,
"halfWidthPp": 9.4,
"tiedWithBrand": false,
"tiedForLead": true
},
{
"name": "Acme Resume",
"isBrand": true,
"mentions": 12,
"sovPct": 12.6,
"lowPct": 7.4,
"highPct": 20.8,
"halfWidthPp": 6.7,
"tiedWithBrand": false,
"tiedForLead": false
},
{
"name": "Kickresume",
"isBrand": false,
"mentions": 9,
"sovPct": 9.5,
"lowPct": 5.1,
"highPct": 17,
"halfWidthPp": 6,
"tiedWithBrand": true,
"tiedForLead": false
}
],
"totalMentions": 95,
"noiseFloorPp": 19.8,
"leaders": [
"Zety",
"Enhancv"
]
}
}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.
