MetricsGo API — data contract

What every number served by /api/v1 and the MCP server means. The same text is returned by the describe_metrics MCP tool. Changing a definition is a breaking change for every consumer reading that field on a schedule — a new field is added instead.

Rules that apply everywhere

  • Meta ads and organic social days are calendar days in Asia/Kolkata (IST, UTC+5:30); a day runs 00:00:00–23:59:59.999 IST. The Google and Shopify surfaces keep the source platform's own day boundary: GA4 days are in the Analytics property's timezone, Search Console days are Pacific, and Shopify days are in the shop's timezone. Those three will not line up with each other or with IST, and none of them is wrong — each response reports the timezone of the assets behind it.
  • null is ABSENCE — the platform did not report the metric — and is never a zero. A genuine zero is 0.
  • Ratios are null when their denominator is zero: no CTR without impressions, no cost per result without results.
  • Numbers come from stored data synced by MetricsGo; meta.syncedAt on every response says how fresh they are. The API never calls Meta on your behalf.
  • Currency amounts are in the ad account's own currency (meta.currency), major units, unrounded.
  • A previous block and a compare range are resolved server-side from a named mode (previous period, previous month, previous year). The compare field on the response is the exact window those numbers cover.
  • Meta has retired several Facebook Page metrics (Page reach, Page impressions, post reach, post impressions). They are null for Facebook, permanently, and are documented as such rather than omitted.

Ads (ads:read)

Field Unit Definition null means
spend currency Amount spent in the range, summed from daily rows.
impressions count Times an ad was on screen, summed from daily rows.
reach count People who saw the ad at least once. Summed over daily rows, so across many days it is an upper bound — Meta de-duplicates reach within a single insights window, and daily rows are separate windows.
clicks count All clicks (Meta clicks), summed.
linkClicks count Clicks on links in the ad (Meta inline_link_clicks), summed.
results count The action Meta counts as this entity's result: the ad set's promoted_object (a custom conversion or standard pixel event) first, then the optimisation goal, then the campaign objective. Matches the Results column in Ads Manager for the same attribution setting. No result definition applies (e.g. account level, or an objective with no countable result).
resultType label The Meta action_type that results counts (e.g. offsite_conversion.custom., purchase, link_click). In the overview it is the ONE type every campaign in scope shares. In the overview, null with results > 0 means the range mixes result types (e.g. leads and video views); that total is a sum of unlike things and must not be compared across campaigns — narrow to a client or campaign. Elsewhere: no result definition.
ctr percent clicks / impressions × 100. No impressions.
cpc currency spend / clicks. No clicks.
costPerResult currency spend / results. No results, no result definition, or (overview) the range mixes result types — there is no single thing to price.
frequency ratio impressions / reach. No reach.
account label The ad account the row belongs to: { id, platformAccountId (Meta act_… id), name }. On every entity row and every overview campaign. Join key to /v1/clients. The account is no longer connected.
client label The MetricsGo client that account is assigned to: { id, name }. This is how a row is attributed to a client. When the account is not assigned to a client, name falls back to the account name and id is null. The account is no longer connected (same as account).
campaign label Parent campaign of an ad set or ad: { id, platformId (Meta campaign_id), name, objective }. Campaign-level rows have no parent campaign.
adSet label Parent ad set of an ad: { id, platformId (Meta adset_id), name }. Only ads have a parent ad set.
videoViews count 3-second video plays (Meta video_play_actions), summed.
purchaseValue currency Sum of purchase conversion value (Meta action_values for purchase). 0 when never reported.
previous count Totals for the period of the same length immediately before from, with the same entity scope. 0 where the previous period had no data for that metric.
funds.balance currency Available prepaid balance summed over scoped accounts; falls back to unbilled spend (balance due) for postpaid accounts. No scoped account reported a balance.
funds.cap currency Spend cap summed over scoped accounts that have one. No scoped account has a cap.
breakdowns.*.reach count Reach within one breakdown slice. NOT additive across slices — one person can appear in several.
stale label Breakdowns only. True when the dashboard would refetch this range from Meta; the numbers may lag until someone opens the entity in the dashboard.

Social (social:read)

Field Unit Definition null means
reach count Accounts reached, summed over daily rows in the range. Not reported. Permanently null for Facebook Pages (retired by Meta).
impressions count Retired by Meta for Facebook Pages and never served for Instagram accounts; kept for shape compatibility. Always null.
views count Instagram views (the metric that replaced impressions), summed.
profileViews count Instagram profile views, summed. Not reported; permanently null for Facebook.
interactions count Instagram total interactions / Facebook page post engagements, summed.
newFollows count New follows in the range, summed.
followers count Follower count at the latest day in the range that has a reading. Point-in-time, not a sum. No reading in the range.
engagementRate percent interactions / reach × 100. Either side missing, or reach is 0.
postTotals.newPosts count Posts published in the range (IST). Always a real count.
`postTotals.likes comments shares saves`
posts[].engagementRate percent (likes + comments + shares + saves) / reach × 100 for one post. Reach is null or 0.
posts[].thumbnailHref label URL of the stored thumbnail on this API, requires the same API key. No thumbnail stored.

Website — Google Analytics and Search Console (web:read)

