Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-format-delivery-breakdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": minor
---

Add capability-gated delivery breakdowns by canonical creative `format_kind` to `get_media_buy_delivery`, including explicit GET-only scope, `custom` aggregation, truncation disclosure, independent reconciliation from creative-level rows, and the full sort contract (`sort_direction` plus the `by_format_sorted_by`/`by_format_sort_direction` applied-sort echo with row-grain fallback and nulls-last semantics).
3 changes: 3 additions & 0 deletions docs/media-buy/media-buys/optimization-reporting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1275,13 +1275,16 @@ Delivery data can be broken down across multiple dimensions within each package.
| Geography | `by_geo` | `geo_level`, `geo_code`, `impressions`, `spend` | `system`, `country`, `geo_name` | `supports_geo_breakdown` |
| Device type | `by_device_type` | `device_type`, `impressions`, `spend` | — | `supports_device_type_breakdown` |
| Device platform | `by_device_platform` | `device_platform`, `impressions`, `spend` | — | `supports_device_platform_breakdown` |
| Creative format | `by_format` | `format_kind`, `impressions`, `spend` | Other delivery metrics | `supports_format_breakdown` |
| Audience | `by_audience` | `audience_id`, `audience_source`, `impressions`, `spend` | `audience_name` | `supports_audience_breakdown` |
| Demographic | `by_demographic` | `demographic`, `demographic_system`, `impressions`, `spend` | `age`, other delivery metrics | `supports_demographic_breakdown` |
| Placement | `by_placement` | `placement_id`, `impressions`, `spend` | `publisher_domain`, `placement_name` | `supports_placement_breakdown` |
| Spot/as-run | `by_spot` | `spot_id`, `aired_at` | `impressions`, `network`, `station`, `daypart`, other delivery metrics | `supports_spot_breakdown` |

Each breakdown entry inherits all fields from `delivery-metrics` (clicks, conversions, and other optional metrics) plus its dimension-specific fields. Every entry requires the fields shown in the required fields column. Check `reporting_capabilities` on the product to discover which dimensions are available; product-level capabilities are authoritative because different products from the same seller may support different breakdowns. `supports_geo_breakdown`, `supports_demographic_breakdown`, and `supports_spot_breakdown` are structured declarations; the other capability declarations in this table are boolean flags. Within `supports_geo_breakdown`, `country` and `region` are booleans, while `metro` is keyed by `metro-system` values and native `postal_area` is keyed by ISO 3166-1 alpha-2 country with arrays of country-local `postal-system` values. Geo rows use `system` for `geo_level: "metro"` and `"postal_area"`; native postal rows also include `country`. Deprecated country-fused postal systems remain accepted for compatibility.

Format breakdowns use the AdCP 3.1 canonical `format_kind` vocabulary and are negotiated on the GET path. Reporting webhook configuration does not negotiate or guarantee dimensional breakdowns, although webhook payloads may carry the same fields as extensions. They compare creative shapes such as `video_hosted`, `native_in_feed`, and `image`; duration, dimensions, and other format-option parameters are outside this grain. Sellers 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. Buyers must not expect row-level correspondence between `by_format` and `by_creative` because they are independently produced at different grains.

Demographic targeting and demographic reporting are separate product capabilities. A product may execute an exact buyer predicate while its measurement source reports only fixed buckets. For example, a social product might declare continuous targeting for ages 18–65 but the following reporting capability:

