Skip to content
Merged
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/sort-direction-and-applied-sort-echo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": minor
---

Add `sort_direction` (asc/desc, default desc) to the six sortable delivery breakdown dimensions and a per-breakdown applied-sort echo (`by_X_sorted_by` / `by_X_sort_direction`, MUST whenever the breakdown is present) so the existing silent fallback-to-spend becomes visible to buyers. Ascending sort enables bottom-N optimization queries (worst placements by viewable_rate) that cannot be recovered from a truncated descending pull.
2 changes: 1 addition & 1 deletion docs/building/concepts/managing-response-size.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ webhook stream.
| `false` | All rows are present |
| `true` | Additional rows exist beyond what was returned |

When a flag is `true`, the returned rows are sorted by the requested metric descending — you have the most significant breakdowns, and the tail is omitted. This is by design: delivery breakdowns are for optimization decisions, not archival reporting. If you need the full dataset, use the seller's native reporting API.
When a flag is `true`, the returned rows are ordered by the applied sort metric in the applied direction (descending by default — the most significant breakdowns; `sort_direction: "asc"` deliberately returns the least significant, e.g. worst performers) and the tail is omitted. The response echoes the applied sort per breakdown (`by_X_sorted_by` / `by_X_sort_direction`), so a fallback to spend is visible. This is by design: delivery breakdowns are for optimization decisions, not archival reporting. If you need the full dataset, use the seller's native reporting API.

## Putting it together

Expand Down
6 changes: 3 additions & 3 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`, `audience`, `demographic`, `placement`, `spot`. Most accept optional `limit`, `sort_by`, and `sort_direction`. 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 @@ -895,7 +895,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`). The sort vocabulary includes leaf metric identities that order rows by a value nested inside an object-shaped metric — `sort_by: "quartile_100"` orders by `quartile_data.q4_views`, `sort_by: "viewable_rate"` orders by `viewability.viewable_rate` — so queries like "top placements by in-view rate" need no flat duplicate fields. Survey/model-based lift scalars are excluded from sorting; see the `sort-metric` enum description. 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, audience, demographic, and placement dimensions accept optional `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 sort vocabulary includes leaf metric identities that order rows by a value nested inside an object-shaped metric — `sort_by: "quartile_100"` orders by `quartile_data.q4_views`, `sort_by: "viewable_rate"` orders by `viewability.viewable_rate` — so queries like "top placements by in-view rate" need no flat duplicate fields. Survey/model-based lift scalars are excluded from sorting; see the `sort-metric` enum description. Setting `sort_direction: "asc"` enables bottom-N queries — for example, the 25 worst placements by `viewable_rate` — that cannot be recovered from a truncated descending pull. The seller echoes the sort it actually applied on each breakdown: `by_geo_sorted_by`/`by_geo_sort_direction`, `by_device_type_sorted_by`/`by_device_type_sort_direction`, and the equivalent pair for `device_platform`, `audience`, `demographic`, and `placement`. Sellers MUST return both echo fields whenever the corresponding breakdown array is present, including when they fall back to sorting by `spend` because they don't report the requested metric — the echo makes that fallback visible instead of leaving the buyer to misread the rows as ordered by the requested metric. 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 Down Expand Up @@ -923,7 +923,7 @@ Delivery metrics on a spot row are optional. Omission means the metric is not ye

### 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. Metric dimensions are sorted by the requested `sort_by` value descending; spot rows always use `aired_at` ascending.
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. Metric dimensions are sorted by the requested `sort_by` value in the requested `sort_direction` (descending by default); the seller echoes the metric and direction it actually applied in `by_*_sorted_by`/`by_*_sort_direction`, which sellers MUST return whenever the breakdown array is present. Spot rows always use `aired_at` ascending and carry neither field.

Demographic reporting also returns `by_demographic_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.

