Skip to main content

Documentation Index

Fetch the complete documentation index at: https://agenticadvertisingorg-changeset-release-main.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Retrieve comprehensive delivery metrics and performance data for media buy reporting. Response Time: ~60 seconds (reporting query)

Scope

get_media_buy_delivery works on any media_buy_id returned by get_media_buys, regardless of how the underlying campaign was created. Sales agents MUST NOT refuse delivery reporting — or narrow its coverage — on the basis that the buy originated outside AdCP. If delivery data for a buy is genuinely unavailable (e.g., the ad server has not yet reported a flight), the seller returns the buy in media_buy_deliveries with zero or partial metrics; the seller does not omit it and does not return MEDIA_BUY_NOT_FOUND for an account-owned buy. Request Schema: /schemas/v3/media-buy/get-media-buy-delivery-request.json Response Schema: /schemas/v3/media-buy/get-media-buy-delivery-response.json

Request Parameters

ParameterTypeRequiredDescription
accountaccount-refNoAccount reference. Pass { "account_id": "..." } or { "brand": {...}, "operator": "..." } if the seller supports implicit resolution. Only returns media buys belonging to this account. When omitted, returns data across all accessible accounts.
media_buy_idsstring[]No*Array of media buy IDs to retrieve
status_filterstring | string[]NoStatus filter: "pending_creatives", "pending_start", "active", "paused", "completed". Defaults to ["active"] when omitted.
start_datestringNoReport start date (YYYY-MM-DD), inclusive. Omit for campaign lifetime data. Only accepted when product supports date_range.
end_datestringNoReport end date (YYYY-MM-DD), exclusive. Omit for campaign lifetime data. Only accepted when product supports date_range.
reporting_dimensionsobjectNoRequest dimensional breakdowns within by_package. Include a key as an empty object (e.g., "device_type": {}) to activate with defaults. Keys: geo, device_type, device_platform, audience, placement. Each accepts optional limit (defaults to 25 for geo, audience, placement) and sort_by (sort-metric enum, default: spend). Geo requires geo_level (one per request) and system for metro/postal levels. Unsupported dimensions are silently omitted; malformed requests return a validation error.
time_granularitystringNoPer-window slice granularity for pull recovery, matching reporting_webhook.reporting_frequency vocabulary (hourly, daily, monthly). When set, the response includes windows[] slices shape-aligned with webhook fires at the same granularity. Capability-scoped — value MUST be in the product’s reporting_capabilities.windowed_pull_granularities. See Windowed pull recovery.
include_window_breakdownbooleanNoWhen true (and time_granularity is set), include the windows[] array on each media buy. Defaults to false. Ignored when time_granularity is omitted.
Date Range Behavior: The date range is start-inclusive, end-exclusive. For example, start_date: "2026-01-01" and end_date: "2026-01-02" returns delivery data for January 1st only (from 2026-01-01 00:00:00 up to, but not including, 2026-01-02 00:00:00). To get a full week of data (Jan 1-7), use end_date: "2026-01-08".
Date Range Examples:
start_dateend_dateData Returned
2026-01-012026-01-02January 1st only (1 day)
2026-01-012026-01-08January 1st through 7th (7 days)
2026-01-012026-02-01Full month of January (31 days)
2026-01-152026-01-16January 15th only (1 day)
*media_buy_ids filters results to specific media buys. If neither provided, returns all media buys in current session context.

Response

Returns delivery report with aggregated totals and per-media-buy breakdowns:
FieldDescription
reporting_periodDate range for report (start/end timestamps)
currencyISO 4217 currency code (USD, EUR, GBP, etc.)
attribution_windowAttribution methodology: post_click and post_view (duration objects), and model (last_touch, first_touch, linear, time_decay, data_driven)
aggregated_totalsCombined metrics across all media buys (impressions, spend, clicks, views, completed_views, conversions, conversion_value, roas, new_to_brand_rate, cost_per_acquisition, completion_rate, reach, reach_unit, frequency, media_buy_count, metric_aggregates)
media_buy_deliveriesArray of delivery data per media buy

Media Buy Delivery Object

