Build a share-of-voice report
Share of voice (SoV) is a single visibility figure that answers "how prominent is this domain across the keywords I track?" — and, crucially, how that prominence trends over time and compares against the competitors you've added.
In this tutorial you'll call GET /v1/domains/{uuid}/share-of-voice to pull the
overall figure and the day-by-day chart series, use
GET /v1/domains/{uuid}/history for context, and turn the response into a small
report that prints the headline number, its change, and a visibility-vs-time
trend. We'll build it up with curl first, then a short Python and Node.js
script.
:::note What you'll need
An API key (see the Quickstart and
Authentication guide) and the UUID of a domain
you already track. List your domains with GET /v1/domains and copy the id of
the one you want to report on. All examples use the production base URL
https://api.ranktracker.com, and every endpoint lives under /v1.
:::
The request
The endpoint takes the domain UUID in the path and three optional query parameters:
| Parameter | In | Description |
|---|---|---|
uuid | path | The domain's UUID (its id from GET /v1/domains). |
start_date | query | Start of the window, as a YYYY-MM-DD date string. |
end_date | query | End of the window, as a YYYY-MM-DD date string. |
device | query | Which device segment to report on: all, desktop, or mobile. Defaults to all. |
:::warning Date format and range
start_date and end_date are plain calendar dates (2026-06-01), not
timestamps. The window may span at most 365 days; a wider range is rejected.
Omit both to fall back to the endpoint's default window.
:::
Here's the base request in curl. Send your key in the Authorization header
with no Bearer prefix:
curl "https://api.ranktracker.com/v1/domains/3f2a9c8e-1d4b-4a7f-9e2c-8b1a2c3d4e5f/share-of-voice?start_date=2026-06-01&end_date=2026-06-30&device=all" \
-H "Authorization: tkn_usr_your_api_key_here"
:::tip URL-encode the query string
The ? and & separators must reach the server intact, so quote the whole URL
(as above) or let your HTTP client build the query string for you — as the
Python and Node.js examples below do.
:::
The response envelope
A 200 OK returns the JSON:API envelope with everything under data. Unlike
the list endpoints, data here is a single object with three sections —
overall, chart, and searchEngines:
{
"data": {
"overall": {
"visibility": 42,
"change": 3
},
"chart": {
"dates": ["2026-06-01", "2026-06-02", "2026-06-03"],
"results": {
"www.example.com": [39, 41, 42],
"www.competitor-a.com": [55, 54, 51],
"www.competitor-b.com": [12, 13, 13]
}
},
"searchEngines": []
}
}
What each section holds:
overall— the headline for the window.visibilityis the share-of-voice figure for your domain, andchangeis how much it moved across the window (a positive number means visibility rose). These are the two numbers most reports lead with.chart— the time series behind the headline.datesis an ordered array ofYYYY-MM-DDstrings, andresultsis a map keyed by host — your domain plus every competitor you track on it. Each value is an array of visibility figures aligned position-for-position withdates:results["www.example.com"][0]is the visibility ondates[0]. This is exactly the shape you need to plot "visibility vs competitors over time".searchEngines— a per-search-engine breakdown of the same figures. It may be empty depending on the domain's configuration; treat it as optional and don't assume any given engine is present.
:::note Visibility is relative
The visibility numbers are comparable within one response — your domain
against the competitors in the same results map, over the same window. Don't
read a single value as an absolute score; the story is in the gap between hosts
and the movement across dates.
:::
Adding history for context
Share of voice tells you how visible the domain is; the domain history endpoint tells you why — the underlying ranking distribution, traffic, and keyword counts on each date. It's a useful companion when a SoV move needs explaining.
GET /v1/domains/{uuid}/history takes the same start_date / end_date
YYYY-MM-DD parameters (also capped at a 365-day window) and returns a
pre-computed series under data — one object per date:
curl "https://api.ranktracker.com/v1/domains/3f2a9c8e-1d4b-4a7f-9e2c-8b1a2c3d4e5f/history?start_date=2026-06-01&end_date=2026-06-30" \
-H "Authorization: tkn_usr_your_api_key_here"
{
"data": [
{
"date": "2026-06-30",
"visibility": 42.0,
"trackedKeywords": 120,
"traffic": 3480,
"organicOne": 8,
"organicTwo": 5,
"organicThreeFive": 14,
"organicUnranked": 22
}
]
}
Each history row carries far more than shown here (the full ranking buckets like
organicSixTen, authority metrics, backlink counts, and so on) — see the
Domains reference for every field. For a SoV report,
the handy ones are date, visibility, trackedKeywords, and traffic: join
them to your chart series on date to annotate a visibility dip with, say, a
drop in top-3 rankings.
Turning it into a report
The chart section is already report-shaped: one date axis, one series per host. The scripts below fetch share of voice, print the headline figure and its change, then walk the series to show the first-to-last trend for every domain in the response.
Python
import requests
BASE = "https://api.ranktracker.com/v1"
DOMAIN_UUID = "3f2a9c8e-1d4b-4a7f-9e2c-8b1a2c3d4e5f"
HEADERS = {"Authorization": "tkn_usr_your_api_key_here"}
resp = requests.get(
f"{BASE}/domains/{DOMAIN_UUID}/share-of-voice",
headers=HEADERS,
params={
"start_date": "2026-06-01",
"end_date": "2026-06-30",
"device": "all",
},
)
resp.raise_for_status()
data = resp.json()["data"]
overall = data["overall"]
sign = "+" if overall["change"] >= 0 else ""
print(f"Overall share of voice: {overall['visibility']} ({sign}{overall['change']})")
dates = data["chart"]["dates"]
print(f"Window: {dates[0]} -> {dates[-1]} ({len(dates)} days)\n")
# First-to-last trend for each domain in the response.
for host, series in data["chart"]["results"].items():
start, end = series[0], series[-1]
delta = end - start
arrow = "up" if delta > 0 else "down" if delta < 0 else "flat"
print(f"{host:<28} {start:>4} -> {end:>4} ({arrow} {delta:+d})")
Running it prints the headline, the window, and a one-line trend per host:
Overall share of voice: 42 (+3)
Window: 2026-06-01 -> 2026-06-30 (30 days)
www.example.com 39 -> 42 (up +3)
www.competitor-a.com 55 -> 51 (down -4)
www.competitor-b.com 12 -> 13 (up +1)
Node.js
const BASE = "https://api.ranktracker.com/v1";
const DOMAIN_UUID = "3f2a9c8e-1d4b-4a7f-9e2c-8b1a2c3d4e5f";
const HEADERS = { Authorization: "tkn_usr_your_api_key_here" };
const params = new URLSearchParams({
start_date: "2026-06-01",
end_date: "2026-06-30",
device: "all",
});
const resp = await fetch(
`${BASE}/domains/${DOMAIN_UUID}/share-of-voice?${params}`,
{ headers: HEADERS },
);
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const { data } = await resp.json();
const { visibility, change } = data.overall;
const sign = change >= 0 ? "+" : "";
console.log(`Overall share of voice: ${visibility} (${sign}${change})`);
const { dates, results } = data.chart;
console.log(`Window: ${dates[0]} -> ${dates.at(-1)} (${dates.length} days)\n`);
for (const [host, series] of Object.entries(results)) {
const start = series[0];
const end = series.at(-1);
const delta = end - start;
const trend = delta > 0 ? "up" : delta < 0 ? "down" : "flat";
const d = delta >= 0 ? `+${delta}` : `${delta}`;
console.log(`${host.padEnd(28)} ${start} -> ${end} (${trend} ${d})`);
}
From here, feeding chart.dates and each chart.results series into a plotting
library or spreadsheet gives you the classic visibility-vs-competitors line
chart; the overall block is your report's headline stat.
Handling errors
Wrap the calls in the usual checks — the share-of-voice and history endpoints share the API's standard failure modes:
400— a required parameter is missing or malformed (for example a date that isn'tYYYY-MM-DD, or a window wider than 365 days).403— the key is missing or invalid, or your plan doesn't have API access enabled.404— the domain UUID doesn't exist or isn't in your account (foreign domains are hidden as "not found").503— the request was throttled. There are noRateLimit-*headers yet, so retry with backoff on a503.
Errors come back in the JSON:API error envelope:
{
"errors": [
{ "code": "bad_request", "status": 400, "detail": "start_date must be YYYY-MM-DD" }
]
}
See the Errors & rate limits guide for the full status-code list and a ready-made retry pattern.
Next steps
- Core concepts — the resource model and the JSON:API envelope this report is built on.
- Usage & quota — reporting reads don't consume keyword or data-row quota, but it's worth knowing your plan limits before you scale up polling.
- Domains reference — every field on the share-of-voice and history responses, plus the rest of the domain endpoints.
- Competitors reference — add or remove the
competitors that show up as hosts in your
chart.resultsmap.