Skip to main content

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:

ParameterInDescription
uuidpathThe domain's UUID (its id from GET /v1/domains).
start_datequeryStart of the window, as a YYYY-MM-DD date string.
end_datequeryEnd of the window, as a YYYY-MM-DD date string.
devicequeryWhich 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. visibility is the share-of-voice figure for your domain, and change is 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. dates is an ordered array of YYYY-MM-DD strings, and results is 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 with dates: results["www.example.com"][0] is the visibility on dates[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't YYYY-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 no RateLimit-* headers yet, so retry with backoff on a 503.

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.results map.