> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# get_media_buys

> get_media_buys task — retrieve media buy status in AdCP including creative approvals, missing assets, configuration, and optional near-real-time delivery snapshots.

Retrieve the current operational state of media buys: configuration, creative approval status, missing assets, and optional near-real-time delivery snapshots.

**Response Time**: \~1 second

## Scope of Results

Sales agents MUST return every media buy owned by the authenticated account, regardless of how the buy was created — via AdCP [`create_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/create_media_buy), via the seller's own APIs, via manual trafficking, via legacy or third-party systems. Scope is **account ownership**, not creation surface. A `media_buy_id` returned here identifies any order in the seller's ad server accessible to the authenticated caller.

Any media buy returned by `get_media_buys` MUST be reachable by every task in its `valid_actions`. Sales agents MUST NOT mark a buy read-only, hide it, or refuse updates on the basis that it was not originally created via AdCP. When an action is unavailable for business reasons (contractual obligations, platform constraints, policy), the seller MUST omit only that action from `valid_actions` — never the whole set, and never merely because the buy was created outside AdCP. A seller that returns non-AdCP buys with a systematically empty `valid_actions` is non-conformant; that pattern is indistinguishable from hiding the buy.

Sellers that need to partition inventory away from a caller MUST do so at the **account boundary**, not within-account. See [Account Ownership vs. Creation Surface](/dist/docs/3.2.0-beta.0/media-buy/specification#account-ownership-vs-creation-surface).

**Request Schema**: [`/schemas/3.2.0-beta.0/media-buy/get-media-buys-request.json`](https://adcontextprotocol.org/schemas/3.2.0-beta.0/media-buy/get-media-buys-request.json)
**Response Schema**: [`/schemas/3.2.0-beta.0/media-buy/get-media-buys-response.json`](https://adcontextprotocol.org/schemas/3.2.0-beta.0/media-buy/get-media-buys-response.json)

## Request Parameters

| Parameter                  | Type                                                                                               | Required | Description                                                                                                                                                                                                                                                                                                                           |
| -------------------------- | -------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account`                  | [account-ref](/dist/docs/3.2.0-beta.0/building/by-layer/L2/accounts-and-agents#account-references) | No       | Account reference. Pass `{ "account_id": "..." }` or `{ "brand": {...}, "operator": "..." }` if the seller supports implicit resolution. When omitted, returns data across all accessible accounts.                                                                                                                                   |
| `media_buy_ids`            | string\[]                                                                                          | No\*     | Array of media buy IDs to retrieve                                                                                                                                                                                                                                                                                                    |
| `status_filter`            | string \| string\[]                                                                                | No       | Status filter: `"pending_creatives"`, `"pending_start"`, `"active"`, `"paused"`, `"completed"`, `"rejected"`, `"canceled"`. Defaults to `["active"]` only when `media_buy_ids` is omitted.                                                                                                                                            |
| `indicator_types`          | string\[]                                                                                          | No       | Return buys with at least one requested current indicator on the buy, a package, or a package–creative assignment. Buyers MUST request only advertised types; sellers MAY reject unsupported filtering with [`UNSUPPORTED_FEATURE`](/dist/docs/3.2.0-beta.0/building/verification/compliance-catalog#error-code-unsupported-feature). |
| `include_snapshot`         | boolean                                                                                            | No       | When true, include near-real-time delivery snapshots for each package. Defaults to `false`.                                                                                                                                                                                                                                           |
| `include_history`          | integer                                                                                            | No       | Include the last N revision history entries per media buy (returns min(N, available)). 0 or omit to exclude. Max 1000.                                                                                                                                                                                                                |
| `include_webhook_activity` | boolean                                                                                            | No       | When true, each media buy includes a `webhook_activity` array with recent delivery-report webhook fires for the calling principal. Defaults to `false`. See [Webhook activity](#webhook-activity).                                                                                                                                    |
| `webhook_activity_limit`   | integer                                                                                            | No       | Per-buy cap on returned webhook records (most-recent first). Range 1–200, default 50. Ignored when `include_webhook_activity` is false.                                                                                                                                                                                               |
| `pagination`               | object                                                                                             | No       | Cursor-based pagination controls (`max_results`, `cursor`) for broad queries.                                                                                                                                                                                                                                                         |

\*`media_buy_ids` filters results to specific media buys. If neither is provided, the query is scope-based and uses `status_filter` + `pagination`.

When `media_buy_ids` are provided, no implicit status filtering is applied. Pass `status_filter` explicitly if you want to filter identified buys by status.

### Deterministic filter contract

Every returned `media_buys[]` row MUST have a `media_buy_id` in
`media_buy_ids` when that filter is present and a `status` in `status_filter`
when that filter is present. When both are supplied, both predicates apply to
every row, across every page. A seller MUST NOT accept a schema-valid filter and
return unfiltered account results.

A filter can validly leave the result unchanged when every accessible buy
already matches. Conformance is established from the returned rows' ID and
status membership—not by requiring filtered and unfiltered response payloads to
differ.

## Response

Returns an array of media buys with current status, creative approval state, and optionally delivery snapshots:

| Field        | Description                                                               |
| ------------ | ------------------------------------------------------------------------- |
| `media_buys` | Array of media buy objects                                                |
| `pagination` | Cursor pagination metadata (`has_more`, `cursor`, optional `total_count`) |
| `errors`     | Task-specific errors (e.g., media buy not found)                          |

### Media Buy Object

<Note>
  **3.1 vocabulary note.** `get_media_buys` returns the lifecycle state on a nested `media_buys[].status` field (no envelope collision — nested at depth 1). [`create_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/create_media_buy) and [`update_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/update_media_buy) success responses return the same 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](https://github.com/adcontextprotocol/adcp/issues/4905)). Buyers maintaining cross-call state should treat the two as the same logical value. See [Migration › `media_buy_status`](/dist/docs/3.2.0-beta.0/reference/migration/media-buy-status) for the full picture.
</Note>

| Field                            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `media_buy_id`                   | Seller's media buy identifier                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `name`                           | Persisted human-readable label for trafficking UI display and buyer-seller communication. Sellers MUST return it for AdCP-created buys that supplied `name`; it may be absent for external buys or buys created without one. It is not an identifier or financial reference.                                                                                                                                                                                                                            |
| `accepted_proposal_id`           | Current immutable commercial snapshot used to begin [`refine_proposals`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/refine_proposals); present for compact-lifecycle buys.                                                                                                                                                                                                                                                                                                                        |
| `accepted_proposal_terms_digest` | Digest binding the current accepted proposal terms for recovery and governance verification.                                                                                                                                                                                                                                                                                                                                                                                                            |
| `accepted_proposal`              | Complete accepted canonical proposal for compact-lifecycle buys. Together with the ID and digest, this lets a restarted client refine the current commercial envelope without reconstructing historical offers.                                                                                                                                                                                                                                                                                         |
| `invoice_recipient`              | Per-buy invoice recipient when provided at creation. Confirms the seller accepted the billing override. Bank details are omitted (write-only).                                                                                                                                                                                                                                                                                                                                                          |
| `status`                         | Current status (`pending_creatives`, `pending_start`, `active`, `paused`, `completed`, `rejected`, `canceled`). Maps to `media_buy_status` on `create_media_buy` / `update_media_buy` success responses (3.1 vocabulary note above).                                                                                                                                                                                                                                                                    |
| `status_as_of`                   | ISO 8601 timestamp when the seller last refreshed the returned media-buy-level `status` from its source of truth. For rolled-up statuses, this MUST NOT be later than the oldest upstream status observation that could affect the returned roll-up. Optional; omitted or `null` makes no freshness assertion, and buyers MUST NOT infer live status from absence. Use this to interpret cached or rolled-up statuses separately from `updated_at`, which means the media buy's last modification time. |
| `currency`                       | ISO 4217 currency for media-buy-level monetary values                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `total_budget`                   | Hard aggregate lifetime budget (in `currency`)                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `daily_budget_cap`               | Current hard aggregate spend ceiling per calendar day. It limits total buy spend without allocating package amounts.                                                                                                                                                                                                                                                                                                                                                                                    |
| `budget_cap_timezone`            | Shared IANA calendar-day boundary for every aggregate and package daily cap. Present whenever any daily cap is set.                                                                                                                                                                                                                                                                                                                                                                                     |
| `budget_allocation`              | Fixed or seller-optimized cross-package allocation configuration. Omission means fixed for legacy buys.                                                                                                                                                                                                                                                                                                                                                                                                 |
| `pacing`                         | Aggregate media-buy pacing strategy                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `bidding`                        | Media-buy-authored bidding policy. Packages that inherit it omit their own `bidding` block.                                                                                                                                                                                                                                                                                                                                                                                                             |
| `creative_deadline`              | Creative upload deadline (ISO 8601)                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `confirmed_at`                   | ISO 8601 timestamp when the seller committed to this media buy. May be `null` until seller commitment occurs in deferred/manual approval flows; stable after it is set.                                                                                                                                                                                                                                                                                                                                 |
| `cancellation`                   | Cancellation metadata (present only when `status` is `canceled`). Object with `canceled_at` (ISO 8601), `canceled_by` (`"buyer"` or `"seller"`), and optional `reason`.                                                                                                                                                                                                                                                                                                                                 |
| `revision`                       | Current revision number. Pass in [`control_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/control_media_buy) or the 3.x `update_media_buy` facade for optimistic concurrency.                                                                                                                                                                                                                                                                                                             |
| `valid_actions`                  | Actions the buyer can perform in the current state (e.g., `["pause", "cancel", "update_budget"]`). See [valid actions mapping](#valid-actions-mapping).                                                                                                                                                                                                                                                                                                                                                 |
| `available_actions`              | Canonical routed actions for compact-lifecycle buys, including whether each action uses [`control_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/control_media_buy), proposal refinement, or another task. Required whenever `accepted_proposal` is present.                                                                                                                                                                                                                              |
| `indicators`                     | Current buy-level seller indicators. In 3.2, `budget_constrained` is valid here. Sibling evaluation fields define exact type coverage and freshness.                                                                                                                                                                                                                                                                                                                                                    |
| `history`                        | Revision history entries, most recent first. Only present when `include_history > 0`. Append-only — entries are never modified or deleted.                                                                                                                                                                                                                                                                                                                                                              |
| `webhook_activity`               | Recent delivery, health, `indicators.changed`, and `creative.assignment_changed` attempts relevant to this buy. Only present when requested and supported.                                                                                                                                                                                                                                                                                                                                              |
| `context`                        | Opaque media-buy-level correlation data echoed unchanged from `create_media_buy`. Sellers MUST include persisted context when the media buy was created through AdCP with context, and MAY omit it for media buys created outside AdCP or without context. Use it to reconcile `media_buy_id` with buyer tracking state.                                                                                                                                                                                |
| `packages`                       | Array of packages with creative status and optional snapshots                                                                                                                                                                                                                                                                                                                                                                                                                                           |

### Package Object

| Field                         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package_id`                  | Seller's package identifier                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `product_id`                  | Product identifier this package is purchased from. For packages created from an explicit `create_media_buy` package request, sellers MUST echo the request package's `product_id` on every response package object that represents that requested package.                                                                                                                                                                                                                                                                |
| `budget`                      | Hard lifetime package spend cap in `media_buy.currency`. In seller-optimized mode this is not a current allocation and may be absent.                                                                                                                                                                                                                                                                                                                                                                                     |
| `daily_budget_cap`            | Optional subordinate hard package ceiling per shared media-buy cap day. It is not a reserved allocation.                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `min_spend_target`            | Soft lifetime package spend target in `media_buy.currency` accepted for seller-optimized allocation.                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `pacing`                      | Package pacing preference. It is subordinate to media-buy aggregate `pacing` and cannot cause aggregate delivery to exceed that strategy.                                                                                                                                                                                                                                                                                                                                                                                 |
| `currency`                    | Legacy/external-buy package denomination. For AdCP-authored buys this equals `media_buy.currency`; it does not reinterpret canonical budget or bidding amounts.                                                                                                                                                                                                                                                                                                                                                           |
| `bidding`                     | Package-authored bidding override. `{automatic: true}` explicitly overrides media-buy controls; omission inherits media-buy `bidding`.                                                                                                                                                                                                                                                                                                                                                                                    |
| `bid_price`                   | Deprecated legacy bid representation; 3.2 sellers should normalize and echo `bidding`.                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `optimization_goals`          | Current package objective functions, separate from currency-bearing `bidding` policy.                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `format_ids`                  | Legacy named-format IDs supplied on `create_media_buy`, echoed whenever the original request included them, including dual-emission cases where another selector won precedence.                                                                                                                                                                                                                                                                                                                                          |
| `format_option_refs`          | Structured 3.1+ format option references supplied on `create_media_buy`, echoed whenever the original request included them.                                                                                                                                                                                                                                                                                                                                                                                              |
| `format_kind`                 | Direct canonical selector supplied on `create_media_buy`, echoed whenever the original request included it, including informational-echo cases where another selector won precedence.                                                                                                                                                                                                                                                                                                                                     |
| `params`                      | Parameters for the direct canonical selector in `format_kind`, echoed whenever the original request included them; requires `format_kind`.                                                                                                                                                                                                                                                                                                                                                                                |
| `targeting_overlay`           | Complete effective targeting currently applied to the package, including configured-product constraints and later buyer additions. Property, collection, and placement selection are represented here alongside audience and geography.                                                                                                                                                                                                                                                                                   |
| `targeting_resolution`        | Generic targeting execution readback. When demographics were requested or applied, `targeting_resolution.demographics` contains the exact-only `equivalent: true` predicate, execution mechanism, and effective determination bases. Other targeting dimensions can evolve inside the same envelope.                                                                                                                                                                                                                      |
| `start_time`                  | Flight start time (ISO 8601). Check this before interpreting delivery status.                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `end_time`                    | Flight end time (ISO 8601)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `paused`                      | Whether buyer has paused this package                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `canceled`                    | Whether this package has been canceled (irreversible)                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `cancellation`                | Cancellation metadata (present only when `canceled` is true). Object with `canceled_at` (ISO 8601), `canceled_by` (`"buyer"` or `"seller"`), and optional `reason`.                                                                                                                                                                                                                                                                                                                                                       |
| `creative_deadline`           | Per-package creative deadline (ISO 8601). When absent, the media buy's `creative_deadline` applies.                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `context`                     | Opaque package-level correlation data echoed unchanged from the `create_media_buy` package request. Sellers MUST include persisted context when the package was created through AdCP with context, and MAY omit it for packages created outside AdCP or without context. Buyers targeting mixed seller populations can use the per-package correlation value they included on create, commonly `context.buyer_ref`, to map `package_id` back to their line items when a legacy create response did not echo `product_id`. |
| `creative_approvals`          | Array of creative approval states (see below)                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `indicators`                  | Current package-level seller indicators such as diversity, audience saturation, inventory forecast, pacing, or budget constraint                                                                                                                                                                                                                                                                                                                                                                                          |
| `formats_pending`             | Canonical package-time format declarations from `formats_to_provide` that do not yet have creative coverage. Full declarations let buyers select a compatible creative agent even when no option ID exists.                                                                                                                                                                                                                                                                                                               |
| `format_ids_pending`          | **Deprecated in 3.2.** Legacy named-format projection of `formats_pending` for older 3.x peers.                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `snapshot_unavailable_reason` | Reason code when `include_snapshot: true` but no snapshot is returned for this package                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `snapshot`                    | Near-real-time delivery snapshot (when `include_snapshot: true`)                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

### Creative Approval Object

| Field                        | Description                                                                                 |
| ---------------------------- | ------------------------------------------------------------------------------------------- |
| `creative_id`                | Creative identifier                                                                         |
| `approval_status`            | `pending_review`, `approved`, `partially_approved`, or `rejected`                           |
| `rejection_reason`           | Explanation of rejection (when `approval_status` is `rejected`)                             |
| `approval_scopes`            | Complete publisher/placement outcomes when `approval_status` is `partially_approved`        |
| `indicators`                 | Current seller indicators scoped to this package–creative relationship                      |
| `indicator_types_evaluated`  | Exact indicator types covered by this snapshot; omitted types remain unknown                |
| `indicators_as_of`           | When the seller completed the evaluation represented by `indicators`                        |
| `indicators_evaluated_scope` | Optional publisher or placement coverage; omission means the whole assignment was evaluated |

Indicators are deliberately attached here rather than to the creative globally. A creative may be fatigued in one package or publisher context while remaining effective elsewhere.

```json theme={null}
{
  "creative_id": "acme_summer_video",
  "approval_status": "approved",
  "indicator_types_evaluated": ["creative_fatigue"],
  "indicators_as_of": "2026-08-04T12:00:00Z",
  "indicators": [
    {
      "type": "creative_fatigue",
      "detected_at": "2026-08-03T09:00:00Z"
    }
  ]
}
```

The media buy itself may also carry `budget_constrained`; packages may carry `creative_diversity_low`, `audience_saturation`, `inventory_shortfall_forecast`, `pacing_risk`, or `budget_constrained`. Assignment types remain nested under `creative_approvals[]`. Omitted `indicators` is unknown; empty is evaluated-clear only for `indicator_types_evaluated` and declared coverage. See [Indicators and Warnings](/dist/docs/3.2.0-beta.0/media-buy/media-buys/indicators).

For sellers that advertise `inline_creative_management` without a creative library, `creative_approvals` is the only standardized approval-state readback surface for the package's currently assigned inline creatives. It reports `creative_id`, aggregate `approval_status`, optional `rejection_reason`, and `approval_scopes` for mixed publisher outcomes, but it does not include the full `CreativeAsset` payload, placement routing, weights, or prior revisions submitted on `create_media_buy` or `update_media_buy`. Buyers should retain their own submitted creative bodies when integrating with inline-only sellers.

Creative revisions are represented as `approval_status: "rejected"` with a specific `rejection_reason`. There is no package-level `input-required` status for creative edits; upload corrected library assets via [`sync_creatives`](/dist/docs/3.2.0-beta.0/creative/task-reference/sync_creatives), or corrected inline-only package assets via `packages[].creatives` on [`update_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/update_media_buy).

### History Entry Object

| Field        | Required | Description                                                                                                                                                                                                     |
| ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `revision`   | Yes      | Revision number after this change was applied                                                                                                                                                                   |
| `timestamp`  | Yes      | ISO 8601 timestamp when this change occurred                                                                                                                                                                    |
| `action`     | Yes      | What happened: `created`, `activated`, `paused`, `resumed`, `canceled`, `rejected`, `completed`, `updated_budget`, `updated_dates`, `updated_packages`, `package_canceled`, `package_paused`, `package_resumed` |
| `actor`      | No       | Identity of who made the change (server-derived from auth context, not caller-provided)                                                                                                                         |
| `summary`    | No       | Human-readable description (e.g., "Budget changed from $5,000 to $7,500 on pkg\_abc")                                                                                                                           |
| `package_id` | No       | Package affected, when the change targeted a specific package                                                                                                                                                   |

History entries are **append-only** — sellers MUST NOT modify or delete previously emitted entries. Callers MAY cache entries by revision number.

`revision` increments only when the seller applies a mutating state change or update. Reads, validation-only calls, and exact idempotency replays do not create history entries or bump revision. Buyers should treat the returned revision as the token for their next `update_media_buy` call intended to change state.

`confirmed_at` is not a delivery status timestamp. It records seller commitment and remains stable through later pause/resume, activation, completion, cancellation, and reporting changes.

### Snapshot Object

| Field               | Description                                                                                                                                                      |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `as_of`             | ISO 8601 timestamp when the platform captured this snapshot                                                                                                      |
| `staleness_seconds` | Maximum data age in seconds. Use this to interpret zero delivery: 900 (15 min) means zero is likely real; 14400 (4 hr) means reporting may still be catching up. |
| `impressions`       | Total impressions delivered since package start                                                                                                                  |
| `spend`             | Total spend since package start                                                                                                                                  |
| `currency`          | Optional snapshot currency override for `spend`                                                                                                                  |
| `clicks`            | Total clicks since package start (when available)                                                                                                                |
| `pacing_index`      | Delivery pace (1.0 = on track, \<1.0 = behind, >1.0 = ahead)                                                                                                     |
| `delivery_status`   | `delivering`, `not_delivering`, `completed`, `budget_exhausted`, `flight_ended`, `goal_met`                                                                      |
| `ext`               | Optional extension object for seller-specific operational fields                                                                                                 |

**`not_delivering`** means the package is within its scheduled flight but has delivered zero impressions for at least one full staleness cycle. Implementers must not return `not_delivering` until `staleness_seconds` have elapsed since package activation — a new package with no impressions in its first minutes is expected, not a problem. Check `start_time` to confirm the package is within its flight before acting on this status.

For AdCP-authored buys, budget constraints and canonical bidding always use `media_buy.currency`. `snapshot.currency` identifies the reporting denomination for snapshot spend, and legacy/external buys may still carry `package.currency`; neither changes the denomination of buyer-authored 3.2 controls.

### Webhook Activity

When `include_webhook_activity: true`, each returned media buy MAY carry a `webhook_activity` array describing recent webhook fires relevant to that buy. It covers both per-buy reporting/health events and account-level `indicators.changed` or `creative.assignment_changed` invalidations whose payload names the buy. This is the buyer-side debug surface for the [persistent-channel webhook contract](/dist/docs/3.2.0-beta.0/building/by-layer/L3/webhooks#persistent-channel-contract).

The record shape, request-field names, scoping, retention floor, three-state presence, and cardinality rules are uniform across AdCP resources that adopt this surface. See [Webhook activity log pattern](/dist/docs/3.2.0-beta.0/protocol/snapshot-and-log#webhook-activity-log-pattern) on the snapshot/log contract page for the cross-resource normative section — the rules below restate it for the media-buy call site and add the media-buy-specific capability gate.

Per-buy delivery events (`scheduled`, `final`, `delayed`, `adjusted`, `window_update`) and health events (`impairment`) use the buy's `push_notification_config`; their `subscriber_id` may be absent in a single-subscriber setup. Indicator and assignment invalidations use account `notification_configs[]`; their `subscriber_id` is required. Support for the latter is declared by `media_buy.relationship_notifications`, including whether this activity log is available. All share the same webhook delivery contract and buyer-side debug need.

| Field                | Description                                                                                                                                                                                                                                                                                                                                                      |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idempotency_key`    | Equals the `idempotency_key` carried in the webhook payload itself ([§ Dedup by `idempotency_key`](/dist/docs/3.2.0-beta.0/building/by-layer/L3/webhooks#dedup-by-idempotency_key)). Stable across retries of the same logical fire — buyers correlate this surface with their own endpoint logs via this exact field. Reference it when filing support tickets. |
| `notification_id`    | Optional event-layer id copied from the webhook payload when the notification type defines one. Sellers SHOULD populate it on newly recorded attempts and MUST preserve the payload value when they do. It may be absent on retained records created before 3.2; point-in-time delivery-report fires do not define it.                                           |
| `subscriber_id`      | Identifies which registered webhook subscriber received this fire. Required for account-level indicator/assignment invalidations; may be absent for a single per-buy subscriber. Per-buy channels populate it once they support multiple subscribers (4.0+, see [#3009](https://github.com/adcontextprotocol/adcp/issues/3009)).                                 |
| `fired_at`           | ISO 8601 timestamp when the seller initiated this attempt.                                                                                                                                                                                                                                                                                                       |
| `completed_at`       | ISO 8601 timestamp when the response was observed (or terminal failure). Null while `status` is `pending`.                                                                                                                                                                                                                                                       |
| `notification_type`  | Verbatim from the webhook payload: `scheduled`, `final`, `delayed`, `adjusted`, `window_update`, `impairment`, `indicators.changed`, or `creative.assignment_changed`.                                                                                                                                                                                           |
| `sequence_number`    | Sequence number from the webhook payload — useful for spotting stale-sequence drops or gaps. Absent when the notification type does not carry one.                                                                                                                                                                                                               |
| `attempt`            | 1-indexed retry counter for this logical fire. Initial fire is `attempt: 1`.                                                                                                                                                                                                                                                                                     |
| `status`             | `success`, `failed`, `timeout`, `connection_error`, or `pending`. See semantics below.                                                                                                                                                                                                                                                                           |
| `url`                | Target URL with **query string and fragment stripped**, and high-entropy / token-shaped path segments redacted. Match this against your registered URL by origin + path, not full URL.                                                                                                                                                                           |
| `http_status_code`   | HTTP status from the buyer's endpoint. Null when no HTTP response was received (`timeout`, `connection_error`, `pending`).                                                                                                                                                                                                                                       |
| `response_time_ms`   | Wall-clock latency between request send and response receipt. Null for non-completed attempts.                                                                                                                                                                                                                                                                   |
| `payload_size_bytes` | Size of the request body the seller sent — useful for diagnosing oversized-payload rejections.                                                                                                                                                                                                                                                                   |
| `error_message`      | Short human-readable server-side classification of failure. Null for `success`. Sellers MUST NOT include request / response bodies or headers here.                                                                                                                                                                                                              |

**Status semantics:**

* `success` — response received with a 2xx status. `http_status_code` populated.
* `failed` — response received with a non-2xx status. `http_status_code` populated; `error_message` describes the response.
* `timeout` — no response within the seller's configured timeout. `http_status_code` null. Operationally: the buyer's endpoint is reachable but slow / overloaded.
* `connection_error` — DNS, TLS, or socket failure before any HTTP response. `http_status_code` null. Operationally: the buyer's endpoint is unreachable or misconfigured.
* `pending` — attempt is in flight or queued for retry. `completed_at` is null; subsequent attempts appear with the same `idempotency_key` and incremented `attempt`.

**Record cardinality:** one record per attempt. A successful first-attempt fire appears as a single record with `attempt: 1`. A 3-attempt retry trail (e.g., two failures then a success) appears as three records sharing `idempotency_key`.

**Scoping (normative):**

* `webhook_activity` MUST be scoped to the **calling principal**. When multiple buyer principals share visibility into the same media buy via account-level access, each principal sees only fires targeting its own endpoint.
* Sellers that surface this field **MUST** retain records for at least 30 days from each record's `completed_at` — uniformly across `success`, `failed`, `timeout`, and `connection_error` outcomes (all of which populate `completed_at`). For records still in `pending` status, the clock runs from `fired_at` until the attempt terminates and then transitions to 30 days from `completed_at` — retry trails do not age out mid-flight. Sellers that cannot honor this floor MUST omit the field entirely rather than return a shorter window; the three-state presence semantics give them a clean opt-out and buyers a single guarantee they can build against.
* This surface is a debug aid, not a full audit log. There is no cursor for older fires beyond `webhook_activity_limit` — buyers needing full history must persist webhook records on their own side.

**Three-state presence semantics:**

| State             | Meaning                                                                                                                                                                                                                                                                           |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Field **omitted** | Seller does not surface an applicable activity log for this buy. Neither a declared per-buy webhook surface nor `media_buy.relationship_notifications.supports_webhook_activity` provides one, no relevant subscriber is registered, or the seller does not persist fire history. |
| Empty array `[]`  | Seller persists fire history but has fired nothing recent for this principal.                                                                                                                                                                                                     |
| Non-empty array   | Actual fire records, most-recent first.                                                                                                                                                                                                                                           |

For per-buy events, sellers whose declared `propagation_surfaces` excludes `webhook` omit those records. That does not suppress account-level indicator/assignment records when `media_buy.relationship_notifications.supports_webhook_activity` is true.

**Diagnosing an unexpected omission.** For a per-buy event, check the buy's `push_notification_config` and the seller's `capabilities.media_buy.propagation_surfaces` via [`get_adcp_capabilities`](/dist/docs/3.2.0-beta.0/protocol/get_adcp_capabilities). For an indicator or assignment event, check the account's `notification_configs[]` and `media_buy.relationship_notifications.supports_webhook_activity`. If the relevant registration and capability both check out, omission is a seller-side persistence gap and warrants an operator ticket.

**Privacy:**

* The `url` field has its query string and fragment **stripped**, and sellers SHOULD redact path segments resembling shared secrets (high-entropy random material, UUID / token shapes).
* Request and response bodies are **not surfaced** by this field. A future `include_webhook_payloads` extension may add them under stricter authorization controls — out of scope here.
* `error_message` is a server-side classification string only — never request headers, never response bodies, never buyer-endpoint stack traces.

#### Diagnose a webhook delivery problem

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { testAgent } from '@adcp/sdk/testing';
  import { GetMediaBuysResponseSchema, type WebhookActivityRecord } from '@adcp/sdk';

  // The WebhookActivityRecord type is regenerated by the SDK from
  // /schemas/core/webhook-activity-record.json — once the SDK rebuilds against this
  // branch's schemas the import resolves. The same type appears on every AdCP resource
  // that surfaces webhook_activity[], so debug helpers can be written once and reused.
  function latestAttempt(trail: WebhookActivityRecord[]): WebhookActivityRecord {
    return trail.reduce((a, b) => (a.attempt >= b.attempt ? a : b));
  }

  const result = await testAgent.getMediaBuys({
    media_buy_ids: ['mb_12345'],
    include_webhook_activity: true,
    webhook_activity_limit: 20,
  });

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

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

  for (const mediaBuy of validated.media_buys) {
    // Three-state semantics — distinguish "seller does not surface" from "no recent fires".
    if (mediaBuy.webhook_activity === undefined) {
      console.log(`${mediaBuy.media_buy_id}: seller does not surface webhook activity for this buy`);
      continue;
    }

    const fires = mediaBuy.webhook_activity;
    if (fires.length === 0) {
      console.log(`${mediaBuy.media_buy_id}: no recent fires for this principal`);
      continue;
    }

    // Group attempts by idempotency_key so we can see the retry trail per logical fire.
    const trails = new Map();
    for (const fire of fires) {
      const trail = trails.get(fire.idempotency_key) ?? [];
      trail.push(fire);
      trails.set(fire.idempotency_key, trail);
    }

    for (const [idempotencyKey, trail] of trails) {
      // Pick the latest attempt by `attempt` number — robust against any iteration order.
      const latest = latestAttempt(trail);
      if (latest.status === 'success') continue;

      const detail = latest.error_message ?? latest.http_status_code ?? '—';
      console.log(
        `${mediaBuy.media_buy_id} ${idempotencyKey} ` +
        `(${latest.notification_type} seq=${latest.sequence_number}): ` +
        `${latest.status} after ${trail.length} attempt(s) — ${detail}`
      );
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  from collections import defaultdict
  from adcp.testing import test_agent
  from adcp.types import GetMediaBuysRequest, WebhookActivityRecord

  # WebhookActivityRecord is regenerated by the SDK from
  # /schemas/core/webhook-activity-record.json — once the SDK rebuilds against this
  # branch's schemas the import resolves. The same type appears on every AdCP resource
  # that surfaces webhook_activity[].
  def latest_attempt(trail: list[WebhookActivityRecord]) -> WebhookActivityRecord:
      return max(trail, key=lambda f: f.attempt)

  async def main():
      result = await test_agent.get_media_buys(
          GetMediaBuysRequest(
              media_buy_ids=['mb_12345'],
              include_webhook_activity=True,
              webhook_activity_limit=20,
          )
      )

      for media_buy in result.media_buys:
          # Three-state semantics: distinguish "seller does not surface" from "no recent fires".
          if media_buy.webhook_activity is None:
              print(f"{media_buy.media_buy_id}: seller does not surface webhook activity for this buy")
              continue

          fires = media_buy.webhook_activity
          if not fires:
              print(f"{media_buy.media_buy_id}: no recent fires for this principal")
              continue

          trails = defaultdict(list)
          for fire in fires:
              trails[fire.idempotency_key].append(fire)

          for idempotency_key, trail in trails.items():
              latest = latest_attempt(trail)
              if latest.status == 'success':
                  continue

              detail = latest.error_message or latest.http_status_code or '—'
              print(f"{media_buy.media_buy_id} {idempotency_key} "
                    f"({latest.notification_type} seq={latest.sequence_number}): "
                    f"{latest.status} after {len(trail)} attempt(s) — {detail}")

  asyncio.run(main())
  ```
</CodeGroup>

## Valid Actions Mapping

The `valid_actions` array tells agents what operations are permitted on a media buy in its current state. Sellers SHOULD include this field. Expected values by status:

| Status              | Expected `valid_actions`                                                                                 |
| ------------------- | -------------------------------------------------------------------------------------------------------- |
| `pending_creatives` | `pause`, `cancel`, [`sync_creatives`](/dist/docs/3.2.0-beta.0/creative/task-reference/sync_creatives)    |
| `pending_start`     | `pause`, `cancel`, `sync_creatives`                                                                      |
| `active`            | `pause`, `cancel`, `update_budget`, `update_dates`, `update_packages`, `add_packages`, `sync_creatives`  |
| `paused`            | `resume`, `cancel`, `update_budget`, `update_dates`, `update_packages`, `add_packages`, `sync_creatives` |
| `completed`         | *(empty array)*                                                                                          |
| `rejected`          | *(empty array)*                                                                                          |
| `canceled`          | *(empty array)*                                                                                          |

Sellers MAY omit actions based on business rules (e.g., omit `cancel` when the media buy has contractual obligations that prevent cancellation).

For creative changes, `sync_creatives` in `valid_actions` is a legacy creative-change action label, not proof that the `sync_creatives` task exists. Use the creative path the seller advertises: `sync_creatives` and `creative_assignments` for sellers with `creative.has_creative_library: true`, or `packages[].creatives` on [`update_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/update_media_buy) for inline-only sellers.

## Common Scenarios

### Check creative approval status

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/sdk/testing';
  import { GetMediaBuysResponseSchema } from '@adcp/sdk';

  const result = await testAgent.getMediaBuys({
    media_buy_ids: ['mb_12345']
  });

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

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

  if (validated.errors?.length > 0) {
    throw new Error(`Query failed: ${JSON.stringify(validated.errors)}`);
  }

  for (const mediaBuy of validated.media_buys) {
    for (const pkg of mediaBuy.packages) {
      // Check for missing creatives
      if (pkg.formats_pending?.length > 0) {
        const labels = pkg.formats_pending.map(f => f.format_option_id ?? f.format_kind);
        console.log(`Package ${pkg.package_id}: missing formats ${labels.join(', ')}`);
      }

      // Check approval states
      for (const approval of pkg.creative_approvals ?? []) {
        if (approval.approval_status === 'rejected') {
          console.log(`Creative ${approval.creative_id} rejected: ${approval.rejection_reason}`);
        } else if (approval.approval_status === 'partially_approved') {
          console.log(`Creative ${approval.creative_id} has mixed publisher approval`, approval.approval_scopes);
        } else if (approval.approval_status === 'pending_review') {
          console.log(`Creative ${approval.creative_id} pending review`);
        }
      }
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent
  from adcp.types import GetMediaBuysRequest

  async def main():
      result = await test_agent.get_media_buys(
          GetMediaBuysRequest(media_buy_ids=['mb_12345'])
      )

      if result.errors:
          raise Exception(f"Query failed: {result.errors}")

      for media_buy in result.media_buys:
          for pkg in media_buy.packages:
              # Check for missing creatives
              if pkg.formats_pending:
                  labels = [f.format_option_id or f.format_kind for f in pkg.formats_pending]
                  print(f"Package {pkg.package_id}: missing formats {', '.join(labels)}")

              # Check approval states
              for approval in pkg.creative_approvals or []:
                  if approval.approval_status == 'rejected':
                      print(f"Creative {approval.creative_id} rejected: {approval.rejection_reason}")
                  elif approval.approval_status == 'partially_approved':
                      print(f"Creative {approval.creative_id} has mixed publisher approval: {approval.approval_scopes}")
                  elif approval.approval_status == 'pending_review':
                      print(f"Creative {approval.creative_id} pending review")

  asyncio.run(main())
  ```
</CodeGroup>

### Monitor delivery with snapshots

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/sdk/testing';
  import { GetMediaBuysResponseSchema } from '@adcp/sdk';

  const result = await testAgent.getMediaBuys({
    status_filter: 'active',
    include_snapshot: true
  });

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

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

  for (const mediaBuy of validated.media_buys) {
    for (const pkg of mediaBuy.packages) {
      const snap = pkg.snapshot;
      if (!snap) continue;

      if (snap.delivery_status === 'not_delivering') {
        console.log(`Package ${pkg.package_id}: zero delivery (data up to ${snap.staleness_seconds}s old)`);
      } else if (snap.pacing_index !== undefined && snap.pacing_index < 0.8) {
        console.log(`Package ${pkg.package_id}: underpacing at ${(snap.pacing_index * 100).toFixed(0)}%`);
      } else {
        console.log(`Package ${pkg.package_id}: ${snap.impressions.toLocaleString()} impressions, pacing ${snap.pacing_index?.toFixed(2)}`);
      }
    }
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent
  from adcp.types import GetMediaBuysRequest

  async def main():
      result = await test_agent.get_media_buys(
          GetMediaBuysRequest(
              status_filter='active',
              include_snapshot=True
          )
      )

      if result.errors:
          raise Exception(f"Query failed: {result.errors}")

      for media_buy in result.media_buys:
          for pkg in media_buy.packages:
              snap = pkg.snapshot
              if not snap:
                  continue

              if snap.delivery_status == 'not_delivering':
                  print(f"Package {pkg.package_id}: zero delivery (data up to {snap.staleness_seconds}s old)")
              elif snap.pacing_index is not None and snap.pacing_index < 0.8:
                  print(f"Package {pkg.package_id}: underpacing at {snap.pacing_index * 100:.0f}%")
              else:
                  print(f"Package {pkg.package_id}: {snap.impressions:,} impressions, pacing {snap.pacing_index:.2f}")

  asyncio.run(main())
  ```
</CodeGroup>

### Campaign readiness check

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { testAgent } from '@adcp/sdk/testing';
  import { GetMediaBuysResponseSchema } from '@adcp/sdk';

  const result = await testAgent.getMediaBuys({
    media_buy_ids: ['mb_12345']
  });

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

  const validated = GetMediaBuysResponseSchema.parse(result.data);
  const [mediaBuy] = validated.media_buys;
  const issues = [];

  for (const pkg of mediaBuy.packages) {
    if (pkg.formats_pending?.length > 0) {
      issues.push(`Package ${pkg.package_id}: ${pkg.formats_pending.length} format(s) not yet uploaded`);
    }

    const rejected = (pkg.creative_approvals ?? []).filter(a => a.approval_status === 'rejected');
    if (rejected.length > 0) {
      issues.push(`Package ${pkg.package_id}: ${rejected.length} creative(s) rejected`);
    }
  }

  if (issues.length === 0) {
    console.log('Campaign ready to launch');
  } else {
    console.log('Campaign has blocking issues:');
    issues.forEach(issue => console.log(`  - ${issue}`));
  }
  ```

  ```python Python theme={null}
  import asyncio
  from adcp.testing import test_agent
  from adcp.types import GetMediaBuysRequest

  async def main():
      result = await test_agent.get_media_buys(
          GetMediaBuysRequest(media_buy_ids=['mb_12345'])
      )

      media_buy = result.media_buys[0]
      issues = []

      for pkg in media_buy.packages:
          if pkg.formats_pending:
              issues.append(f"Package {pkg.package_id}: {len(pkg.formats_pending)} format(s) not yet uploaded")

          rejected = [a for a in (pkg.creative_approvals or []) if a.approval_status == 'rejected']
          if rejected:
              issues.append(f"Package {pkg.package_id}: {len(rejected)} creative(s) rejected")

      if not issues:
          print("Campaign ready to launch")
      else:
          print("Campaign has blocking issues:")
          for issue in issues:
              print(f"  - {issue}")

  asyncio.run(main())
  ```
</CodeGroup>

## Snapshot vs. `get_media_buy_delivery`

|                     | `get_media_buys` (with snapshot) | [`get_media_buy_delivery`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/get_media_buy_delivery) |
| ------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Purpose**         | Operational monitoring           | Reporting and reconciliation                                                                        |
| **Freshness**       | Minutes (entity-level stats)     | Hours (batch report jobs)                                                                           |
| **Accuracy**        | Best-effort                      | Authoritative, billing-grade                                                                        |
| **Date range**      | Always "since campaign start"    | Configurable period                                                                                 |
| **Daily breakdown** | No                               | Yes                                                                                                 |
| **Creative status** | Yes                              | No                                                                                                  |
| **Missing assets**  | Yes                              | No                                                                                                  |

Use `get_media_buys` to answer "what is the current state of my campaigns?" and `get_media_buy_delivery` for "how did my campaigns perform over a period?"

Status taxonomy is shared for lifecycle filters across both tasks (`pending_creatives`, `pending_start`, `active`, `paused`, `completed`). `get_media_buy_delivery` may additionally return reporting-only statuses (`reporting_delayed`, `failed`) in webhook contexts.

## Data Freshness

Snapshot `staleness_seconds` varies by platform:

| Platform type                                  | Typical `staleness_seconds` |
| ---------------------------------------------- | --------------------------- |
| Entity-level stats (e.g., GAM LineItemService) | 900 (15 min)                |
| Near-real-time insights API                    | 60–300                      |
| Batch-only reporting                           | 14400 (4 hr)                |

When the platform only has batch reporting, the seller agent should return the most recent cached data with the appropriate `staleness_seconds`.

If `include_snapshot: true` and `snapshot` is omitted for a package, check `snapshot_unavailable_reason`:

* `SNAPSHOT_UNSUPPORTED`: the seller does not support package snapshots for this integration
* `SNAPSHOT_TEMPORARILY_UNAVAILABLE`: snapshot pipeline is delayed or degraded; retry later
* `SNAPSHOT_PERMISSION_DENIED`: caller lacks permission to view snapshot metrics for that package

## Pagination

Use cursor pagination for broad status queries to avoid large payloads:

* Request: set `pagination.max_results` (1-100, default 50) and optional `pagination.cursor`
* Response: read `pagination.has_more`; when true, pass `pagination.cursor` into the next request
* ID-targeted queries (`media_buy_ids`) can omit pagination unless the ID set is very large

## Error Handling

| Error Code                                                                                                               | Description                              | Resolution                                            |
| ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | ----------------------------------------------------- |
| [`MEDIA_BUY_NOT_FOUND`](/dist/docs/3.2.0-beta.0/building/verification/compliance-catalog#error-code-media-buy-not-found) | Media buy ID does not exist              | Verify `media_buy_id`                                 |
| [`AUTH_MISSING`](/dist/docs/3.2.0-beta.0/building/verification/compliance-catalog#error-code-auth-missing)               | No credentials presented                 | Provide credentials via auth header                   |
| [`AUTH_INVALID`](/dist/docs/3.2.0-beta.0/building/verification/compliance-catalog#error-code-auth-invalid)               | Credentials rejected (expired / revoked) | Human credential rotation required; do not auto-retry |

A valid query with no matching media buys succeeds with an empty `media_buys` array.

## Next Steps

* **Upload missing creatives**: Use [`sync_creatives`](/dist/docs/3.2.0-beta.0/creative/task-reference/sync_creatives) for library-backed sellers, or `packages[].creatives` on [`update_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/update_media_buy) for inline-only sellers
* **Investigate zero delivery**: Check `delivery_status: "not_delivering"` and `start_time` to confirm the flight is active, then use [`update_media_buy`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/update_media_buy) to adjust pricing or targeting
* **Detailed reporting**: Use [`get_media_buy_delivery`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/get_media_buy_delivery) for date-range reporting and daily breakdowns
* **Optimize campaigns**: Use [`provide_performance_feedback`](/dist/docs/3.2.0-beta.0/media-buy/task-reference/provide_performance_feedback) to share results with the seller