Field Unit Definition null means
analytics label The Google Analytics block, with the properties behind it. Null means no Analytics property is attached to the clients in scope — "not connected", not "no traffic". No GA4 property attached.
sessions count GA4 sessions, summed over daily rows. The honest volume metric for a range.
totalUsers count Sum of GA4’s DAILY unique users. Somebody who visits on Monday and again on Friday is counted twice, so over a range this is an upper bound on distinct people, not a headcount.
activeUsers count Sum of GA4’s daily active users. Same daily-sum caveat as totalUsers.
newUsers count Users whose first session ever fell in the range, summed daily.
engagedSessions count Sessions GA4 counts as engaged (over 10 seconds, a key event, or 2+ pageviews), summed.
engagementRate ratio engagedSessions / sessions, 0–1 (not a percentage). Recomputed over the whole range, never an average of daily rates. No sessions.
bounceRate ratio 1 − engagementRate, 0–1. GA4 defines a bounce as a session that was not engaged — it is not the old Universal Analytics single-pageview bounce. No sessions.
averageSessionDuration ratio Seconds per session, weighted by sessions over the range (Σ(duration × sessions) / Σ sessions). No sessions.
screenPageViews count Page and screen views, summed.
eventCount count All GA4 events, summed.
keyEvents count Events marked as key events (conversions) in the property, summed. Which events count is the property owner’s configuration, not ours.
conversionRate ratio keyEvents / sessions, 0–1. This product’s one definition of website conversion rate. No sessions.
totalRevenue currency GA4 total revenue in the property’s currency (meta.currency), summed.
purchaseRevenue currency GA4 purchase revenue only, summed. Not reconciled with Shopify: GA4 attributes on its own day boundary and only sees what the tag fired.
transactions count GA4 transactions, summed. Will not equal Shopify orders — different timezone, different definition, and GA4 misses what the tag did not see.
revenuePerSession currency totalRevenue / sessions. No sessions.
`channels sources landingPages pages
organicLandingPages label Entry pages counting ORGANIC SEARCH sessions only. Not a subset of landingPages, whose rows include paid, social and direct traffic to the same page.
partial label True when GA4 collapsed part of at least one breakdown into its "(other)" row, so the tables do not add up to the totals. Cardinality limits in GA4, not missing data.
properties[].timezone label The Analytics property’s reporting timezone — the day boundary every GA4 number here uses. Not reported by the property.
search label The Search Console block. Null means no Search Console site is attached to the clients in scope. No Search Console site attached.
search.clicks count Clicks from Google search results to the site, summed over days.
search.impressions count Times a link to the site appeared in search results, summed over days.
search.ctr ratio clicks / impressions, 0–1. Recomputed over the range. No impressions.
search.position ratio Average position: a RANK, so LOWER IS BETTER — 4.1 beats 8.4. Weighted by impressions (Σ(position × impressions) / Σ impressions), never a plain average: a query seen 10,000 times at rank 3 and one seen twice at rank 90 do not average to 46.5. No impressions.
search.positionWeighted ratio Σ(position × impressions), exposed so a consumer can re-weight a subset of rows correctly instead of averaging positions.
search.queries label Top queries by clicks. These rows DO NOT SUM to the site totals: Google withholds queries too few people searched, so query clicks and impressions are always less than the site total. The gap is expected and is not missing data.
`search.pages countries devices appearances`
search.searchType label Which Search Console surface the numbers cover: web (default), image, video, news, discover or googleNews. Types are separate indexes and do not add up to a grand total.
sites[].firstIncompleteDate label The first day from which Google’s own data for this site is still settling — numbers on and after it can still rise. Google reported nothing pending.
search.days label Search Console days are PACIFIC time, unlike GA4 (the property’s timezone) and Shopify (the shop’s). The three sources will not line up day for day.

Store — Shopify (store:read)

Field Unit Definition null means
store label The Shopify block, with the connected shops behind it. Null means no store is connected for the clients in scope. No Shopify store connected.
grossSales currency Product price × quantity, BEFORE tax, shipping and discounts, and INCLUDING cancelled, pending and unpaid orders. Shopify’s own definition; it is not "money received".
discounts currency Discounts applied in the range, as a positive number subtracted from gross sales.
returns currency Refunded value, DATED ON THE REFUND rather than on the original order. A refund can push a day’s net sales negative and never reduces the day the order was placed.
netSales currency grossSales − discounts − returns. Still EXCLUDES shipping and tax.
`shipping taxes duties tips`
totalSales currency netSales + shipping + taxes + duties + tips. The bottom line of Shopify’s own finance summary.
orders count Orders placed in the range, including cancelled and unpaid ones (consistent with grossSales).
cancelledOrders count Orders cancelled, counted on the order’s own day. Included in orders, not subtracted from it.
refundedOrders count Orders with a refund, counted on the refund’s day — the same dating rule as returns.
unitsSold count Line-item quantity summed over orders in the range.
`customers newCustomers returningCustomers` count
averageOrderValue currency netSales / orders, recomputed over the whole range. Never an average of daily averages — a Tuesday with two orders must not weigh as much as a Saturday with two hundred. No orders.
averageUnitsPerOrder ratio unitsSold / orders. No orders.
returnRate ratio returns / grossSales, 0–1. Because returns are dated on the refund, a short range can show a rate above 1. No gross sales.
discountRate ratio discounts / grossSales, 0–1. No gross sales.
newCustomerRate ratio newCustomers / customers, 0–1. No customers.
revenuePerCustomer currency netSales / customers. No customers.
`products channels referrers countries
sessions count DOES NOT EXIST. Shopify’s Admin API reports no sessions and no store conversion rate, so neither is served here. Say the data is unavailable rather than deriving one from ad clicks or GA4 sessions — those measure different populations. Never present; the field is absent entirely.
stores[].timezone label The shop’s own reporting timezone — the day boundary every Shopify number here uses. It will not match GA4 (the property’s) or Search Console (Pacific). Not reported by the shop.
hasAllOrdersScope label False means Shopify granted only the last 60 days of order history for this shop, so earlier days are genuinely absent rather than zero.