```json test=false
Expand Down
8 changes: 6 additions & 2 deletions docs/media-buy/task-reference/get_media_buy_delivery.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Retrieve comprehensive delivery metrics and performance data for media buy repor
| `status_filter` | string \| string[] | No | Status filter: `"pending_creatives"`, `"pending_start"`, `"active"`, `"paused"`, `"completed"`. Defaults to `["active"]` when omitted. |
| `start_date` | string | No | Report start date (YYYY-MM-DD), inclusive. Omit for campaign lifetime data. Only accepted when product supports `date_range`. |
| `end_date` | string | No | Report end date (YYYY-MM-DD), **exclusive**. Omit for campaign lifetime data. Only accepted when product supports `date_range`. |
| `reporting_dimensions` | object | No | Request 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`, `demographic`, `placement`, `spot`. Most accept optional `limit` and `sort_by`. Demographic also accepts canonical `age_ranges`, each of which must match the product's independent demographic reporting capability exactly. `spot` accepts only `limit`, returns rows by `aired_at` ascending, and defaults to the complete log for the reporting period. Geo requires `geo_level`; include `system` for metro/postal levels. Unsupported dimensions are silently omitted; unsupported requested demographic ranges return [`UNSUPPORTED_FEATURE`](/docs/building/verification/compliance-catalog#error-code-unsupported-feature); malformed requests return a validation error. |
| `reporting_dimensions` | object | No | Request 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`, `format`, `audience`, `demographic`, `placement`, `spot`. Most accept optional `limit` and `sort_by`. `format` groups rows by canonical `format_kind`, not duration or other format-option parameters, and additionally accepts `sort_direction` (`asc`/`desc`, default `desc`) with an applied-sort echo in the response. Demographic also accepts canonical `age_ranges`, each of which must match the product's independent demographic reporting capability exactly. `spot` accepts only `limit`, returns rows by `aired_at` ascending, and defaults to the complete log for the reporting period. Geo requires `geo_level`; include `system` for metro/postal levels. Unsupported dimensions are silently omitted; unsupported requested demographic ranges return [`UNSUPPORTED_FEATURE`](/docs/building/verification/compliance-catalog#error-code-unsupported-feature); malformed requests return a validation error. |
| `time_granularity` | string | No | Per-window slice granularity for pull recovery, matching `reporting_webhook.reporting_frequency` vocabulary (`hourly`, `daily`, `weekly`, `monthly`, `quarterly`, `post_campaign`). 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](#windowed-pull-recovery). |
| `include_window_breakdown` | boolean | No | When `true` (and `time_granularity` is set), include the `windows[]` array on each media buy. Defaults to `false`. Ignored when `time_granularity` is omitted. |

Expand Down Expand Up @@ -886,6 +886,7 @@ When you include `reporting_dimensions` in the request, the response includes di
"reporting_dimensions": {
"geo": { "geo_level": "metro", "system": "nielsen_dma", "limit": 10 },
"device_type": {},
"format": { "sort_by": "spend" },
"demographic": {
"age_ranges": [{ "min": 26, "max": 30, "include_unknown": false }]
},
Expand All @@ -895,7 +896,7 @@ When you include `reporting_dimensions` in the request, the response includes di
}
```

Geo, device, audience, demographic, and placement dimensions accept optional `limit` and `sort_by` (any value from the `sort-metric` enum; defaults to `spend`). Geo requires `geo_level` (`country`, `region`, `metro`, `postal_area`). Demographic accepts optional canonical `age_ranges`. Each requested range must be supported by the product's `supports_demographic_breakdown.age`: `exact_predicates` accepts ranges within its declared bounds, while `enumerated_intervals` accepts only exact declared intervals. Unsupported ranges return [`UNSUPPORTED_FEATURE`](/docs/building/verification/compliance-catalog#error-code-unsupported-feature); the seller never substitutes a wider or narrower bucket. Omitting `age_ranges` requests the product's native declared breakdown. The spot log accepts only `limit`; its canonical order is `aired_at` ascending. Breakdowns are per-dimension only — cross-dimensional intersections are not supported.
Geo, device, format, audience, demographic, and placement dimensions accept optional `limit` and `sort_by` (any value from the `sort-metric` enum; defaults to `spend`). The format dimension also accepts `sort_direction` (`asc` or `desc`; defaults to `desc`) — sellers MUST apply the requested direction, fall back to `spend` only when the requested metric is not reported at row grain (direction resets to `desc` on fallback), order rows lacking the applied sort metric last, and echo the applied sort on the response via `by_format_sorted_by` and `by_format_sort_direction` (MUST whenever `by_format` is present). Geo requires `geo_level` (`country`, `region`, `metro`, `postal_area`). Demographic accepts optional canonical `age_ranges`. Each requested range must be supported by the product's `supports_demographic_breakdown.age`: `exact_predicates` accepts ranges within its declared bounds, while `enumerated_intervals` accepts only exact declared intervals. Unsupported ranges return [`UNSUPPORTED_FEATURE`](/docs/building/verification/compliance-catalog#error-code-unsupported-feature); the seller never substitutes a wider or narrower bucket. Omitting `age_ranges` requests the product's native declared breakdown. The spot log accepts only `limit`; its canonical order is `aired_at` ascending. Breakdowns are per-dimension only — cross-dimensional intersections are not supported.

