Resource reference
Every v1 REST resource — request contract, response fields, types, and scales — for brands, overview, prompts, competitors, sources, urls, answers, shopping, and crawler ingest.
All read resources share one request contract; the ingest resource is documented separately at the end. Read Conventions first — it defines the scales, the fixed 7-day window, and null handling referenced throughout.
Shared request contract (read resources)
- Method / path:
GET /api/v1/{resource} - Auth:
Authorization: Bearer <key>(see Authentication). - Query params:
brand=<brandId>— optional; defaults to your first brand. There are no date parameters; the window is fixed at 7 days. The list resources also take optionalcursor/limitpagination params (additive, W5) — see Limits; a request without them gets the exact pre-pagination response. - Response: JSON. Metric fields carry the scale conventions; nullable fields are
null, never0, when empty. Every/api/v1and/api/mcpresponse carries theX-MF-API-Versionheader — see the Changelog.
The valid resources are: brands, overview, prompts, competitors, sources, urls, answers, shopping, shopping-trend.
Two additive endpoints sit outside the fixed 7-day contract: GET /api/v1/metrics/history and GET /api/v1/crawlers take window parameters.
GET /api/v1/brands
Lists the brands your key can see. Takes no brand param.
{ "brands": [ { "id": "…", "name": "…", "domain": "…", "archived": false } ] }
archived (additive) is true for a brand whose collection was stopped from Brand hub → Danger Zone. Archived brands never leave this list, and every read resource keeps answering for them — the flag tells you the data stops at the archive date; it doesn't hide it.
GET /api/v1/overview
The visibility scorecard for one brand over the 7-day window.
| Field | Type | Meaning |
|---|---|---|
brand | {id,name,domain} | the tracked brand |
window_days | int | 7 |
run_count | int | blended runs in the window |
visibility | number 0–100 | rounded to 0.1 |
presence_rate | number 0–1 | rounded to 0.001 |
share_of_voice | number 0–100 | deprecated, frozen — percentage, rounded to 0.1 |
share_of_voice_fraction | number 0–1 | additive — same number on the fraction rule, rounded to 0.001 |
citation_share | number 0–1 | null | unrounded |
sentiment_index | number 0–100 | null | 50 = neutral |
avg_position | number | null | mean 1-based rank |
low_confidence | bool | true when run_count < 5 |
has_prior_window | bool | whether a prior 7-day window has runs |
GET /api/v1/prompts
{ brand, prompts: [...] } — all active prompts for the brand.
| Field | Type | Meaning |
|---|---|---|
id | string | prompt id |
text | string | the prompt |
stage | string | funnel stage label, or — |
topic | string | null | frozen legacy field — the first tag |
topic_id | string | null | topic folder id (additive) |
topic_name | string | null | topic folder name (additive) |
tags | string[] | tags |
est_volume | number | null | legacy point volume estimate |
est_volume_band | [low,high] | null | demand band (AI asks/mo) |
volume_confidence | string | null | confidence tier |
matched_gsc_query | string | null | GSC query behind a measured tier |
gsc_impressions_28d | number | null | that query's 28-day site impressions |
presence_rate | number 0–1 | rounded to 0.001 |
visibility | number 0–100 | rounded to integer |
avg_position | number | null | mean 1-based rank |
topic is frozen (it stays the first tag); folders are exposed additively as topic_id/topic_name — see Conventions.
GET /api/v1/competitors
{ brand, competitors: [...] } — one row per tracked entity, including your own brand (flagged is_brand).
| Field | Type | Meaning |
|---|---|---|
id | string | entity id |
name | string | entity name |
domain | string | "" if the competitor has none |
is_brand | bool | true for your brand's row |
visibility | number 0–100 | rounded to 0.1 |
share_of_voice | number 0–100 | deprecated, frozen — percentage, rounded to 0.1 |
share_of_voice_fraction | number 0–1 | additive — same number on the fraction rule, rounded to 0.001 |
citation_share | number 0–1 | null | unrounded |
sentiment_index | number 0–100 | null | — |
avg_position | number | null | mean 1-based rank |
Every metric here, share of voice included, blends the eight search-grounded engines (see Conventions).
GET /api/v1/sources
{ brand, sources: [...] } — the top 20 cited domains, by citations descending.
| Field | Type | Meaning |
|---|---|---|
domain | string | cited domain |
classification | string | owned / competitor / editorial / ugc / … — the effective label; a workspace reclassification (Sources → Type) is already merged in |
overridden | bool | true when classification is a workspace reclassification rather than the automatic label |
citations | int | total citations to this domain in the window |
brand_present | bool | brand mentioned in ≥1 run citing this domain |
GET /api/v1/urls
{ brand, urls: [...] } — the top 50 cited URLs across all domains (content keys: YouTube watch?v= spellings and Reddit permalink variants merge; elsewhere only fragments and tracking parameters — utm_*/ref/fbclid/gclid — are stripped, so query-keyed content like item?id= threads stays distinct), by peak-window citations.
| Field | Type | Meaning |
|---|---|---|
url | string | canonical cited URL |
domain | string | its domain |
title | string | null | page title when an engine reported one |
classification | string | the domain's effective classification (reclassifications merged) |
overridden | bool | true when the classification is a workspace reclassification |
citations | int | citations in the current 7-day window (0 = cited in the prior window only) |
prior_citations | int | null | citations in the prior 7 days; null = no prior window observed yet, not zero |
answers | int | distinct answers citing it in the current window |
engines | string[] | engines citing it |
brand_present | bool | brand mentioned in ≥1 answer citing this URL |
page_type | string | null | deterministic page type from the URL path + stored title: listicle / comparison / howto / review / docs / forum / news / product; null = no recognizable signal (unclassified, never a guessed default) |
GET /api/v1/answers
{ brand, answers: [...] } — up to 200 runs, newest first. Includes all engines (not just blended).
| Field | Type | Meaning |
|---|---|---|
id | string | run id |
date | string YYYY-MM-DD | collection date |
region | string | e.g. us |
engine | string | one of the ten engine ids |
model | string | model version ("" if unknown) |
prompt_id | string | source prompt |
brand_present | bool | brand mentioned in this run |
mentions | [{name,order,sentiment}] | order 1-based; sentiment positive/neutral/negative/null |
citations | [{domain,url}] | cited sources |
GET /api/v1/metrics/history
Daily time series for one metric — the same series the dashboard charts, additive (W5). Unlike the resources above, it takes window parameters.
Query params:
| Param | Values | Meaning |
|---|---|---|
brand | brand id | optional; defaults to your first brand |
metric | visibility | share_of_voice_fraction | citation_share | sentiment_index | required |
days | 7 | 14 | 28 | 56 | preset window (default 28), anchored at the newest collected day; 56 is API-only (the dashboard range control tops out at 28) and spans the whole live run horizon, so its trend is always null |
from, to | YYYY-MM-DD | custom inclusive range (instead of days; max 366 days) |
engine | engine id | single-engine series; default blends the search-grounded engines (see Conventions) |
competitors | true | include tracked-competitor series alongside the brand |
Response envelope:
| Field | Type | Meaning |
|---|---|---|
brand | {id,name,domain} | — |
metric / unit | string | unit is score_0_100, fraction_0_1, or index_0_100 |
window | {start,end,days} | resolved bounds; start/end null when nothing was ever collected in scope |
engine | string | null | echo of the filter |
engine_scope | string | blended_search_grounded or single_engine |
source | string | runs (live-computed, last 56 days — the dashboard's exact code path) or daily_stats (nightly aggregate, older custom ranges) |
metrics_version | int | current metric-formula version |
series | array | one entry per entity (below) |
note | string | null | honesty caveats (single-day window, empty scope, trend suppressed at the run horizon, preset axis clamped to the horizon, daily_stats limits) |
Each series[] entry:
| Field | Type | Meaning |
|---|---|---|
id / name / is_brand | — | entity identity |
points | [{date,value,sample_size}] | one point per calendar day of the window; value null + sample_size 0 = nothing collected that day (null is not zero); value null with sample_size > 0 = collected but unmeasurable (e.g. no run cited any source) |
window_value | number | null | the metric over the whole window (same math as the dashboard KPI); null on daily_stats source |
window_run_count | int | runs behind window_value |
low_confidence | bool | window_run_count < 5 |
trend | object | null | {previous_window_value, delta, delta_unit} vs the prior same-length window; null for single-day windows, on daily_stats source, and whenever the prior window would fall outside the 56-day live run horizon (days=56 presets, custom ranges starting less than days after the horizon) — a truncated baseline is never compared, note says so |
Scales: visibility and sentiment_index are 0–100 rounded to 0.1; fraction metrics are 0–1 rounded to 0.001. delta_unit is relative_pct for visibility, fraction_points / index_points for the others. On source: daily_stats, blended share_of_voice_fraction/citation_share/sentiment_index are not derivable from the stored per-engine aggregates — the endpoint returns 400 suggesting engine= rather than a fabricated blend.
Also available as the get_metrics_history MCP tool.
GET /api/v1/crawlers
Agent analytics over the API (additive, W5): which AI agents read the brand's own site, from its connected crawler logs, plus each agent's robots.txt verdict — the same data as the /crawlers dashboard and its per-agent drill-down. Plan-gated like that page (Growth and above): a Starter/Trial workspace key gets 403. The operator key is exempt.
Query params: brand (as everywhere) and days — 7 (default), 14, or 28.
Top level:
| Field | Type | Meaning |
|---|---|---|
brand | {id,name,domain} | — |
window_days | int | echo of days |
connected | bool | false = no crawler logs have EVER been received for this brand |
last_event_at | string | null | newest event on record, unwindowed; null = never connected |
robots_fetch_state | ok | absent | error | absent = no robots.txt (a real finding: nothing blocked); error = couldn't check — verdicts are null, not a green light |
totals | {hits, pages} | window totals; null when connected is false (unknown, never a fabricated zero) |
agents | array | every known AI agent + anything the logs carried (below), by hits descending |
note | string | null | honesty caveats: never-connected, quiet pipe (nothing from ANY agent in 7 days — zero hits may be missing data), robots fetch failure |
Each agents[] entry:
| Field | Type | Meaning |
|---|---|---|
agent | string | stable key (e.g. gptbot) |
label / operator | string | display name and the company running it |
purpose | training | search_index | user_fetch | blocking search_index/user_fetch agents removes you from answers, not just training |
engine | string | null | the product engine this crawler feeds, when it maps cleanly |
hits / pages | int | null | window activity; null when no logs were ever connected — unknown, not zero |
first_seen / last_seen | string | null | window-scoped timestamps; null when this agent has no hits |
robots | {verdict, rule, blocked_path} | null | this agent's robots.txt verdict (allowed/blocked/unaddressed); null when the fetch failed — "couldn't check", never reassurance |
Also available as the get_crawler_analytics MCP tool.
GET /api/v1/shopping
The shopping leaderboard over the 7-day window. Built from the same computation as the dashboard's shopping analytics. Metrics blend every collecting engine; the per-engine split is the channel_coverage block.
Top level:
| Field | Type | Meaning |
|---|---|---|
brand | {id,name,domain} | — |
window_days | int | 7 |
totals.carousels | int | distinct carousels captured |
totals.products | int | distinct products |
totals.own_appearances | int | own-product placements |
totals.own_avg_position | number | null | null = own never appeared |
products | array | up to 30 (below) |
channel_coverage | array | per-engine shopping capability (additive; below) |
note | string | null | absence explainer (see Conventions) |
Each channel_coverage[] entry describes one shopping-capable engine:
| Field | Type | Meaning |
|---|---|---|
engine | string | engine id (chatgpt, aio, aimode, copilot, perplexity, gemini) |
status | string | parses-today — the engine's payloads carry parseable product data; awaiting-payload — extraction is wired but the data vendor delivers no product fields yet |
sampled | bool | the engine collected runs in this window |
carousels | int | null | carousels observed this window; null exactly when unobservable — the engine wasn't sampled, or the payload is awaiting the vendor. Null is not zero: a 0 means a sampled, parsing engine showed no carousel |
Engines with no shopping surface (Claude, DeepSeek, Grok, Mistral) are not listed. See Engine coverage for the product-side view of the same map.
Each products[] entry:
| Field | Type | Meaning |
|---|---|---|
product | string | product name |
merchant_domain | string | null | derived from the product link only |
merchant | string | null | verbatim engine merchant text (usually null) |
price | string | null | the engine's verbatim price text; null when the engine sent none |
price_amount | number | null | exact amount — taken from the payload's structured price fields when the engine provides them, else parsed once from price; null = unknown, never guessed |
currency | string | null | currency of price_amount; null when the payload didn't pin one |
url | string | null | product link |
is_own | bool | your own product |
appearances | int | total placements |
carousels_featuring | int | distinct carousels featuring it |
win_rate | number 0–1 | share of its appearances in slot #1 |
visibility_share | number 0–1 | carousels featuring it ÷ window carousels |
avg_position | number | mean slot (1 = front of shelf) |
Price fields are independent: some engines send only structured numbers, so price can be null while price_amount and currency are set. When you need a display price, format it from price_amount + currency (that is how the dashboard renders one); a fully-null trio means the engine sent nothing.
GET /api/v1/shopping-trend
{ brand, window_days, days_observed, trend: [...], note }. days_observed counts collected days; days without carousels produce no point.
Each trend[] point:
| Field | Type | Meaning |
|---|---|---|
date | string YYYY-MM-DD | — |
carousels | int | carousels captured that day |
own_carousel_share | number 0–1 | that day's carousels featuring an own product |
own_win_share | number 0–1 | that day's carousels with an own product in slot #1 |
own_avg_position | number | null | null on days you weren't shelved |
note flags a single-day snapshot; check days_observed >= 2 before charting a line.
POST /api/v1/crawler-events
The one write endpoint — ingests AI-crawler logs. Reachable by full and ingest keys (see Crawler ingest).
- Path:
POST /api/v1/crawler-events?brand=<id>(brandoptional; defaults to first). - Body: raw text — nginx/apache combined log lines, or NDJSON (Cloudflare Logpush / Vercel Log Drains). Up to 10 MB per request. Only recognised AI agents are stored, paths only.
- Success
200:
{ "inserted": 0, "matched": 0, "totalLines": 0, "unparsed": 0 }
| Field | Meaning |
|---|---|
inserted | rows actually written (duplicates skipped) |
matched | recognised AI-crawler hits |
totalLines | lines that parsed as requests |
unparsed | lines that couldn't be parsed |
Related
- MCP server — the same reads as agent tools.
- Conventions · Limits · Errors.