Expand Down
2 changes: 1 addition & 1 deletion 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/outcome-target.test.cjs 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/compact-product-lifecycle-storyboards.test.cjs tests/timezone-resolution-storyboards.test.cjs tests/schema-deprecation-metadata.test.cjs tests/products-only-brief-compatibility.test.cjs tests/async-identity-convergence.test.cjs tests/creative-rotation.test.cjs tests/metric-identity-coherence.test.cjs tests/lint-schema-enum-drift.test.cjs tests/synthetic-depiction.test.cjs tests/creative-rendering-authority.test.cjs && npm run test:premium-display-formats && npm run test:geo-region-targeting",
"test:schemas": "node tests/schema-validation.test.cjs && node --test tests/outcome-target.test.cjs 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/compact-product-lifecycle-storyboards.test.cjs tests/timezone-resolution-storyboards.test.cjs tests/schema-deprecation-metadata.test.cjs tests/products-only-brief-compatibility.test.cjs tests/async-identity-convergence.test.cjs tests/creative-rotation.test.cjs tests/metric-identity-coherence.test.cjs tests/sort-contract-delivery-reporting.test.cjs tests/lint-schema-enum-drift.test.cjs tests/synthetic-depiction.test.cjs tests/creative-rendering-authority.test.cjs && npm run test:premium-display-formats && 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
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,13 @@
},
"sort_by": {
"$ref": "/schemas/enums/sort-metric.json",
"description": "Metric to sort breakdown rows by (descending). Falls back to 'spend' if the seller does not report the requested metric.",
"description": "Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response.",
"default": "spend"
},
"sort_direction": {
"$ref": "/schemas/enums/sort-direction.json",
"description": "Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback.",
"default": "desc"
}
},
"required": [
Expand Down Expand Up @@ -213,8 +218,13 @@
},
"sort_by": {
"$ref": "/schemas/enums/sort-metric.json",
"description": "Metric to sort breakdown rows by (descending). Falls back to 'spend' if the seller does not report the requested metric.",
"description": "Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response.",
"default": "spend"
},
"sort_direction": {
"$ref": "/schemas/enums/sort-direction.json",
"description": "Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback.",
"default": "desc"
}
},
"additionalProperties": true
Expand All @@ -230,8 +240,13 @@
},
"sort_by": {
"$ref": "/schemas/enums/sort-metric.json",
"description": "Metric to sort breakdown rows by (descending). Falls back to 'spend' if the seller does not report the requested metric.",
"description": "Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response.",
"default": "spend"
},
"sort_direction": {
"$ref": "/schemas/enums/sort-direction.json",
"description": "Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback.",
"default": "desc"
}
},
"additionalProperties": true
Expand All @@ -248,8 +263,13 @@
},
"sort_by": {
"$ref": "/schemas/enums/sort-metric.json",
"description": "Metric to sort breakdown rows by (descending). Falls back to 'spend' if the seller does not report the requested metric.",
"description": "Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response.",
"default": "spend"
},
"sort_direction": {
"$ref": "/schemas/enums/sort-direction.json",
"description": "Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback.",
"default": "desc"
}
},
"additionalProperties": true
Expand All @@ -275,8 +295,13 @@
},
"sort_by": {
"$ref": "/schemas/enums/sort-metric.json",
"description": "Metric to sort breakdown rows by (descending). Falls back to 'spend' if the seller does not report the requested metric.",
"description": "Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response.",
"default": "spend"
},
"sort_direction": {
"$ref": "/schemas/enums/sort-direction.json",
"description": "Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback.",
"default": "desc"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -305,8 +330,13 @@
},
"sort_by": {
"$ref": "/schemas/enums/sort-metric.json",
"description": "Metric to sort breakdown rows by (descending). Falls back to 'spend' if the seller does not report the requested metric.",
"description": "Metric to sort breakdown rows by, in `sort_direction` order (descending by default). Falls back to 'spend' when the seller does not report the requested metric at this breakdown's row grain; on fallback the sort direction resets to 'desc'. Rows lacking a value for the applied sort metric order last regardless of direction. The applied sort is echoed in the response.",
"default": "spend"
},
"sort_direction": {
"$ref": "/schemas/enums/sort-direction.json",
"description": "Direction for sort_by ordering. Defaults to 'desc' (largest first). 'asc' enables bottom-N queries (e.g., the 25 worst placements by viewable_rate) that cannot be recovered from a truncated descending pull. Sellers MUST apply the requested direction to the applied sort metric — direction has no availability fallback.",
"default": "desc"
}
},
"additionalProperties": true
Expand Down
Loading
Loading