### Available dimensions

Expand All @@ -904,13 +905,16 @@ Geo, device, audience, demographic, and placement dimensions accept optional `li
| Geography | `by_geo` | `geo_level`, `geo_code`, `impressions`, `spend` | `system`, `country`, `geo_name` | `supports_geo_breakdown` |
| Device type | `by_device_type` | `device_type`, `impressions`, `spend` | — | `supports_device_type_breakdown` |
| Device platform | `by_device_platform` | `device_platform`, `impressions`, `spend` | — | `supports_device_platform_breakdown` |
| Creative format | `by_format` | `format_kind`, `impressions`, `spend` | Other delivery metrics | `supports_format_breakdown` |
| Audience | `by_audience` | `audience_id`, `audience_source`, `impressions`, `spend` | `audience_name` | `supports_audience_breakdown` |
| Demographic | `by_demographic` | `demographic`, `demographic_system`, `impressions`, `spend` | `age`, other delivery metrics | `supports_demographic_breakdown` |
| Placement | `by_placement` | `placement_id`, `impressions`, `spend` | `publisher_domain`, `placement_name` | `supports_placement_breakdown` |
| Spot/as-run | `by_spot` | `spot_id`, `aired_at` | `impressions`, `network`, `station`, `daypart`, other delivery metrics | `supports_spot_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.

