Skip to main content
GET /api/v2/buyer/reporting/metrics Returns delivery metrics rolled up across the advertiser → campaign → media buy → package tree. Choose a hierarchical summary or flat timeseries view, scope with advertiserId, campaignId, channelGroupId, or mediaBuyId, and set the window with days or an explicit startDate/endDate. This endpoint is day-grain only — for hourly counts use Get event summary.

Request

Parameters

Response

The example below uses illustrative, non-zero conversionValue, cpa, and roas values to show the response shape. See the note below the response for how these fields behave when a seller has not reported conversion data.
Each campaign block (and each timeseries row) carries a management state: tracked (a campaign you did not set up through the platform, mirrored read-only from a connected provider account) or managed (authored or adopted through the platform). This surface currently reports managed campaigns only — tracked mirror delivery is excluded — so totals never silently mix the two states. conversionValue is advertiser-attributed revenue from conversions; unlike spend, it is never grossed up with platform fees. cpa (spend / conversions) is null when there are no conversions. roas (conversionValue / spend) is null when spend is zero, or when there is no conversion signal at all — zero conversions and zero attributed value; it reports a true 0 only when conversions are real but their attributed value is zero. These fields depend on the seller reporting conversion data. Where a seller reports none, conversionValue is 0 and cpa and roas are null.

Goal progress

Every media buy block, and every campaign block, that has something to judge against carries goalProgress: delivery compared with the buyer’s goal and the seller’s commitment. A media buy is judged against its own goal commitment; a campaign against its primary goal, with the weakest commitment across its buys. The campaign’s answeredTarget is likewise the weakest answer: the highest cost per unit, or the lowest rate or return. It is null when the buys answered in different kinds. Blocks with neither a goal nor a commitment omit the field. The timeseries view carries the same judgements, over the whole period, in a goalProgress object with mediaBuys[] and campaigns[]. What each part means and how to read it is explained in Goal-seeking campaigns. The rows below give the media buy block’s path; the campaign block carries the same object at advertisers[].campaigns[].goalProgress. A media buy committed at 3.00 per click that has delivered 500 clicks for 2,000 reads actual: { kind: "cost_per", value: 4, unit: "clicks", units: 500 } and verdict: "behind". The same buy on its first day with two clicks reads the actual and verdictWithheld: "too_few_observations", with no verdict.
A viewable_rate goal is judged only from the viewable and measurable impression counts in the AdCP viewability object. Reporting does not read that object yet, so a viewable_rate goal currently reads verdictWithheld: "metric_not_reported" with viewability in missingMetrics. The views metric is content views — the quantity CPV pricing bills on — and is never used as a stand-in for viewable impressions.

Spend pace

goalProgress.pace answers a separate question from the goal: is the money moving at the rate the flight implies? “Spend 200 a day at 80% viewable” is two answers, not one — the pace verdict and the goal verdict are reported side by side. Pace is not spend ÷ budget: a buy three days into a thirty-day flight that has spent a tenth of its budget is exactly on pace, while a buy on its last day that has spent the same tenth is nine tenths undelivered.
Pace is always measured over the window you asked for. Narrow days and both actualSpend and expectedSpend shrink together, so the verdict stays comparable; it never compares a week of spend with a month of expectation.
When download=true, the response is instead { downloadUrl, expiresAt, fileName, rowCount } with a signed URL that expires in 7 days. Treat that URL as a bearer credential.

Channel-group lineage

When a campaign uses channel groups, reporting carries the group that compiled into each media buy:
  • A summary response returns advertisers[].campaigns[].mediaBuys[].channelGroup as { channelGroupId, name }.
  • A timeseries response returns channelGroupId and channelGroupName on every row.
  • A CSV export appends Channel Group ID and Channel Group Name columns.
Legacy or ungrouped media buys return channelGroup: null in a summary, channelGroupId: null and channelGroupName: null in time-series JSON, and blank CSV fields. These values are saved media-buy lineage; Interchange does not infer them from names, products, or delivery. This REST endpoint accepts channelGroupId as a scope filter, but it does not group rows by that field. For an agent-side rollup, call V3 get_delivery with report: "campaign_delivery", dimensions: ["channel_group"], and, when you need one group, filters.channelGroupId. Rows then return the selected dimension as channelGroup: { id, name }, or null for an ungrouped buy. When you pass filters.mediaBuyId, get_delivery returns delivery for that media buy only; it does not include other media buys from the same storefront.

Errors

  • 400 VALIDATION_ERROR — malformed startDate/endDate, days outside 0–90, or a campaignId that does not belong to the given advertiserId.
  • 422 SPEND_DENOMINATION_UNRESOLVED — one or more media buys in scope have spend that cannot be denominated, so no figure would be correct. The error names them in its message and in details.mediaBuyIds, alongside details.unresolvedCount. Classified terminal — do not retry. The cause is delivery data that is wrong at rest (a source reporting a currency it is not paid in, a buy missing its cross-currency booking evidence, or currencies mixed within the period), so every retry reproduces it. Report the named ids to support; to keep reporting in the meantime, scope the request to exclude them (a single campaign, or a period they did not deliver in). Unaffected buys report normally, and view=timeseries can still return rows for buys whose days each carry their own currency.
  • 503 FX_RATE_UNAVAILABLE — the exchange-rate feed could not price a currency pair. Classified transient; retry shortly.
See Errors for the full error contract, and Cross-currency for how delivery spend is denominated.

Reporting tasks

All reporting operations

Reporting overview guide

Hierarchy, metrics, CSV export, delivery flow

Get event summary

Hourly event counts by type