REST API Reference
The RankZero REST API (/api/v1) - pull per-brand visibility, GSC, and GA4 KPIs server-to-server.
The RankZero REST API exposes the same data as our MCP server over plain HTTP+JSON, so automations and integrations can pull per-brand KPIs server-to-server without speaking MCP.
Base URL
https://www.rankzero.io/api/v1(Same origin as the RankZero app.)
Authentication
Every request must send a bearer key:
Authorization: Bearer <YOUR_API_KEY>Keys are issued on request. A key is scoped to one account - it can only read that account's brands (brands the account owns or shares via its organization). There is no all-brands access.
Missing or invalid key → 401.
curl https://www.rankzero.io/api/v1/brands \
-H "Authorization: Bearer $RANKZERO_API_KEY"Addressing a brand
The canonical identifier is the brand's id (a stable UUID). Discover your brands and their ids with GET /brands. As a convenience you may also pass a brand's domain or name:
- Matches exactly one brand → resolves.
- Matches more than one (e.g. two brands on the same domain) →
409 {"error":"ambiguous brand, use id"}. - Matches none (or a brand outside your scope) →
404 {"error":"unknown brand"}.
Reporting period
KPI endpoints accept ?period=, one of 24h, 7d, 30d, 90d, 6m, 12m, 16m. Default 7d.
The long windows (6m, 12m, 16m) are intended for the aggregate Search Console trend - 16m is the full retention Google Search Console keeps. They are reliable for totals-over-time; a dimensioned GSC breakdown (?dimensions=query|page) still caps at 1000 rows per request, so over long spans it truncates rather than paginating. GSC data also lands with a ~2-3 day lag, so the trailing few days of any window are incomplete.
Endpoints
GET /brands
Lists the brands your key can access.
curl https://www.rankzero.io/api/v1/brands \
-H "Authorization: Bearer $RANKZERO_API_KEY"[
{ "id": "3f8a1c2e-9b4d-4e7a-bc1f-2d6e5a0b9c11", "name": "Tesla", "domain": "tesla.com" }
]GET /brands/{brand}/kpis
Headline snapshot: visibility, Google Search Console totals, and GA4 sessions by source.
curl "https://www.rankzero.io/api/v1/brands/tesla.com/kpis?period=7d" \
-H "Authorization: Bearer $RANKZERO_API_KEY"{
"brand": "Tesla",
"period": "7d",
"asOf": "2026-06-08T12:00:00.000Z",
"visibility": { "percent": 11.4, "rank": 8 },
"gsc": { "clicks": 31, "impressions": 1606, "avgPosition": 12.4, "ctr": 0.019 },
"ga4": {
"sessionsBySource": [ { "source": "chatgpt.com", "sessions": 3 } ],
"totalSessions": 42
}
}gsc/ga4arenullwhen that integration isn't connected for the brand (visibility is still returned).ga4.sessionsBySourceis unfiltered - every traffic source is returned so you can apply your own AI-referrer allowlist.
GET /brands/{brand}/overview
Own-brand visibility plus the full industry ranking (competitors).
{
"brand": "Tesla",
"period": "7d",
"asOf": "2026-06-08T12:00:00.000Z",
"visibility": { "percent": 11.4, "rank": 8 },
"ranking": [
{ "name": "Tesla", "url": "tesla.com", "isOwnBrand": true, "rank": 8, "visibilityPercent": 11.4, "sentiment": 62 }
]
}GET /brands/{brand}/gsc
Google Search Console totals for the period.
{
"brand": "Tesla",
"period": "7d",
"asOf": "2026-06-08T12:00:00.000Z",
"gsc": { "clicks": 31, "impressions": 1606, "avgPosition": 12.4, "ctr": 0.019 }
}Breakdown by dimension. Add ?dimensions= (comma-separated; one or more of query, page, date, country, device, searchAppearance) to get per-row performance instead of totals - the raw material for opportunity scans (near-miss queries at position 11–20, high-impression/low-CTR pages). Optional ?limit (default 1000). Each row carries the requested dimension(s) plus clicks, impressions, ctr, position. rows is null if GSC isn't connected.
curl "https://www.rankzero.io/api/v1/brands/tesla.com/gsc?dimensions=query&period=30d&limit=1000" \
-H "Authorization: Bearer $RANKZERO_API_KEY"{
"brand": "Tesla",
"period": "30d",
"asOf": "2026-06-08T12:00:00.000Z",
"dimensions": ["query"],
"rows": [
{ "query": "electric suv range", "clicks": 12, "impressions": 480, "ctr": 0.025, "position": 13.4 }
]
}GET /brands/{brand}/ga4
GA4 sessions broken down by source (unfiltered).
{
"brand": "Tesla",
"period": "7d",
"asOf": "2026-06-08T12:00:00.000Z",
"ga4": {
"sessionsBySource": [ { "source": "chatgpt.com", "sessions": 3 } ],
"totalSessions": 42
}
}GET /brands/{brand}/competitors
The brand's tracked competitors.
{
"brand": "Tesla",
"asOf": "2026-06-08T12:00:00.000Z",
"competitors": [
{ "id": "…", "name": "Rivian", "url": "https://rivian.com", "isOwnBrand": false }
]
}GET /brands/{brand}/citations
Sources cited in AI answers, with content-type categorization. Accepts ?period, ?limit (default 50), ?country, ?provider (chatgpt|gemini|perplexity|anthropic|google_ai_overview).
{
"brand": "Tesla",
"period": "30d",
"asOf": "2026-06-08T12:00:00.000Z",
"sources": [
{
"source_url": "https://www.youtube.com/watch",
"page_title": "…",
"domain": "youtube.com",
"usage_pct": 22.22,
"avg_citations": 1,
"prompt_texts": ["best electric SUV"],
"content_type": "video"
}
]
}GET /brands/{brand}/mentions
Recent prompt runs that mention the brand / its competitors. Accepts ?period, ?limit (default 50).
{
"brand": "Tesla",
"period": "7d",
"asOf": "2026-06-08T12:00:00.000Z",
"mentions": [
{
"run_id": "…",
"prompt_text": "best electric SUV",
"model": "chatgpt",
"output_preview": "…",
"brands_mentioned_name": ["Tesla", "Rivian"]
}
]
}GET /brands/{brand}/prompts
The brand's active tracked prompts - the ones being monitored (paginated, 50/page). Accepts ?page, ?search. Distinct from the suggestion queue below.
source records where each prompt came from: manual, ai_generated, own_brand, or a suggestion_* origin (suggestion_gsc, suggestion_bing, suggestion_paa, suggestion_generated, suggestion_rankzero, suggestion_perplexity) when it was promoted from the suggestion queue. intents / tags are the prompt's classification (may be null).
{
"brand": "Tesla",
"asOf": "2026-06-08T12:00:00.000Z",
"page": 1,
"pageSize": 50,
"count": 15,
"prompts": [
{
"id": "…",
"text": "best electric SUV",
"country": "United States",
"isActive": true,
"source": "suggestion_bing",
"intents": ["commercial"],
"tags": null,
"createdAt": "…"
}
]
}GET /brands/{brand}/prompt-suggestions
The suggestion queue - questions you could add to tracking, distinct from the tracked prompts above. Accepts ?type, ?status (single value or comma-separated).
This queue spans every engine: suggestionType identifies where each came from - direct_conversion_gsc / direct_conversion_bing (queries the site converts on, per engine), related_keywords_bing (Bing keyword research), and PAA / generated / Perplexity variations. source.impressions (and clicks/ctr/position where the engine returns them) carry the demand behind each. So Bing keyword suggestions come out here, alongside GSC and the rest - filter to Bing with ?type=direct_conversion_bing,related_keywords_bing.
{
"brand": "Tesla",
"asOf": "2026-06-08T12:00:00.000Z",
"count": 12,
"suggestions": [
{
"id": "…",
"promptText": "which electric SUV has the longest range",
"suggestionType": "direct_conversion_bing",
"status": "pending",
"anchorQuery": "electric SUV range",
"confidenceScore": 0.82,
"sourceQuery": "longest range electric suv",
"country": "United States",
"source": { "clicks": 12, "impressions": 480, "ctr": 0.025, "position": 8.3 },
"createdAt": "…"
}
]
}GET /brands/{brand}/bing
The brand's Bing Webmaster integration - the Bing counterpart to /gsc, plus the crawl/index health GSC exposes per URL. Bing is the demand signal AI assistants ground on (Copilot and ChatGPT search lean on it), so everything Bing lives here:
queries- the site's Bing query performance, rolled up to one row perquerywith the same shape as/gsc's query breakdown (clicks,impressions,ctr,position; clicks + impressions summed, CTR derived, position averaged weighted by impressions), sorted by impressions. The demand the site already earns - Bing's/gscanalog. Fetched live; present only when a Bing site is connected.related- keyword research. Populated only when you pass?seed=<keyword>: a live Bing expansion of the seed into related keywords with impression volume (sorted by volume), surfacing demand the site doesn't yet rank for. Optional?country=(lowercase alpha-2, defaultus) and?language=(full locale, defaulten-US); the volume window is the trailing 30 days. Empty without a seed, or when not connected.crawlStats: the freshest daily crawl breakdown, fetched live:inIndex,crawledPages,code2xx/code4xx/code5xx,blockedByRobotsTxt,containsMalware,crawlErrors,date.inIndex(pages in Bing's index) is the aggregate index count; per-URL coverage is inindexStatus.nullwhen not connected or Bing returns no data.crawlIssues- one row per (url, issue):{ url, label, severity }(404s, blocked, malware, redirects…). Fetched live; empty when clean or not connected.indexStatus: live per-URL index coverage. RankZero takes the brand's sitemap URLs and inspects a page of them against Bing's index, returning one row per URL:{ url, indexed, lastCrawl?, reason? }.indexedis tri-state:true(Bing reports a crawl date,lastCrawl, the only per-URL index signal Bing exposes),false(a confident not-indexed verdict,reasone.g.Not in Bing index,Discovered, not yet crawled), ornull(unknown: the live lookup was rate-limited or failed,reasonlookup throttled/lookup failed). Never treatnullas not-indexed, it is a transient artifact, not a gap.indexedCount/notIndexedCount/unknownCountcover the returned page and count only their own verdict;countis the full sitemap size. Paginate with?page(default1) and?limit(default25, max100). Use it to find non-indexed live URLs, then submit them via/bing/submit.nullwhen not connected.
Bing keyword suggestions are not here - they live in /prompt-suggestions alongside every other engine (filter with ?type=direct_conversion_bing,related_keywords_bing).
Not connected → connected: false, empty/null live fields, 200 - never an error. seed echoes the requested seed (null if none).
# The brand's Bing integration (query impressions + crawl/index health)
curl "https://www.rankzero.io/api/v1/brands/tesla.com/bing" \
-H "Authorization: Bearer $RANKZERO_API_KEY"
# ...plus live keyword research expanded from a seed
curl "https://www.rankzero.io/api/v1/brands/tesla.com/bing?seed=electric%20suv&country=us&language=en-US" \
-H "Authorization: Bearer $RANKZERO_API_KEY"
# ...page through per-URL index coverage
curl "https://www.rankzero.io/api/v1/brands/tesla.com/bing?page=1&limit=100" \
-H "Authorization: Bearer $RANKZERO_API_KEY"{
"brand": "Tesla",
"asOf": "2026-06-08T12:00:00.000Z",
"connected": true,
"siteUrl": "https://tesla.com/",
"seed": "electric suv",
"queries": [
{ "query": "electric suv range", "clicks": 34, "impressions": 1240, "ctr": 0.027, "position": 6.8 }
],
"related": [
{ "query": "best electric suv 2026", "impressions": 8400 },
{ "query": "electric suv with longest range", "impressions": 3100 }
],
"crawlStats": {
"inIndex": 4120,
"crawledPages": 5300,
"code2xx": 5100,
"code4xx": 140,
"code5xx": 12,
"blockedByRobotsTxt": 48,
"containsMalware": 0,
"crawlErrors": 152,
"date": "2024-06-07T00:00:00.000Z"
},
"crawlIssues": [
{ "url": "https://tesla.com/old", "label": "Client error (4xx)", "severity": "error" }
],
"indexStatus": {
"asOf": "2026-06-08T12:00:00.000Z",
"page": 1,
"pageSize": 25,
"count": 5300,
"indexedCount": 22,
"notIndexedCount": 2,
"unknownCount": 1,
"urls": [
{ "url": "https://tesla.com/model-y", "indexed": true, "lastCrawl": "2026-06-07T04:12:00.000Z" },
{ "url": "https://tesla.com/new-page", "indexed": false, "reason": "Not in Bing index" },
{ "url": "https://tesla.com/model-s", "indexed": null, "reason": "lookup throttled" }
]
}
}POST /brands/{brand}/bing/submit
Submit live URLs to Bing for (re)crawl through the brand's existing Bing Webmaster connection, the IndexNow equivalent, with no per-site {key}.txt files. Pair it with /bing's indexStatus: page through to find indexed: false URLs, then submit them here.
Body: { "urls": string[] } (http(s) URLs; duplicates and malformed entries are dropped into errors). Auth and brand resolution are identical to the other /bing routes.
Respects Bing's remaining daily submission quota: URLs beyond it are not sent and come back in errors as daily submit quota exhausted. Response:
submitted: count actually sent to Bing.quotaRemaining: daily quota left after this call (omitted when Bing doesn't report a quota).errors: one{ url?, error }per dropped/failed URL (invalid input, quota, or a Bing error message).
A brand with no Bing connection returns a clean 409 { "error": "brand not connected to Bing" }. An empty/invalid body or no valid URLs returns 400.
curl -X POST "https://www.rankzero.io/api/v1/brands/tesla.com/bing/submit" \
-H "Authorization: Bearer $RANKZERO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "urls": ["https://tesla.com/new-page", "https://tesla.com/model-y"] }'{
"brand": "Tesla",
"asOf": "2026-06-08T12:00:00.000Z",
"submitted": 2,
"quotaRemaining": 8,
"errors": []
}GET /search
Searches the RankZero knowledge base (docs, glossary, blog). Not brand-scoped. Requires ?query (?q also accepted); optional ?locale.
{
"query": "visibility",
"results": [
{ "id": "/docs/metrics/visibility", "type": "page", "url": "/docs/metrics/visibility", "content": "…" }
]
}Errors
Errors are flat JSON: { "error": "<message>" }.
| Status | Meaning |
|---|---|
401 | Missing/invalid API key (or API not configured). |
404 | unknown brand - not found, or outside your key's scope. |
409 | ambiguous brand, use id - a domain/name matched more than one brand. |
500 | internal error. |