`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.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"deploy:cdn-artifacts-cutover:dry-run": "wrangler deploy --config workers/artifact-cdn/wrangler.cutover.toml --dry-run",
"verify:cdn-artifacts-cutover": "node scripts/verify-cdn-artifacts-cutover.mjs",
"typecheck": "tsc --project server/tsconfig.json --noEmit",
"test:schemas": "node tests/schema-validation.test.cjs && node --test tests/trusted-match-offer-creative-data.test.cjs tests/accessibility-violation-details.test.cjs tests/portfolio-routing-scope.test.cjs tests/catalog-item-availability-updates.test.cjs tests/schema-deprecation-metadata.test.cjs tests/creative-rotation.test.cjs tests/lint-schema-enum-drift.test.cjs tests/synthetic-depiction.test.cjs && npm run test:geo-region-targeting",
"test:schemas": "node tests/schema-validation.test.cjs && node --test tests/trusted-match-offer-creative-data.test.cjs tests/accessibility-violation-details.test.cjs tests/portfolio-routing-scope.test.cjs tests/catalog-item-availability-updates.test.cjs tests/schema-deprecation-metadata.test.cjs tests/creative-rotation.test.cjs tests/format-delivery-reporting-contract.test.cjs tests/lint-schema-enum-drift.test.cjs tests/synthetic-depiction.test.cjs && npm run test:geo-region-targeting",
"test:performance-feedback": "node --test --test-force-exit --test-timeout=30000 tests/performance-feedback-contract.test.cjs",
"test:dist-schema-version-ids": "node --test --test-force-exit --test-timeout=30000 tests/dist-schema-version-ids.test.cjs",
"test:examples": "node tests/example-validation-simple.test.cjs && npm run test:tmp-context-merge",
Expand Down Expand Up @@ -134,7 +134,7 @@
"test": "npm run test:docs-nav && npm run test:owned-links && npm run test:release-docs-nav && npm run test:rewrite-dist-redirect-links && npm run test:rewrite-dist-links-idempotency && npm run test:docs-error-handling-copy && npm run test:schemas && npm run test:performance-feedback && npm run test:mcp-schema-projection && npm run test:dist-schema-version-ids && npm run test:examples && npm run test:extensions && npm run test:extension-schemas && npm run test:error-handling && npm run test:json-schema && npm run test:audio-radio && npm run test:adagents-catalog-only && npm run test:canonical-reference-resolver && npm run test:composed && npm run test:rejection-arm-mutex && npm run test:migrations && npm run test:hmac-vectors && npm run test:hmac-signer-conformance && npm run test:webhook-signing-vectors && npm run test:webhook-receiver-envelope && npm run test:oauth-setup-vectors && npm run test:transport-errors && npm run test:targeting-overlay-vectors && npm run test:targeting-aware-discovery && npm run test:demographic-targeting && npm run test:language-targeting && npm run test:attestation-vectors && npm run test:rights-attestations && npm run test:governance-runtime-attestations && npm run test:governance-conditions-storyboard && npm run test:signal-governance-storyboard && npm run test:audience-evidence && npm run test:status-as-of-vectors && npm run test:storyboard-scoping && npm run test:storyboard-branch-sets && npm run test:storyboard-provides-state-for && npm run test:storyboard-fixture-resolution && npm run test:storyboard-contradictions && npm run test:storyboard-context-entity && npm run test:storyboard-auth-shape && npm run test:storyboard-test-kits && npm run test:compliance-packaged-refs && npm run test:compliance-source-authority && npm run test:storyboard-sample-request-schema && npm run test:storyboard-response-schema && npm run test:storyboard-context-output-paths && npm run test:storyboard-validations-paths && npm run test:storyboard-check-enum && npm run test:update-media-buy-affected-packages && npm run test:storyboard-advisory-expiry && npm run test:storyboard-raw-mode-required && npm run test:storyboard-upstream-traffic-paths && npm run test:refine-finalize-validation-ids && npm run test:run-storyboards-schema-root && npm run test:storyboard-doc-parity && npm run test:pagination-invariant && npm run test:version-envelope && npm run test:test-dynamic-imports && npm run test:sdk-shims && npm run test:format-identity-boundaries && npm run test:sdk-runner-capability-gates && npm run test:callapi-state-change && npm run test:sign-protocol-tarball && npm run test:chat-streaming-code-fences && npm run test:certification-demo-formatting && npm run test:build-schemas-hoist-enums && npm run test:build-schemas-hoist-marked && npm run test:build-schemas-async-response-refs && npm run test:release-workflow && npm run test:immutable-release-artifacts && npm run test:patch-3-0-compat-bundle && npm run test:error-codes && npm run test:compliance-snippets && npm run test:doc-compliance-drift && npm run test:substitution-vector-names && npm run test:platform-agnostic && npm run test:oneof-discriminators && npm run test:schema-utf8 && npm run test:unit && npm run test:server-unit && npm run test:openapi && npm run typecheck",
"test:all": "npm run test:schemas && npm run test:examples && npm run test:extensions && npm run test:error-handling && npm run test:snippets && npm run typecheck",
"precommit:server-unit": "node scripts/precommit-server-unit.cjs",
"precommit": "bash scripts/with-timeout.sh 180 npm run test:unit && npm run test:test-dynamic-imports && npm run test:format-identity-boundaries && npm run test:callapi-state-change && bash scripts/with-timeout.sh 240 npm run precommit:server-unit && npm run typecheck",
"precommit": "bash scripts/with-timeout.sh 180 npm run test:unit && npm run test:test-dynamic-imports && npm run test:format-identity-boundaries && npm run test:callapi-state-change && bash scripts/with-timeout.sh 420 npm run precommit:server-unit && npm run typecheck",
"test:storyboards": "bash scripts/run-storyboards-matrix.sh",
"test:storyboards:3.0-compat": "bash scripts/run-storyboards-3-0-compat.sh",
"prepare": "sh scripts/configure-git-hooks.sh",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
}
},
"supports_creative_breakdown": { "type": "boolean" },
"supports_format_breakdown": { "type": "boolean" },
"supports_keyword_breakdown": { "type": "boolean" },
"supports_geo_breakdown": { "$ref": "/schemas/core/geo-breakdown-support.json" },
"supports_device_type_breakdown": { "type": "boolean" },
Expand Down
Loading
Loading