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

# Media buys & pending operations

> See every buy on your storefront, trace one buy end to end, and work everything that is waiting on someone

Three read-only surfaces answer the operating questions "what buys do I have, where is this one stuck, and what needs attention" — without a ticket and without platform support looking at a database. Each is available as a REST endpoint and as an in-chat surface (ask Murph, or open it from the rail's **Operate** section).

All examples use the storefront base URL:

```
https://api.apostra.com/api/v2/storefront
```

Authenticate every request with `Authorization: Bearer $SCOPE3_API_KEY`.

## Recover a failed GAM order safely

If GAM created an order but line-item trafficking failed, Pending Operations shows a separate cleanup task. Apostra uses ESA's structured cleanup result; it never interprets vendor exception text or exposes GAM credentials.

* If `ArchiveOrders` is missing, grant it in GAM and re-check, or archive the order manually.
* If authentication expired, reconnect the ad-server connection before re-checking.
* Review and archive appears when cleanup is verified retryable. Permission and authentication failures instead offer a confirmation-gated re-check, and archive proceeds only if the repeated safety check passes.
* Unknown or unsafe orders require GAM review and cannot be archived from Apostra.

Only storefront account admins can resolve these tasks. Archiving requires explicit confirmation naming the GAM order, and ESA repeats every safety check immediately before archiving. An already-archived response completes the task. Marking an order manually cleaned requires the matching order ID and an audit note; it is recorded distinctly and never claims ESA archived it.

## Media buys — every buy on the storefront

`GET /media-buys` returns every buy forwarded across connected sources plus buys
managed directly by an ad-server source. The API retains `kind: routed | esa` as
legacy wire values; those values describe implementation provenance, not
campaign mode or settlement. Filter by `status`, `buyerCustomerId`, `sourceId`,
or flight-start window.

The default sort is **urgency**: buys still waiting on someone whose flight starts within 48 hours (or has already started) come first, ordered by flight start; everything else follows newest-first.

Each row carries:

| Field                     | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`                  | A coarse seller lifecycle view derived from persisted state: `pending_approval`, `forwarding`, `forward_failed`, `awaiting_source`, `rejected`, `canceled`, `booked`, `delivering`, `paused`, `completed`. This is a platform list-view convenience, never an AdCP status.                                                                                                                                                                                                                                                                                                                   |
| `sourceStatus`            | The raw upstream status as persisted, for transparency.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `pendingReason`           | Why the buy is not delivering yet — the **same shared vocabulary buyers see** (`awaiting_storefront_approval`, `awaiting_source_moderation`, `no_creatives_attached`, `source_rejected_creatives`, `creative_processing_at_source`, `awaiting_creative_approval`, `forward_failed_retrying`, `forward_failed_needs_correction`, `accepted_awaiting_trafficking`, `scheduled_not_started`), rolled up to the most-blocking source leg. It is an annotation, never a status. `no_creatives_attached` and `source_rejected_creatives` are buyer-owned (the buyer must attach or fix creatives). |
| `errorCode`               | The structured error code of the latest failed exchange (the platform's ledger vocabulary, e.g. `unknown_product_ids`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `forwardOutcome`          | How far dispatch got: `not_forwarded`, `all_completed`, `all_submitted`, `partial`, `failed`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `advertiser` / `operator` | The advertised brand and buying-operator domain, kept separate from the persisted transaction account reference. Identity can come from a pending submitted request or an auto-approved forwarded route, including an inline AdCP v2.5 `brand_manifest`. A missing operator remains null; the buyer-customer display name is not substituted.                                                                                                                                                                                                                                                |
| `commercial`              | Seller-authorized amount, currency, denomination, and seller-net CPM when derivable. Only seller-forwarded route payloads support a `net_media` claim; a manual buy that has not been forwarded shows commercial data as unavailable. Source-managed totals are `source_total` when their contract does not declare gross/net. Buyer gross budget, buyer effective CPM, and fee terms are never exposed.                                                                                                                                                                                     |
| `creative`                | Creative state and attached count when known; `state` is null when no creative is attached or the source contract does not report state. `attachedCount` and `pendingReason` distinguish those cases when known.                                                                                                                                                                                                                                                                                                                                                                             |
| `openWork` / `nextAction` | Modular work correlated by this exact buyer-customer + media-buy identity, split into actionable and blocked counts, plus the current owner and an explicit action or no-action message. Media-buy ids can be reused by different buyers.                                                                                                                                                                                                                                                                                                                                                    |

## Per-buy timeline — what was sent, when, and where it is stuck

`GET /media-buys/{mediaBuyId}/timeline` is the seller-scoped projection of the buy's exchange record. Because routed media-buy ids are buyer-scoped, pass the row's `buyerCustomerId` query parameter when it is available; the Media Buys Page does this automatically. Id-only requests remain supported for callers whose ids are unambiguous. Stages follow the exchange lifecycle, failure branches included:

```
received → screened → decided → forwarded / forward-failed → submitted
        → source moderation (pending since T) → accepted / rejected → delivering
```

**Accepted means the source accepted the transaction boundary. It does not
mean trafficked or delivered.** Creative processing, modular trafficking, and
other seller work can remain pending after acceptance; the timeline and
`nextAction` name that remaining boundary honestly.

Update exchanges reuse the same stages. Inbound source webhooks appear as **evidence inside stages**, never as a stage of their own — many sources never send webhooks, so their absence means nothing. The `screened` stage is reserved for the acceptance-policy pre-screen record and is not emitted today; do not wait for it.

**What you can see, and what you cannot.** The forwarded payload is yours to inspect — you are the counterparty to that message — *minus platform-internal fields*: webhook/push-notification configuration and any signing or credential material are removed server-side. Platform-internal diagnostics (resolution cache internals, worker polling state, cross-tenant audit entries) never appear; failures surface as structured error codes with a recovery class instead. A payload shown as `{"pruned": true}` means retention removed the stored bytes (payload bodies are kept for 90 days; the slim event record is kept indefinitely).

Each source leg carries a trafficker-grade summary of the request (flight, budget, packages, targeting dimensions) with the raw JSON behind a disclosure.

## Canonical reporting — what is due and what arrived

`GET /media-buys/{mediaBuyId}/reporting?buyerCustomerId={buyerCustomerId}`
returns the canonical reporting obligations, current health, and retained
revision evidence for one seller-visible buy. `buyerCustomerId` is required
because different buyers can reuse the same media-buy ID. The endpoint first
verifies the buy against the current storefront and buyer-account grant; a
foreign or missing buy returns `404` rather than a plausible-looking empty
report.

The top-level `health` is one of `waiting`, `healthy`, `delayed`,
`action_required`, or `complete`. Each obligation keeps these cases distinct:

* `missingFirstReport: true` means no revision has arrived by the expected time.
* A successful revision with `rowCount: 0` is a real zero-row report, not a
  missing report.
* `kind` distinguishes `snapshot`, `official`, and `restatement` revisions.
* `coverage` can be `full`, `partial`, or `none`; it is `null` when no coverage
  value was observed. Partial coverage is never presented as whole-buy coverage.
* `dataThrough`, `nextExpectedAt`, and `freshness` describe the reporting data,
  not the media buy's delivery status.
* `obligationsTruncated`, `mediaBuyIdsTruncated`, and
  `relatedMediaBuyIdsTruncated` explicitly mark bounded responses. When the
  obligation-row limit is exceeded, aggregate health and freshness fail closed
  to `null` instead of being calculated from a partial ledger view.

`sourceProvenance: acquired` means an immutable acquisition plan attributes
the reporting evidence to a Source. `null` means that proof was not observed;
it does not mean the Agent cannot report. The response is read-only. Its three
action descriptors remain unavailable with `reason: adapter_pending` until
their current-read and retained-revision adapters are connected.

### Whose reference to quote to whom

Every leg carries two references, and they are for different counterparties:

| Reference                                           | Field                                                                                                             | Quote it to                                                                                                                                                                                                                         |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Their reference** — the source's own ids          | `theirReference.mediaBuyId` / `theirReference.taskId`                                                             | The inventory source. These are ids minted in *their* system; their support can look them up directly. For adapter sources (e.g. Google Ad Manager) the platform order id plays this role.                                          |
| **Platform reference** — the create idempotency key | `platformReference.idempotencyKey` (`sf:<storefront>:<buy>:<source>`) paired with `platformReference.requestedAt` | The source (for pre-acceptance exchanges — it is the deduplication key they received) and Apostra support. Always quote the key **together with the request timestamp**: the key is stable across the buy's life, requests are not. |

While a source is still moderating (no upstream buy exists yet), their task id is the handle; once accepted, their media-buy id is.

## Pending operations — everything waiting on someone

In Apostra, open this Page from **Approvals & operations** in the seller rail.

`GET /pending-operations` returns the union of work in flight, grouped; a group appears **only when it is non-empty**:

* **`approvals`** — media buys (creates and updates) waiting on your review.
* **`creativeReviews`** — creatives waiting on your review. Each item carries
  the exact `reviewRef` used by the Page action, so repeated versions of one
  buyer creative never open or decide a different review.
* **`failedForwards`** — approved buys that could not be sent to their source, **grouped by structured error code** so a source outage reads as one row, not N. Each group and item carries its gated action (below).
* **`awaitingSource`** — buys a source accepted asynchronously and is still moderating, each with "waiting since" and the source's task id.
* **`manualSourceWork`** — modular booking, creative-sync, and final-reporting tasks, grouped by the same `buyerCustomerId` + `mediaBuyId` + `sourceId`. Every group and item repeats `buyerCustomerId`; every item carries its exact `workItemId`, seller owner, actionable/blocked state, and a typed deep-link to that exact source work item.
* **`sourceDegradations`** — seller-owned source-health diagnoses: an ad-server or sales-agent source you operate that is degraded, each with its severity (`blocking`, `attention`, or `advisory`) and a one-line summary. Only diagnoses **you** own appear — an issue Apostra or a vendor must fix never shows up as your task. Each row links to the [source diagnostics surface](/v2/storefront/inventory-sources/diagnostics) scoped to that source.
* **`gamCleanups`** — failed GAM order cleanups that require an operator decision. The Page offers automatic archive only when the server marks the cleanup safe; recording a manual cleanup requires a note.

The same Page works in Apostra, Claude, ChatGPT, and other MCP-app hosts. Its buttons open the exact portable Approvals, timeline, retry, or source-diagnostics surface directly—there is no generated chat prompt or second assistant turn. GAM cleanup is a two-step human confirmation inside the Page and uses an app-only commit that is not exposed to the model.

Manual modular-source work is bounded to 200 tasks per response. Its envelope
keeps full-backlog `count` and `mediaBuyCount`, and returns `offset`,
`returnedCount`, `truncated`, and `nextOffset`. Continue with
`manualWorkSkip=nextOffset` (and optionally `manualWorkTake`, max 200). The
Pending Operations Page does this with **Load more**, appending tasks without
deduplicating different buyers that reuse the same media-buy id.
If completed tasks make a requested offset fall past the live backlog, the
server rebases `offset` to `0`; clients should replace their loaded manual-work
rows when the returned offset moves backward. The Page handles that reset.

### Recovery classes: why some failures have no retry

Every forwarding failure carries a recovery class, and the class — not the operator's optimism — decides which action is offered:

| Recovery class | Meaning                                                                                                                                                         | Action offered                                                                                                                                                                                                                                                                                                      |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transient`    | The send can succeed if repeated (source outage, timeout). The platform retries these itself for up to 6 hours after approval.                                  | **Retry** — a one-decision confirmation that re-fires the forward. The create idempotency key makes a duplicate booking impossible if the source already received it.                                                                                                                                               |
| `correctable`  | The request needs a change before any send can succeed (e.g. an expired cross-currency quote).                                                                  | **Review correction** — opens the exact buy timeline and evidence; retrying the same payload would fail identically.                                                                                                                                                                                                |
| `structural`   | No retry of the same payload can succeed (e.g. the selected product no longer resolves). The platform terminalizes these — it stops retrying and marks the buy. | **Review escalation** — opens the exact buy timeline and references. No retry button is shown here, because repeating the identical send would fail identically. Once the cause is fixed, the API can re-forward the buy in place — see [Retry forwarding](/v2/storefront/media-buy-approvals/tasks/retry-forward). |

A `transient` failure whose retry window has lapsed is treated as terminalized and also becomes escalate-only.

<Tip>
  **If your source consistently produces `transient` timeout failures** on `create_media_buy` or `update_media_buy` — visible in source diagnostics and as persistent `failedForwards` here — the long-term fix is to implement AdCP asynchronous acceptance on your sales agent. Return a `submitted` response (with a stable `task_id`) immediately when the platform's request arrives, complete the work out-of-band, then POST the result to the `push_notification_config` webhook URL included in the original request. This decouples your processing time from the platform's call timeout and eliminates the failure class entirely.

  See the [AdCP async operations reference](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/update_media_buy#asynchronous-operations) for the protocol and payload details. Contact your operator or Apostra support to discuss implementation.
</Tip>

### Transition notifications

You do not have to keep this surface open: a **forward failure** on an approved buy and a **source-moderation wait that ages past its deadline** each push one notification to your storefront's notification stream (in-app, email, Slack — per your delivery settings). These fire on the transition only, never repeatedly, and carry the error code and recovery class.

## Task reference

<CardGroup cols={2}>
  <Card title="List media buys" href="/v2/storefront/media-buys/overview" icon="list">
    `GET /media-buys` — every buy across the storefront and connected sources,
    urgency-sorted
  </Card>

  <Card title="Get a buy's timeline" href="/v2/storefront/media-buys/overview" icon="timeline">
    `GET /media-buys/{mediaBuyId}/timeline` — the seller-scoped exchange record
  </Card>

  <Card title="Get a buy's reporting state" href="/v2/storefront/media-buys/overview" icon="chart-line">
    `GET /media-buys/{mediaBuyId}/reporting` — canonical obligations, health,
    and retained revisions for one buyer-scoped buy
  </Card>

  <Card title="List pending operations" href="/v2/storefront/media-buys/overview" icon="hourglass">
    `GET /pending-operations` — everything waiting on someone
  </Card>

  <Card title="Retry a failed forward" href="/v2/storefront/media-buy-approvals/overview" icon="rotate-right">
    `POST /media-buy-approvals/{mediaBuyId}/retry-forward` — transient, non-terminalized failures only
  </Card>
</CardGroup>
