get-media-buy-delivery-request.json
Response schema: get-media-buy-delivery-response.json
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.
Exact Reliable Reporting revision read
Reliable Reporting adds the mutually exclusivereporting_revision_id selector
in the next RC schema. It returns the immutable content for exactly that revision,
not a newly calculated date-range result. The response carries the full
reporting_revision metadata, authoritative reporting_rows, and
reporting_revision_binding with the same revision ID, row count, control totals,
and the committed revision_content_sha256 over the JCS binding object. Consumers verify those fields before
using the content as reporting evidence. Existing media_buy_ids and date-range
calls remain unchanged. A zero-row revision returns an empty media_buy_deliveries
array with row_count: 0, proving that no rows is a published result rather than a
missing report.
Exact reads are cursor-paginated: every page repeats identical reporting_revision,
reporting_revision_binding, and pagination.total_count, which equals the
revision row count. Consumers MUST exhaust the cursor, concatenate reporting_rows
in returned order, then compute SHA-256 of RFC 8785/JCS
{ reporting_revision_id, row_count, control_totals, reporting_rows }. Hashing one
page is not verification. pagination is available only with
reporting_revision_id; exact mode rejects every other delivery/filter selector.
When account is omitted, the seller searches all accounts accessible to the
authenticated caller. Unknown and unauthorized revision/account combinations return
the same nondisclosing REPORTING_REVISION_NOT_FOUND result.
Request Schema: /schemas/3.2.0-rc.1/media-buy/get-media-buy-delivery-request.json
Response Schema: /schemas/3.2.0-rc.1/media-buy/get-media-buy-delivery-response.json
Request Parameters
Date Range Behavior: The date range is start-inclusive, end-exclusive. For example,start_date: "2026-01-01"andend_date: "2026-01-02"returns delivery data for January 1st only (from2026-01-01 00:00:00up to, but not including,2026-01-02 00:00:00). To get a full week of data (Jan 1-7), useend_date: "2026-01-08".
Deterministic filter contract
media_buy_ids and accepted date bounds MUST observably scope the response.
Every media_buy_deliveries[] row MUST have a media_buy_id in
media_buy_ids when that filter is present. A seller MUST NOT accept the filter
and return delivery rows for other buys.
For an accepted start_date and end_date, reporting_period.start and
reporting_period.end MUST represent those exact UTC day boundaries, and all
aggregates, per-buy totals, daily rows, and requested window slices MUST be
computed only from delivery in the start-inclusive, end-exclusive interval.
Delivery outside that interval MUST NOT contribute to the response.
A filtered request can legitimately return the same rows and values as a less
restrictive request when all underlying delivery already matches. Conformance is
therefore established from returned membership, status, and date boundaries—not
by requiring two response payloads to differ.
Date Range Examples:
*
media_buy_ids filters results to specific media buys. If neither provided, returns all media buys in current session context.
Response
Returns delivery data as per-media-buy rows: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.
See schema for the released field list. RC1-only exact-revision fields are documented in the unreleased source noted above.
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 whatevermeasurement_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_finalandmedia_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_finaland.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, …).
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’smeasurement_terms.billing_measurement:
- Seller-attested (the default when
billing_measurementis absent or names the seller’s own ad server): invoice off final rows onget_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_deliverywithis_final: true; when the buyer holds the vendor relationship instead, the buyer pushes viareport_usagewithfinal: trueandfinalized_atset. - 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.
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.
Currency and cross-buy aggregation
The response-widecurrency and aggregated_totals fields are deprecated in AdCP 3.2 and removed in 4.0. When legacy aggregated_totals is present, the response-wide currency is required solely to denominate its spend. One response can contain media buys in different currencies, and AdCP does not define exchange rates, conversion timestamps, or an FX authority. The response-wide currency therefore MUST NOT otherwise be interpreted as an aggregation currency or as evidence that the seller converted package spend.
Sellers SHOULD populate media_buy_deliveries[].currency whenever a row has one denomination. It denominates that row’s monetary totals and every package monetary value. For an AdCP-authored buy, every by_package[].currency MUST equal the row currency and the media-buy currency. For a legacy or externally created mixed-currency buy, sellers omit row currency, daily_breakdown, and all monetary or money-derived values from row and window totals. They report those values only on package rows (including window package rows) with each package’s own currency.
Consumers that encounter a row without currency must process each package amount with that package’s currency; they must not silently drop those amounts or synthesize a row total:
test=false
media_buy_deliveries[]. They MUST first verify that currencies, metric qualifiers, measurement windows, finality, and deduplication semantics are compatible. Counts such as impressions are not automatically safe to add merely because they are non-monetary; reach may be deduplicated differently, and rates require their component-weighted calculation rather than summing or averaging row values.
For the deprecated aggregated_totals.reach field, sellers producing AdCP 3.2 payloads SHOULD include reach_aggregation whenever aggregate reach is present. reach_aggregation: "deduplicated" means the seller deduplicated uniques across every included media buy. reach_aggregation: "sum_of_constituent_reach" means the seller added the independently unique reach values from each buy, so the same reach unit may be counted more than once. Sellers MUST omit aggregate frequency for sum_of_constituent_reach. Legacy payloads without reach_aggregation remain valid, but their aggregation semantics are unknown; consumers MUST NOT use that reach as a frequency denominator.
Common Scenarios
Single Media Buy
Multiple Media Buys
Date Range Reporting
Multi-Status Query
Correlating Your Own Reference
media_buy_ids are seller-assigned. They are the media_buy_id values returned by create_media_buy and get_media_buys — not buyer-side identifiers. AdCP has no buyer-reference lookup key: the top-level buyer_ref field was removed in 3.0.0, making seller-assigned media_buy_id/package_id canonical. If you hold only your own identifier, include it in the opaque context object (commonly context.buyer_ref) when you call create_media_buy; the seller echoes context back on create_media_buy and get_media_buys, so you can reconcile your reference to the seller’s media_buy_id before requesting delivery.Account-Scoped Query
Metrics Definitions
Query Behavior
Context-Based Queries
- If no
media_buy_idsprovided, 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_delayedandfailedare delivery/reporting statuses returned in webhook contexts, not request filter values- Some legacy integrations may emit
pending; treat it as equivalent topending_start
Date Ranges
- If dates not specified, returns campaign lifetime delivery data
- Both
start_dateandend_datemust be provided together — partial date ranges are invalid - Date format:
YYYY-MM-DD - Start-inclusive, end-exclusive:
start_dateis included,end_dateis excluded. For example,start_date: "2026-01-01"andend_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 includestart_date/end_datewith anUNSUPPORTED_FEATUREerror - 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_windowdescribes 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
Requesting a metric subset
requested_metrics narrows the response to a specific list of metrics — the same field and semantics as reporting_webhook.requested_metrics, now also available on the GET path. It applies to every metrics-bearing object in the response: totals, by_package, daily and window slices, and breakdown rows. impressions and spend are always included regardless of what the list contains, except that a legacy or externally created mixed-currency buy omits monetary values from media-buy and window totals, omits daily_breakdown, and reports monetary values only on currency-qualified package rows. Requesting a leaf metric identity (e.g. viewable_rate, quartile_75) returns its canonical nested carrier (viewability, quartile_data) rather than a flat duplicate field. A metric requested but not available for the buy is simply omitted — no error — and missing_metrics is unaffected by that omission: it still reconciles solely against committed_metrics, and sellers MUST NOT list a metric there just because the buyer excluded it via requested_metrics. Values outside the product’s reporting_capabilities.available_metrics are ignored. Omitting the field preserves current behavior — all available metrics are returned.
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
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
A valid query with no matching media buys succeeds with an empty
media_buy_deliveries array.
Package-Level Metrics
Theby_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)
delivery_status: System-reported operational state:delivering- Package is actively delivering impressionsnot_delivering- Package recorded zero impressions for the entire reporting window while it was in-flight. Sellers should only report this for a final window; a provisional zero may reflect measurement lag.completed- Package finished successfullybudget_exhausted- Package ran out of budgetflight_ended- Package reached its end dategoal_met- Package achieved its impression/conversion goal
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, revenue_share, etc.)commissionable_value: Forrevenue_share, the settled attributed value to which the selectedcommission_rateapplies. Verifyspend = round_currency(commissionable_value × commission_rate).
metric_values: Qualified standard values actually delivered for this package. Each row uses{ scope: "standard", metric_id, qualifier, value, ...components }, matching standard-scopepackage.committed_metricsandmissing_metricson(scope, metric_id, qualifier). Sellers report the finest available qualifier set at package grain. When a metric appears here, its flat package scalar MUST be omitted to prevent two sources of truth. Vendor-scope values remain invendor_metric_values, and buyers perform only semantically compatible cross-package aggregation.missing_metrics: Metrics the binding reporting contract advertised but that are not populated in this report. Each entry uses an explicitscopediscriminator:{ "scope": "standard", "metric_id": "completed_views" }for entries from the closedavailable-metric.jsonenum,{ "scope": "vendor", "vendor": { "domain": "..." }, "metric_id": "attention_units" }for vendor-defined metrics. Standard entries MAY carry aqualifiermirroring thecommitted_metricsqualifier (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 againstpackage.committed_metrics(filtered to entries wherecommitted_at < reporting_period.end) when present; falls back to the product’s currentreporting_capabilities.available_metricsandvendor_metricswhen 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 currentmeasurement_window(e.g., post-IVT counts during the live window) — those will appear (or not) when a wider window supersedes this report viasupersedes_window.vendor_metric_values: Reported values for vendor-defined metrics that the product’sreporting_capabilities.vendor_metricsdeclared (proprietary attention, emissions, panel demographics, brand-lift surveys, etc.). Each entry carries{ vendor, metric_id, value, unit?, measurable_impressions?, measurable_plays?, measurable_play_seconds?, vendor_relationship?, qualifier?, breakdown? }. Themeasurable_impressionsfield 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 asmeasurable_impressions / impressions. Whenmeasurable_impressionsis absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. For play-based channels (DOOH, cinema, place-based) whereimpressionsis itself modelled fromplays, the row carriesmeasurable_plays(coverage =measurable_plays / plays) ormeasurable_play_seconds(coverage =measurable_play_seconds / dooh_metrics.screen_time_secondson screen networks, or against the seller’s reported playout seconds on place-based audio) instead; a row SHOULD carry exactly one denominator.vendor_relationship(first_party/affiliated/third_party) MAY echo the product’sreporting_capabilities.vendor_metrics[].vendor_relationshipso the row states the seller–vendor relationship without a join back to the product; when present it MUST equal the declared value, and absence is undeclared, neverthird_party. Distinct from the row-levelmeasurement_sourceprovider slug used by measured channels (radio, broadcast, OOH). When a declared vendor metric is omitted entirely from this array, infer no measurement happened (no integration). JIC or panel-based co-viewing adjustments, claim reconciliation, confidence intervals, and panel sizes belong here, usually inbreakdown, rather than in buy-time signal targeting definitions.
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 assignmentcreative_name: Optional human-readable name current when the report is generated. This is convenience metadata; usecreative_idas the stable identity.weight: Delivery weight for this creative during the reporting period (0-100)- All standard delivery metrics (impressions, spend, clicks, ctr, etc.)
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.
By default by_creative is returned automatically at the seller’s discretion, with no limit, sort, or truncation contract. To answer questions like “top creatives by completion rate,” include reporting_dimensions: { "creative": { "limit": 10, "sort_by": "quartile_100" } } in the request — this negotiates the breakdown, making by_creative_truncated, by_creative_sorted_by, and by_creative_sort_direction binding. See Dimension Breakdowns.
Catalog-item reporting
For catalog-driven packages (packages with acatalog 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:
This is optional. Sellers that support item-level reporting populate
by_catalog_item; sellers that do not simply omit it.
By default by_catalog_item is returned automatically at the seller’s discretion, with no limit, sort, or truncation contract. Including reporting_dimensions.catalog_item (e.g., { "limit": 25, "sort_by": "roas" }) negotiates the breakdown, making by_catalog_item_truncated, by_catalog_item_sorted_by, and by_catalog_item_sort_direction binding. See Dimension Breakdowns.
Windowed pull recovery
reporting_webhook fires at the buyer’s chosen reporting_frequency (hourly through post_campaign). 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 viareporting_capabilities.windowed_pull_granularities. Buyers MUST check the capability before requesting time_granularity:
test=false
Requesting windowed slices
test=false
Response shape
Each media buy gains awindows[] array on the response:
test=false
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_granularityrequests for any value inwindowed_pull_granularities. Pulls outside the declared set returnUNSUPPORTED_GRANULARITY. - Asymmetric is honest — sellers MAY emit higher-frequency webhooks than they expose for pull. Declaring
available_reporting_frequencies: ["hourly", "daily"]withwindowed_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.
Dimension Breakdowns
When you includereporting_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
limit, sort_by (any value from the sort-metric enum; defaults to spend), and sort_direction (asc or desc; defaults to desc, largest first). The seller echoes the sort it actually applied in the corresponding by_*_sorted_by and by_*_sort_direction fields. For every requested dimension that the product declares supported, sellers MUST return the breakdown array (possibly empty) and by_*_truncated. Metric-sorted dimensions also return both sort echoes; demographic and property-grain arrays also return by_*_suppressed. Geo requires geo_level; demographic accepts optional canonical age_ranges; spot accepts only limit, is ordered by aired_at, and has no sort echoes. Most arrays are independent one-dimensional marginals. collection_property, installment_property, and placement_property are deliberate intersections that prove which property carried the collection, specific installment, or placement.
creative, keyword, and catalog_item are different from the other dimensions in one respect: sellers return them automatically whenever they support the underlying capability, whether or not the buyer’s reporting_dimensions mentions them. Omitting the key preserves that automatic default — rows show up with no completeness contract, and by_creative_truncated/by_keyword_truncated/by_catalog_item_truncated and their sort echoes are only RECOMMENDED. Including the key (even as {}) negotiates the breakdown: limit and sort_by/sort_direction are honored, and the truncation flag and applied-sort echo become MUST-return fields, the same as any other requested dimension.
Available dimensions
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.
by_format uses the AdCP 3.1 canonical format_kind vocabulary. It answers cross-kind questions such as how video_hosted, native_in_feed, and image delivery compare; it does not distinguish two creatives that share a format kind but differ by duration, dimensions, or another format-option parameter. Sellers MUST combine adopter-defined shapes into one custom row. When by_format_truncated is false, additive metrics such as impressions and spend SHOULD reconcile to package totals, subject to each metric’s measurement and attribution semantics. by_format_sorted_by and by_format_sort_direction echo the sort actually applied, so a fallback to spend is visible rather than silent. Buyers MUST NOT expect row-level correspondence between by_format and by_creative because they are independently produced at different grains. Requested dimensional breakdowns, including by_format, are negotiated on the GET path. Reporting webhook configuration does not negotiate or guarantee them, although webhook payloads MAY carry the same fields as extensions. Requested dimensions do not apply to windows[].by_package; those rows remain shape-aligned with webhook fires for pull recovery.
demographic_targeting declares what the product can execute; supports_demographic_breakdown separately declares what it can measure and report. A product can therefore target 26–30 but disclose fixed 25–34 reporting. Buyers MUST NOT infer exact reporting from targeting capability or parse equivalence from a native label. demographic uses the notation of its accompanying demographic_system; canonical age, when present, is the authoritative machine-comparable interval.
supports_geo_breakdown, supports_demographic_breakdown, and supports_spot_breakdown are structured capability objects; the other declarations in this table are boolean flags. Demographic capability declares exact predicates versus fixed intervals, available measurement systems, and whether small cells may be suppressed. supports_spot_breakdown.available_metrics lists metrics available at spot grain; an empty array means the product supplies as-run airing evidence but no per-spot delivery metrics.
Placement identity names its authority. placement_identity.kind: "publisher_ref" carries the publisher adagents.json domain; kind: "seller_inline" carries the defining seller_agent. The flat placement_id remains required for 3.1 consumers. New 3.2 sellers SHOULD add placement_identity to by_placement; legacy rows without it remain valid but are only package-scoped. The new by_placement_property row always requires placement_identity. placement_name is convenience metadata.
Every property row includes publisher_domain and an operational identifier such as a domain, app bundle or store ID, station ID, or facility ID. Their tuple is the stable operational key even for unregistered surfaces. When the surface resolves to the publisher catalog, optional property_ref adds {publisher_domain, property_id} and MUST use the same domain. Collection identity is canonical through collection_ref; installment_ref nests it. Independent marginals do not establish correlation. by_collection_property, by_installment_property, and by_placement_property are affirmative delivery evidence for returned intersections; catalog declarations establish only eligibility or carriage. For programmed channels, the installment intersection is what proves that a specific content airing or programming block ran on each reported host property. It is distinct from by_spot, which identifies an individual ad as-run occurrence. When an intersection array is truncated or suppressed, the absence of a row does not prove non-delivery.
Each property-grain array has a by_*_suppressed flag, separate from by_*_truncated. true means privacy, policy, or measurement thresholds omitted otherwise reportable rows from that array; false means no rows were threshold-suppressed. Suppression policies may use different bases or thresholds at different grains, so AdCP 3.2 does not attempt to encode a shared numeric floor. Suppression and truncation may both be true.
For social inventory, the platform or serving app maps to the property axis and the feed, story, short-video, search, or other render position maps to the placement axis. A fictional network could therefore report property social.example/photo_app intersecting publisher placement social.example/feed. For a conversational publisher, web, mobile app, and assistant surfaces are properties while sponsored-answer, recommendation-card, or sidebar locations are placements. Adapters omit dimensions the upstream platform does not expose; they never infer placement delivery from campaign eligibility.
Spot rows are channel-neutral as-run records. Linear TV commonly supplies network, radio commonly supplies station, and sellers MAY populate both; neither is required. spot_id identifies the scheduled occurrence and remains stable when the same airing is re-reported across Live, C3, C7, or another measurement-window progression. aired_at proves that it aired. Sellers MUST order rows by aired_at ascending. When reporting_dimensions.spot.limit is omitted, sellers SHOULD return the complete log for the requested reporting period. by_spot_truncated tells the buyer whether the returned log is complete.
The optional creative_id identifies the creative that aired, matching the package’s creative asset or assignment. For library-backed sellers this is the sync_creatives identifier; inline-only sellers use the creative_id supplied through packages[].creatives. Sellers SHOULD populate it whenever they can associate a specific airing with a creative, particularly when the package could serve more than one creative during any part of the reporting period, including a mid-period replacement. Sellers that cannot make that association MUST omit the field rather than emit a default or placeholder. Buyers MUST NOT aggregate by_spot rows by creative_id and expect the result to equal by_creative[] impressions for the same creative: the two dimensions are independently produced at different granularities and are subject to different metric-maturation and attribution semantics within the package measurement_window. by_creative is the authoritative creative-performance aggregate; creative_id on a spot row identifies which creative aired, not an independent metric roll-up source.
Delivery metrics on a spot row are optional. Omission means the metric is not yet available in the package’s current measurement_window, or the product did not declare it in supports_spot_breakdown.available_metrics. A later window may return the same spot_id with impressions populated. An explicit impressions: 0 is a measured zero; it does not mean pending or unavailable, and null is invalid.
Truncation
Each breakdown array has a sibling boolean flag (e.g.,by_geo_truncated). When true, additional non-suppressed rows exist beyond the returned top-N set; this contract does not provide a cursor, so it is an analytical ranking rather than a complete export. When false, every non-suppressed row is present. AdCP 3.2 does not define a complete property-export operation; buyers that need exhaustive brand-safety or allowlist reconciliation must use the seller’s native reporting or export interface. For every dimension requested via reporting_dimensions, sellers MUST return the truncated flag whenever the corresponding breakdown array is present. Metric dimensions are sorted by the requested sort_by value in the requested sort_direction (descending by default); rows without the applied metric sort last regardless of direction. The seller echoes the metric and direction it actually applied in by_*_sorted_by/by_*_sort_direction, which sellers MUST return whenever a requested dimension’s breakdown array is present. For by_creative, by_keyword, and by_catalog_item rows returned automatically without a request key, the truncated and echo fields are RECOMMENDED rather than required — automatic rows carry no completeness contract. Spot rows always use aired_at ascending and carry neither field.
Demographic and property-grain reporting also return by_*_suppressed. true means privacy, policy, or measurement thresholds omitted otherwise reportable rows, so the visible rows may not reconcile to package totals. This is distinct from truncation caused by a row limit, and both flags may be true.
Audience sources
Theaudience_source field indicates where the audience segment originated:
Best Practices
1. Check Date Range Support Before requesting date-filtered delivery, checkreporting_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:- Pull delivery data via
get_media_buy_delivery - Report outcomes to the governance agent via
report_plan_outcome - The governance agent compares actual delivery against planned parameters (drift detection)
- Validate property delivery via
validate_property_deliveryto catch unauthorized supply paths
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:- Optimize Campaigns: Use
update_media_buyto adjust budgets, pacing, or targeting - Provide Feedback: Use
provide_performance_feedbackto share results with seller - Update Creatives: Use
sync_creativesfor library-backed sellers, or inlinepackages[].creativesonupdate_media_buyfor inline-only sellers - Create Follow-Up Campaigns: Use
create_media_buybased on insights
Learn More
- Media Buy Lifecycle - Complete campaign workflow
- Async Operations - Async patterns and status handling
- Performance Optimization - Using delivery data for optimization