3.1 vocabulary note. get_media_buy_delivery returns the lifecycle state on a nested media_buy_deliveries[].status field (no envelope collision — nested at depth 1). create_media_buy and update_media_buy success responses return the same lifecycle state on a top-level media_buy_status field (added in 3.1 to avoid colliding with the envelope task-status status). Same enum, two field names in 3.1 — the cascade unifies in 4.0 (#4905). See Migration › media_buy_status for the full picture.
FieldDescription
media_buy_idMedia buy identifier
statusCurrent status (pending_creatives, pending_start, active, paused, completed). In webhook context, may also be reporting_delayed or failed. Maps to media_buy_status on create_media_buy / update_media_buy success responses (3.1 vocabulary note above).
totalsAggregate metrics (impressions, spend, clicks, ctr, conversions, conversion_value, roas, new_to_brand_rate)
by_packagePackage-level breakdowns with delivery_status, paused state, and pacing_index
daily_breakdownDay-by-day delivery (date, impressions, spend, conversions, conversion_value, roas, new_to_brand_rate)
See schema for complete field list.

Final vs provisional numbers

A delivery row is either final for its measurement window or it isn’t. Final means the seller considers these numbers closed for the period — no further revision — and is willing to invoice on them, subject to whatever measurement_terms.billing_measurement the buy was created with. Anything else is provisional: still settling as measurement matures (broadcast C3 → C7 DVR accumulation, post-IVT scrubbing, conversion dedup) and not an invoicing source of truth. Per-row signals:
  • media_buy_deliveries[*].is_final and media_buy_deliveries[*].finalized_at — row-level finality, true iff every package in the row is final for the same measurement window.
  • media_buy_deliveries[*].by_package[*].is_final and .finalized_at — package-level finality with the exact timestamp.
  • media_buy_deliveries[*].by_package[*].measurement_window — which maturation stage the numbers represent (c3, c7, post_sivt, downloads_30d, …).
Callers using rows where is_final is false (or absent) for pacing and reporting are safe; callers using them for reconciliation, accruals, or finance close are not.

Who is authoritative for billing

Whose number invoices the buy is a contract term, declared in the buy’s measurement_terms.billing_measurement:
  • Seller-attested (the default when billing_measurement is absent or names the seller’s own ad server): invoice off final rows on get_media_buy_delivery.
  • Vendor-attested (third-party measurement vendor named in the buy — e.g., Nielsen, IAS, DV, MOAT): invoice off the named vendor’s authoritative numbers. Operationally this is most often the seller pulling from the vendor and publishing on get_media_buy_delivery with is_final: true; when the buyer holds the vendor relationship instead, the buyer pushes via report_usage with final: true and finalized_at set.
  • Buyer-attested (buyer’s 3PAS or MMP named in the buy — e.g., CM360, Flashtalking): invoice off the buyer’s final records pushed via report_usage.
When the authoritative party doesn’t publish final numbers within measurement_terms.billing_measurement.finalization_deadline_hours, the counterparty MAY fall back to its own attestation; the breach is handled under makegood_policy. The deadline applies symmetrically to whichever party is named in vendor. When variance between parties exceeds max_variance_percent, parties resolve via the buy’s makegood_policy.available_remedies and out-of-band negotiation. See Billing authority for the end-to-end flow. A structured dispute task — opening, transitioning, and resolving a delivery dispute on the wire — is targeted for AdCP 3.2.

Aggregated metric partitions (metric_aggregates)

Cross-buy delivery values that vary by qualifier (measurement standard, transparency disclosure) are reported as a partitioned array on aggregated_totals.metric_aggregates rather than as flat scalars. This solves the apples-to-oranges sum problem at the aggregate layer: MRC and GroupM viewability define materially different thresholds and must never be combined into a single rate. Each row carries the same atomic unit as package.committed_metrics and by_package[].missing_metrics:
committed_metrics row : { scope, metric_id, qualifier, committed_at }
missing_metrics row   : { scope, metric_id, qualifier }
metric_aggregates row : { scope, metric_id, qualifier, value, ...components }
Reconciliation is a row-level join on (scope, metric_id, qualifier). For each committed_metrics row, find the matching metric_aggregates row; absent matches surface as missing_metrics. No per-metric reconciliation logic, no traversal asymmetry between contract and delivery. Granularity rule. One row per (metric_id, full-qualifier-set), reported at the finest available granularity. Buyers re-aggregate up if they want a coarser view. This eliminates rollup ambiguity and prevents accidental double-counting. Unqualified metrics stay top-level. impressions, spend, media_buy_count, and other metrics with no qualifier remain at the top of aggregated_totals. metric_aggregates is only used for metrics with non-empty qualifier sets. Mutual exclusion (MUST). For any metric_id appearing in metric_aggregates, the corresponding top-level scalar in aggregated_totals MUST be omitted — not zeroed. Sellers MUST NOT emit both. Avoids duplicate sources of truth. Qualifier vocabulary is closed today on both committed_metrics and metric_aggregates (additionalProperties: false). Five keys exist: viewability_standard (MRC vs GroupM viewability), completion_source (seller- vs vendor-attested completion), attribution_methodology (deterministic_purchase / probabilistic / panel_based / modeled — for outcome metrics), attribution_window (structured duration — for outcome metrics), and lift_dimension (awareness / consideration / favorability / purchase_intent / ad_recall — for brand_lift). The delivery vocabulary is expected to diverge from contract in future minors as transparency disclosures buyers don’t commit to ship delivery-only (e.g., tracker_firing pending #3832). New qualifier keys ship explicitly in subsequent minors on either surface. Heterogeneous value types: qualifier values are mostly string enums but attribution_window is an object-valued duration; consumers MUST dispatch on key name to know value shape, and structured-value qualifiers join on canonical (key-sorted) deep equality. Qualifier-set drift across reports. When a campaign gains a new qualifier mid-flight (e.g., adds tracker_firing partitioning in week 2 after only client-side firing in week 1), prior periods’ rows remain valid at their original granularity. Buyers SHOULD NOT retroactively repartition; report supersession via supersedes_window is the documented path for window-level revisions. Per-buy totals shape stays flat. Each individual buy is single-qualifier by definition; only the cross-buy aggregate spans qualifiers and needs the partitioned shape. Per-buy totals.viewability continues to be a flat object with its own standard field. Example:
{
  "aggregated_totals": {
    "impressions": 1000000,
    "spend": 5000.00,
    "media_buy_count": 3,
    "metric_aggregates": [
      {
        "scope": "standard",
        "metric_id": "viewable_rate",
        "qualifier": { "viewability_standard": "mrc" },
        "value": 0.7286,
        "measurable_impressions": 700000,
        "viewable_impressions": 510000
      },
      {
        "scope": "standard",
        "metric_id": "viewable_rate",
        "qualifier": { "viewability_standard": "groupm" },
        "value": 0.55,
        "measurable_impressions": 180000,
        "viewable_impressions": 99000
      }
    ]
  }
}
Value typing dispatch. Buyer agents MUST inspect metric_id before doing arithmetic. Rate metrics (viewable_rate, completion_rate) are 0.0–1.0; cost-per metrics are currency; count metrics are non-negative numbers; ROAS is a ratio. Same dispatch convention as committed_metricsmetric_id is the type tag.

Common Scenarios

Single Media Buy

import { testAgent } from '@adcp/sdk/testing';
import { GetMediaBuyDeliveryResponseSchema } from '@adcp/sdk';

// Get single media buy delivery report
const result = await testAgent.getMediaBuyDelivery({
  media_buy_ids: ['mb_12345'],
  start_date: '2024-02-01',
  end_date: '2024-02-07'
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = GetMediaBuyDeliveryResponseSchema.parse(result.data);

// Check for errors (discriminated union response)
if ('errors' in validated && validated.errors) {
  throw new Error(`Query failed: ${JSON.stringify(validated.errors)}`);
}

console.log(`Delivered ${validated.aggregated_totals.impressions.toLocaleString()} impressions`);
console.log(`Spend: $${validated.aggregated_totals.spend.toFixed(2)}`);
if (validated.media_buy_deliveries.length > 0) {
  console.log(`CTR: ${(validated.media_buy_deliveries[0].totals.ctr * 100).toFixed(2)}%`);
}

Multiple Media Buys

import { testAgent } from '@adcp/sdk/testing';
import { GetMediaBuyDeliveryResponseSchema } from '@adcp/sdk';

// Get all active media buys from context
const result = await testAgent.getMediaBuyDelivery({
  status_filter: 'active',
  start_date: '2024-02-01',
  end_date: '2024-02-07'
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = GetMediaBuyDeliveryResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Query failed: ${JSON.stringify(validated.errors)}`);
}

console.log(`${validated.aggregated_totals.media_buy_count} active campaigns`);
console.log(`Total impressions: ${validated.aggregated_totals.impressions.toLocaleString()}`);
console.log(`Total spend: $${validated.aggregated_totals.spend.toFixed(2)}`);

// Review each campaign
validated.media_buy_deliveries.forEach(delivery => {
  console.log(`${delivery.media_buy_id}: ${delivery.totals.impressions.toLocaleString()} impressions, CTR ${(delivery.totals.ctr * 100).toFixed(2)}%`);
});

Date Range Reporting

import { testAgent } from '@adcp/sdk/testing';
import { GetMediaBuyDeliveryResponseSchema } from '@adcp/sdk';

// Get month-to-date performance
const now = new Date();
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
const dateFormat = date => date.toISOString().split('T')[0];

const result = await testAgent.getMediaBuyDelivery({
  media_buy_ids: ['mb_12345'],
  start_date: dateFormat(monthStart),
  end_date: dateFormat(now)
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = GetMediaBuyDeliveryResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Query failed: ${JSON.stringify(validated.errors)}`);
}

if (validated.media_buy_deliveries.length > 0) {
  // Analyze daily breakdown
  const dailyBreakdown = validated.media_buy_deliveries[0].daily_breakdown;
  if (dailyBreakdown && dailyBreakdown.length > 0) {
    console.log(`Daily average: ${Math.round(validated.aggregated_totals.impressions / dailyBreakdown.length).toLocaleString()} impressions`);

    // Find peak day
    const peakDay = dailyBreakdown.reduce((max, day) =>
      day.impressions > max.impressions ? day : max
    );
    console.log(`Peak day: ${peakDay.date} with ${peakDay.impressions.toLocaleString()} impressions`);
  }
}

Multi-Status Query

import { testAgent } from '@adcp/sdk/testing';
import { GetMediaBuyDeliveryResponseSchema } from '@adcp/sdk';

// Get both active and paused campaigns
const result = await testAgent.getMediaBuyDelivery({
  status_filter: ['active', 'paused'],
  start_date: '2024-02-01',
  end_date: '2024-02-07'
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = GetMediaBuyDeliveryResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Query failed: ${JSON.stringify(validated.errors)}`);
}

// Group by status
const byStatus = validated.media_buy_deliveries.reduce((acc, delivery) => {
  if (!acc[delivery.status]) acc[delivery.status] = [];
  acc[delivery.status].push(delivery);
  return acc;
}, {});

console.log(`Active campaigns: ${byStatus.active?.length || 0}`);
console.log(`Paused campaigns: ${byStatus.paused?.length || 0}`);

// Identify underperforming campaigns
byStatus.paused?.forEach(delivery => {
  if (delivery.by_package && delivery.by_package.length > 0) {
    const avgPacing = delivery.by_package.reduce((sum, pkg) => sum + pkg.pacing_index, 0) / delivery.by_package.length;
    console.log(`${delivery.media_buy_id}: paused with ${(avgPacing * 100).toFixed(0)}% pacing`);
  }
});

Buyer Reference Query

import { testAgent } from '@adcp/sdk/testing';
import { GetMediaBuyDeliveryResponseSchema } from '@adcp/sdk';

// Query by buyer reference instead of media buy ID
const result = await testAgent.getMediaBuyDelivery({
  media_buy_ids: ['acme_q1_campaign_2024', 'acme_q1_retargeting_2024']
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = GetMediaBuyDeliveryResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Query failed: ${JSON.stringify(validated.errors)}`);
}

// Lifetime delivery data (no date range specified)
console.log(`Total lifetime impressions: ${validated.aggregated_totals.impressions.toLocaleString()}`);
console.log(`Total lifetime spend: $${validated.aggregated_totals.spend.toFixed(2)}`);

// Compare campaigns
validated.media_buy_deliveries.forEach(delivery => {
  if (delivery.totals.impressions > 0) {
    const cpm = (delivery.totals.spend / delivery.totals.impressions) * 1000;
    console.log(`${delivery.media_buy_id}: CPM $${cpm.toFixed(2)}, CTR ${(delivery.totals.ctr * 100).toFixed(2)}%`);
  }
});

Account-Scoped Query

import { testAgent } from '@adcp/sdk/testing';
import { GetMediaBuyDeliveryResponseSchema } from '@adcp/sdk';

// Get delivery for a specific advertiser account
const result = await testAgent.getMediaBuyDelivery({
  account: { account_id: 'acc_acme_pinnacle' },
  status_filter: 'active',
  start_date: '2024-02-01',
  end_date: '2024-02-07'
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = GetMediaBuyDeliveryResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Query failed: ${JSON.stringify(validated.errors)}`);
}

console.log(`${validated.aggregated_totals.media_buy_count} campaigns for account`);
console.log(`Total spend: $${validated.aggregated_totals.spend.toFixed(2)}`);

Metrics Definitions

MetricDefinition
ImpressionsNumber of times ads were displayed
SpendAmount spent in specified currency
ClicksNumber of ad clicks (if available)
CTRClick-through rate (clicks/impressions)
ViewsContent engagements at the billable view threshold — video views, audio/podcast stream starts, or format-specific view events
Completed ViewsAudio/video completions (at threshold or 100%)
Completion RateCompletion rate (completed_views/impressions)
ConversionsAttributed conversions (purchases, new listeners, app installs, etc.)
Conversion ValueTotal monetary value of attributed conversions
ROASReturn on ad spend (conversion_value / spend)
New-to-Brand RateFraction of conversions from first-time brand buyers (0-1)
Cost per AcquisitionCost per conversion (spend / conversions)
ReachUnique users reached (see reach_unit for measurement unit: individuals, households, devices, accounts, cookies). Measurement window declared via reach_window; without it, buyers must not sum reach across rows.
Reach UnitUnit of measurement for reach — required when reach is present
Reach WindowWindow semantics for reported reach/frequency: cumulative (uniques since campaign start), period (uniques within a single non-overlapping reporting period — e.g., daily snapshot), or rolling (uniques within a trailing window — e.g., trailing-7-day). Never sum across rows. Optional but strongly recommended when reach is present.
FrequencyAverage ad exposures per reach unit, measured over reach_window
ViewabilityObject with vendor, measurable_impressions (denominator), viewable_impressions, viewable_rate, viewed_seconds (average in-view duration per measurable impression — pairs with the viewed_seconds optimization goal), and standard
FollowsNew followers, subscribes, or page likes attributed to delivery
Pacing IndexActual vs. expected delivery rate (1.0 = on track, <1.0 = behind, >1.0 = ahead)
CPMCost per thousand impressions (spend/impressions * 1000)

Query Behavior

Context-Based Queries

  • If no media_buy_ids provided, returns all media buys from current session context
  • Context established by previous operations (e.g., create_media_buy)

Status Filtering

  • Defaults to ["active"] if not specified
  • Can be single string ("active") or array (["active", "paused"])
  • Valid filter values are media-buy lifecycle statuses: pending_creatives, pending_start, active, paused, completed
  • reporting_delayed and failed are delivery/reporting statuses returned in webhook contexts, not request filter values
  • Some legacy integrations may emit pending; treat it as equivalent to pending_start

Date Ranges

  • If dates not specified, returns campaign lifetime delivery data
  • Both start_date and end_date must be provided together — partial date ranges are invalid
  • Date format: YYYY-MM-DD
  • Start-inclusive, end-exclusive: start_date is included, end_date is excluded. For example, start_date: "2026-01-01" and end_date: "2026-01-02" returns data for January 1st only.
  • Products declare date range support in reporting_capabilities.date_range_support
  • Products with date_range_support: "lifetime_only" reject requests that include start_date/end_date with a DATE_RANGE_NOT_SUPPORTED error
  • Products with date_range_support: "date_range" accept date parameters and filter delivery data accordingly
  • Daily breakdown may be truncated for long date ranges to reduce response size

Metric Availability

  • Universal: Impressions, spend (available on all platforms)
  • Format-dependent: Clicks, completed_views, completion_rate (depends on inventory type and platform capabilities)
  • Audience: Reach, frequency (available on platforms with deduplicated measurement)
  • Commerce attribution: Conversions, conversion_value, roas, new_to_brand_rate (available on commerce media and streaming platforms)
  • Engagement: Follows, saves, engagements, profile_visits (available on social and streaming platforms)
  • Attribution window: attribution_window describes the lookback windows and model used for conversion attribution (e.g., 14-day click, 1-day view, last_touch)
  • Package-level: All metrics broken down by package with pacing_index

Data Freshness

  • Reporting data typically has 2-4 hour delay
  • Real-time impression counts not available
  • Use for periodic reporting and optimization decisions, not live monitoring
Phased-maturation channels: Data freshness differs for channels where billing-grade data is produced in phases rather than arriving final on day one — broadcast TV (Live → C3 → C7 DVR accumulation, final C7 ~15–22 days after broadcast), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → post-GIVT → post-SIVT), and podcast (7-day → 30-day downloads). Products with reporting_capabilities.measurement_windows declare these timelines. Buyers reconcile against the measurement_window specified in billing_measurement on the agreed terms. See Accountability for measurement terms and Optimization and reporting for the full lifecycle.

Error Handling

Error CodeDescriptionResolution
AUTH_MISSINGNo credentials presentedProvide credentials via auth header
AUTH_INVALIDCredentials rejected (expired / revoked)Human credential rotation required; do not auto-retry
MEDIA_BUY_NOT_FOUNDMedia buy doesn’t existVerify media_buy_id
INVALID_DATE_RANGEInvalid start/end datesUse YYYY-MM-DD format, ensure start < end
DATE_RANGE_NOT_SUPPORTEDProduct only supports lifetime reportingOmit start_date and end_date. Check reporting_capabilities.date_range_support on the product.
UNSUPPORTED_GRANULARITYRequested time_granularity is not in the product’s windowed_pull_granularitiesRe-issue with a granularity from error.details.supported_granularities, or omit time_granularity to fall back to cumulative date-range pulls. See Windowed pull recovery.
CONTEXT_REQUIREDNo media buys in contextProvide media_buy_ids explicitly
INVALID_STATUS_FILTERInvalid status valueUse valid status: pending_creatives, pending_start, active, paused, completed

Package-Level Metrics

The by_package array provides per-package delivery details with these key fields: Buyer Control:
  • paused: Whether the package is currently paused by the buyer (true/false)
System State:
  • delivery_status: System-reported operational state:
    • delivering - Package is actively delivering impressions
    • completed - Package finished successfully
    • budget_exhausted - Package ran out of budget
    • flight_ended - Package reached its end date
    • goal_met - Package achieved its impression/conversion goal
Performance:
  • pacing_index: Delivery pace (1.0 = on track, below 1.0 = behind, above 1.0 = ahead)
  • rate: Effective pricing rate (e.g., CPM)
  • pricing_model: How the package is billed (cpm, cpcv, cpp, etc.)
Accountability:
  • missing_metrics: Metrics the binding reporting contract advertised but that are not populated in this report. Each entry uses an explicit scope discriminator: { "scope": "standard", "metric_id": "completed_views" } for entries from the closed available-metric.json enum, { "scope": "vendor", "vendor": { "domain": "..." }, "metric_id": "attention_units" } for vendor-defined metrics. Standard entries MAY carry a qualifier mirroring the committed_metrics qualifier (e.g., { "scope": "standard", "metric_id": "viewable_rate", "qualifier": { "viewability_standard": "mrc" } } flags a missing MRC commitment even when GroupM viewability was reported, or { "scope": "standard", "metric_id": "completion_rate", "qualifier": { "completion_source": "vendor_attested" } } flags a missing vendor-attested commitment even when seller-attested completion was reported — the paths are not interchangeable). Reconciled against package.committed_metrics (filtered to entries where committed_at < reporting_period.end) when present; falls back to the product’s current reporting_capabilities.available_metrics and vendor_metrics when absent. Empty array (or absent) indicates clean delivery against the contract; non-empty signals an accountability breach. Sellers MUST exclude metrics that are not yet measurable for the current measurement_window (e.g., post-IVT counts during the live window) — those will appear (or not) when a wider window supersedes this report via supersedes_window.
  • vendor_metric_values: Reported values for vendor-defined metrics that the product’s reporting_capabilities.vendor_metrics declared (proprietary attention, emissions, panel demographics, brand-lift surveys, etc.). Each entry carries { vendor, metric_id, value, unit?, measurable_impressions?, breakdown? }. The measurable_impressions field is the coverage denominator — vendor measurement is rarely 100% of delivery, since vendors only score impressions where their SDK fires or their panel matches. Buyers compute coverage as measurable_impressions / impressions. When measurable_impressions is absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When a declared vendor metric is omitted entirely from this array, infer no measurement happened (no integration).
Key Distinction: paused reflects buyer control, while delivery_status reflects system reality. A package can be not paused but have delivery_status: "budget_exhausted".

Creative-Level Metrics

When the seller supports creative-level reporting (supports_creative_breakdown in reporting capabilities), each package includes a by_creative array with per-creative delivery metrics. Each creative entry includes:
  • creative_id: Creative identifier matching the creative assignment
  • weight: Delivery weight for this creative during the reporting period (0-100)
  • All standard delivery metrics (impressions, spend, clicks, ctr, etc.)
{
  "by_package": [
    {
      "package_id": "pkg_001",
      "spend": 5000,
      "impressions": 100000,
      "pricing_model": "cpm",
      "rate": 50,
      "currency": "USD",
      "delivery_status": "delivering",
      "by_creative": [
        {
          "creative_id": "hero_video_30s",
          "weight": 60,
          "impressions": 60000,
          "spend": 3000,
          "clicks": 3000,
          "ctr": 0.05,
          "completion_rate": 0.72
        },
        {
          "creative_id": "hero_video_15s",
          "weight": 40,
          "impressions": 40000,
          "spend": 2000,
          "clicks": 1200,
          "ctr": 0.03,
          "completion_rate": 0.85
        }
      ]
    }
  ]
}
For deeper creative analytics including variant-level delivery data (asset combination optimization, generative creative), use get_creative_delivery. This is a Creative Protocol task — call it on any agent that implements the Creative Protocol, which may be the same sales agent if it declares "creative" in supported_protocols. See Creative capabilities on sales agents.

Catalog-item reporting

For catalog-driven packages (packages with a catalog field), the seller can return per-catalog-item delivery in the by_catalog_item array within each package. Each entry identifies the catalog item and includes standard delivery metrics:
FieldDescription
content_idThe item identifier (SKU, GTIN, job ID, etc.)
content_id_typeIdentifier type (sku, gtin, job_id, etc.) matching the catalog’s content_id_type
Standard metricsimpressions, spend, clicks, ctr, conversions, roas, and other delivery metrics
This is optional. Sellers that support item-level reporting populate by_catalog_item; sellers that do not simply omit it.
{
  "by_package": [
    {
      "package_id": "pkg_001",
      "spend": 5000,
      "impressions": 100000,
      "pricing_model": "cpc",
      "rate": 1.20,
      "currency": "USD",
      "delivery_status": "delivering",
      "by_catalog_item": [
        {
          "content_id": "SKU-12345",
          "content_id_type": "sku",
          "impressions": 45000,
          "spend": 2250,
          "clicks": 1800,
          "ctr": 0.04,
          "conversions": 90,
          "roas": 4.2
        },
        {
          "content_id": "SKU-67890",
          "content_id_type": "sku",
          "impressions": 55000,
          "spend": 2750,
          "clicks": 2200,
          "ctr": 0.04,
          "conversions": 110,
          "roas": 3.8
        }
      ]
    }
  ]
}

Windowed pull recovery

reporting_webhook fires at the buyer’s chosen reporting_frequency (hourly, daily, monthly). When a receiver is offline long enough for transport retries to expire, the buyer loses per-window detail unless GET can reproduce the same slices. time_granularity + include_window_breakdown close that gap.

Capability check

Sellers declare which granularities they honor for pull recovery via reporting_capabilities.windowed_pull_granularities. Buyers MUST check the capability before requesting time_granularity:
test=false
{
  "reporting_capabilities": {
    "available_reporting_frequencies": ["hourly", "daily"],
    "windowed_pull_granularities": ["daily"]
  }
}
The seller in this example emits hourly webhooks but only honors daily pulls — common in stream-tap architectures where the webhook is a Kafka tap and historical pulls go through a warehouse. Two-paths-parity holds at the declared set; for hourly recovery, the webhook is primary. Sellers that want full parity declare every frequency they fire.

Requesting windowed slices

test=false
{
  "media_buy_ids": ["mb_12345"],
  "start_date": "2026-06-01",
  "end_date": "2026-06-02",
  "time_granularity": "hourly",
  "include_window_breakdown": true
}

Response shape

Each media buy gains a windows[] array on the response:
test=false
{
  "media_buy_deliveries": [
    {
      "media_buy_id": "mb_12345",
      "status": "active",
      "totals": { "impressions": 12345678, "spend": 5432.10 },
      "by_package": [ /* cumulative per-package — unchanged */ ],
      "windows": [
        {
          "window_start": "2026-06-01T00:00:00Z",
          "window_end": "2026-06-01T01:00:00Z",
          "totals": { "impressions": 510234, "spend": 226.05 },
          "by_package": [
            { "package_id": "pkg_001", "impressions": 510234, "spend": 226.05 }
          ],
          "is_final": true
        },
        {
          "window_start": "2026-06-01T01:00:00Z",
          "window_end": "2026-06-01T02:00:00Z",
          "totals": { "impressions": 488112, "spend": 215.83 },
          "is_final": true
        }
      ]
    }
  ]
}
Slices are ordered by window_start ascending; consecutive rows are contiguous (each row’s window_end equals the next row’s window_start). Each slice payload is shape-aligned with what reporting_webhook would have delivered for the same window — a buyer reconciling a missed webhook joins on (media_buy_id, window_start).

Spec contract

  • Capability-scoped MUST — sellers MUST honor time_granularity requests for any value in windowed_pull_granularities. Pulls outside the declared set return UNSUPPORTED_GRANULARITY.
  • Asymmetric is honest — sellers MAY emit higher-frequency webhooks than they expose for pull. Declaring available_reporting_frequencies: ["hourly", "daily"] with windowed_pull_granularities: ["daily"] is valid; the buyer treats the hourly webhook as primary at that frequency.
  • Same-shape recovery — slice payloads mirror webhook fire payloads at the same granularity so a buyer’s reconciliation pipeline does not branch on transport path.
This surface anchors snapshot-and-log Rule 4 (either path is complete) for data-bearing events. See that page for the broader contract.

Dimension Breakdowns

When you include reporting_dimensions in the request, the response includes dimensional breakdown arrays within each by_package entry. Each breakdown entry inherits all fields from delivery-metrics plus dimension-specific identifiers.

Requesting breakdowns

test=false
{
  "media_buy_ids": ["mb_123"],
  "reporting_dimensions": {
    "geo": { "geo_level": "metro", "system": "nielsen_dma", "limit": 10 },
    "device_type": {},
    "placement": { "limit": 5, "sort_by": "roas" }
  }
}
Each dimension accepts optional limit (max rows; defaults to 25 for geo, audience, and placement) and sort_by (any value from the sort-metric enum, e.g., spend, impressions, clicks, roas — defaults to spend descending; falls back to spend if the seller does not report the requested metric). Geo requires geo_level (country, region, metro, postal_area) and system for metro/postal levels. Each request uses a single geo_level — for multiple granularities (e.g., country and region), make separate requests. Unsupported dimensions are silently omitted from the response, but malformed requests (e.g., geo without geo_level) return a validation error. Breakdowns are per-dimension only — cross-dimensional intersections (e.g., device_type × geo) are not supported.

Available dimensions

DimensionBreakdown fieldRequired fieldsCapability flag
Geographyby_geogeo_level, geo_code, impressions, spendsupports_geo_breakdown
Device typeby_device_typedevice_type, impressions, spendsupports_device_type_breakdown
Device platformby_device_platformdevice_platform, impressions, spendsupports_device_platform_breakdown
Audienceby_audienceaudience_id, audience_source, impressions, spendsupports_audience_breakdown
Placementby_placementplacement_id, impressions, spendsupports_placement_breakdown
Check reporting_capabilities on the product to discover which dimensions are available. Product-level capabilities are authoritative since different products from the same seller may support different breakdowns.

Truncation

Each breakdown array has a sibling boolean flag (e.g., by_geo_truncated). When true, additional rows exist beyond the returned set. When false, the list is complete. Sellers MUST return the truncated flag whenever the corresponding breakdown array is present. Rows are sorted by the requested sort_by metric descending.

Audience sources

The audience_source field indicates where the audience segment originated:
SourceDescriptionTargetable?
syncedBuyer’s first-party data via sync_audiencesYes — use audience_include/audience_exclude
platformSeller’s native segments (interest, behavioral)No — informational
third_partyExternal data provider segmentsNo — informational
lookalikePlatform-generated expansion from a seedNo — informational
retargetingPrior engagement via seller’s pixel/tagNo — informational
unknownUnclassified or unrecognized audience sourceNo — informational

Best Practices

1. Check Date Range Support Before requesting date-filtered delivery, check reporting_capabilities.date_range_support on the product. Products with lifetime_only support reject date range requests — omit start_date and end_date to get campaign lifetime data instead. 2. Use Date Ranges for Analysis For products that support date ranges, specify dates for period-over-period comparisons and trend analysis. 3. Monitor Pacing Index Aim for 0.95-1.05 pacing index. Values outside this range indicate delivery issues. 4. Check Daily Breakdown Identify delivery patterns and weekend/weekday performance differences. 5. Compare Package Performance Use by_package breakdowns to identify best-performing inventory. Check both paused state and delivery_status to understand why packages aren’t delivering. 6. Track Status Changes Use multi-status queries to understand why campaigns were paused or completed.

Post-Delivery Governance Validation

Delivery reporting is not the final step. When campaign governance is active, delivery data feeds into governance validation to detect unauthorized supply paths, geo drift, and pacing violations. The governance feedback loop:
  1. Pull delivery data via get_media_buy_delivery
  2. Report outcomes to the governance agent via report_plan_outcome
  3. The governance agent compares actual delivery against planned parameters (drift detection)
  4. Validate property delivery via validate_property_delivery to catch unauthorized supply paths
Governance taskPurpose
report_plan_outcomeFeed delivery data to the governance agent for budget tracking and drift detection
validate_property_deliveryValidate delivery records against property lists — catches ads running on unauthorized properties
validate_content_deliveryValidate content artifacts against brand suitability standards
get_plan_audit_logsView the full plan state and audit trail
Without this feedback loop, delivery data is reported but never validated. Budget overruns, pacing divergence, geo drift, and unauthorized supply paths go undetected.

Next Steps

After retrieving delivery data:
  1. Optimize Campaigns: Use update_media_buy to adjust budgets, pacing, or targeting
  2. Provide Feedback: Use provide_performance_feedback to share results with seller
  3. Update Creatives: Use sync_creatives to refresh underperforming assets
  4. Create Follow-Up Campaigns: Use create_media_buy based on insights

Learn More