ChangelogPricing
Log in

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 / ga4 are null when that integration isn't connected for the brand (visibility is still returned).
  • ga4.sessionsBySource is 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 per query with 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 /gsc analog. 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, default us) and ?language= (full locale, default en-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 in indexStatus. null when 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? }. indexed is tri-state: true (Bing reports a crawl date, lastCrawl, the only per-URL index signal Bing exposes), false (a confident not-indexed verdict, reason e.g. Not in Bing index, Discovered, not yet crawled), or null (unknown: the live lookup was rate-limited or failed, reason lookup throttled / lookup failed). Never treat null as not-indexed, it is a transient artifact, not a gap. indexedCount / notIndexedCount / unknownCount cover the returned page and count only their own verdict; count is the full sitemap size. Paginate with ?page (default 1) and ?limit (default 25, max 100). Use it to find non-indexed live URLs, then submit them via /bing/submit. null when 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": []
}

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>" }.

StatusMeaning
401Missing/invalid API key (or API not configured).
404unknown brand - not found, or outside your key's scope.
409ambiguous brand, use id - a domain/name matched more than one brand.
500internal error.