> ## Documentation Index
> Fetch the complete documentation index at: https://agenticadvertisingorg-feature-feedback.mintlify.site/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`, 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](/docs/media-buy/specification#account-ownership-vs-creation-surface).

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

## Request Parameters

| Parameter                  | Type                                                                             | Required | Description                                                                                                                                                                                         |
| -------------------------- | -------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account`                  | [account-ref](/docs/building/integration/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.          |
| `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.

## 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` and `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`](/docs/reference/migration/media-buy-status) for the full picture.
</Note>

| Field               | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `media_buy_id`      | Seller's media buy identifier                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `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`      | Total campaign budget (in `currency`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `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 `update_media_buy` 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).                                                                                                                                                                                                                                                                                                                                                 |
| `history`           | Revision history entries, most recent first. Only present when `include_history > 0`. Append-only — entries are never modified or deleted.                                                                                                                                                                                                                                                                                                                                                              |
| `webhook_activity`  | Recent reporting and health webhook fires for the calling principal, most-recent first. Only present when `include_webhook_activity` is true AND the seller surfaces fire history for this buy. See [Webhook activity](#webhook-activity) for three-state presence semantics.                                                                                                                                                                                                                           |
| `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.                                                                                                                                                                                                                                                                |
| `currency`                    | Optional package-level currency override (defaults to media buy `currency`)                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `bid_price`                   | Current bid price for auction-based packages (in package `currency` if present, otherwise media buy `currency`)                                                                                                                                                                                                                                                                                                                                                                                                           |
| `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`.                                                                                                                                                                                                                                                                                                                                                                                |
| `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)                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `format_ids_pending`          | Format IDs from `format_ids_to_provide` not yet uploaded                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `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`, or `rejected`                     |
| `rejection_reason` | Explanation of rejection (when `approval_status` is `rejected`) |

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`, `approval_status`, and optional `rejection_reason`, 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`](/docs/creative/task-reference/sync_creatives), or corrected inline-only package assets via `packages[].creatives` on [`update_media_buy`](/docs/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.

Money fields use this currency precedence: `snapshot.currency` -> `package.currency` -> `media_buy.currency`.

### Webhook Activity

When `include_webhook_activity: true`, each returned media buy MAY carry a `webhook_activity` array describing recent reporting and health webhook fires from the seller to the buyer's registered endpoint. This is the buyer-side debug surface for the [persistent-channel webhook contract](/docs/building/by-layer/L3/webhooks#persistent-channel-contract) — buyers use it to verify that the publisher fired, what the buyer's gateway returned, and whether retries are still in flight, without needing an operator-level query against the seller's logs.

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](/docs/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.

The surface covers both delivery-report notification types (`scheduled`, `final`, `delayed`, `adjusted`) and health notification types (`impairment`). All share the same webhook delivery contract and the same buyer-side debug need.

| Field                | Description                                                                                                                                                                                                                                                                                                                                    |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idempotency_key`    | Equals the `idempotency_key` carried in the webhook payload itself ([§ Dedup by `idempotency_key`](/docs/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. |
| `subscriber_id`      | Identifies which registered webhook subscriber received this fire. Absent in single-subscriber configurations; populated under multi-subscriber buys (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`, `impairment`.                                                                                                                                                                                                                                                  |
| `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 webhook activity for this buy. Either the seller does not persist fire history, the seller's declared [`propagation_surfaces`](/docs/media-buy/media-buys/lifecycle) excludes `webhook`, or the buy has no registered webhook endpoint for the calling principal. |
| Empty array `[]`  | Seller persists fire history but has fired nothing recent for this principal.                                                                                                                                                                                                             |
| Non-empty array   | Actual fire records, most-recent first.                                                                                                                                                                                                                                                   |

Sellers whose declared `propagation_surfaces` does not include `webhook` MUST omit the field; opting in via `include_webhook_activity: true` does not override that.

**Diagnosing an unexpected omission.** When you expected fires but got an omitted field, two observables let you discriminate the cause without filing a ticket: (1) check your own `push_notification_config` registration state for this buy — if not registered, that's the cause; (2) check the seller's `capabilities.media_buy.propagation_surfaces` via `get_adcp_capabilities` — if `webhook` is absent, that's the cause. When both check out, "seller does not persist fire history" is the remaining cause; that's a seller-side 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`                                                                      |
| `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` 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.format_ids_pending?.length > 0) {
        console.log(`Package ${pkg.package_id}: missing formats ${pkg.format_ids_pending.map(f => f.id).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 === '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.format_ids_pending:
                  ids = [f.id for f in pkg.format_ids_pending]
                  print(f"Package {pkg.package_id}: missing formats {', '.join(ids)}")

              # 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 == '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.format_ids_pending?.length > 0) {
      issues.push(`Package ${pkg.package_id}: ${pkg.format_ids_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.format_ids_pending:
              issues.append(f"Package {pkg.package_id}: {len(pkg.format_ids_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`     |
| ------------------- | -------------------------------- | ---------------------------- |
| **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` | Media buy ID does not exist                 | Verify `media_buy_id`                                              |
| `CONTEXT_REQUIRED`    | No media buys found for the requested scope | Provide valid IDs/refs or broaden `status_filter`/pagination scope |
| `AUTH_MISSING`        | No credentials presented                    | Provide credentials via auth header                                |
| `AUTH_INVALID`        | Credentials rejected (expired / revoked)    | Human credential rotation required; do not auto-retry              |

## Next Steps

* **Upload missing creatives**: Use [`sync_creatives`](/docs/creative/task-reference/sync_creatives) for library-backed sellers, or `packages[].creatives` on [`update_media_buy`](/docs/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`](/docs/media-buy/task-reference/update_media_buy) to adjust pricing or targeting
* **Detailed reporting**: Use [`get_media_buy_delivery`](/docs/media-buy/task-reference/get_media_buy_delivery) for date-range reporting and daily breakdowns
* **Optimize campaigns**: Use [`provide_performance_feedback`](/docs/media-buy/task-reference/provide_performance_feedback) to share results with the seller
