> ## 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.

Pass `activeOnly=true` to drop buys that are finished and cannot need anyone:
`completed`, `canceled`, `rejected`. It defaults to `false`, so the list is
your whole inventory. Set it on any "what needs me now" view, because the list
is newest-first and a buy that settled yesterday otherwise outranks an older
one still running. Leave it off when you filter by one of those three
statuses, or the list comes back empty.

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.

Pass `sort=attention` to get the first page ordered by **what needs you**:
money the flight will not deliver, then work waiting on you (including a
delivery problem you have to act on: a source that cannot report, a running
flight nothing has reported for, or spend running ahead of the flight), then a
goal being missed, then everything else. That ranking depends on delivery and goal
verdicts that can only be judged once a page is loaded, so it is accepted at
`skip=0` only and the response says `attentionRanked`. It ranks a page, never
your whole storefront — page past the first and you are back in the stable
urgency order. Your storefront home opens in this order, and so does the
Media buys entry in the rail — they are the same page over the same buys, and
ordering them differently by how you arrived would rearrange the list with
nothing to explain it.

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`). `source_input_required` means the source paused the create or update for more input from its caller, Apostra (the AdCP `input-required` status). Apostra cannot supply that input yet, so the operation fails as soon as the pause is seen instead of waiting on the source, and `nextAction` names the platform: you have nothing to do. |
| `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. |
| `warnings` (response level) | Anything that stopped the list answering a question fully — an unreachable source, or delivery reporting being unreadable. Delivery is an enrichment: if it cannot be read, every row's `delivery`, `pace` and `goal` read empty and a warning says so, rather than the whole list failing. |
| `delivery` (limit) | A routed buy from a **third-party AdCP buyer** — one with no account on this platform — reports no delivery, and therefore no pacing or goal progress. Delivery is attributed by the buyer it was reported under and the sources the buy was routed to; a third-party buy carries no buyer identity that reaches reporting, so a total for it cannot be proven to belong to one buy. We would rather show you nothing than show you a number that might be another buyer's. Buys your ad server manages are unaffected — they report through `adServerDelivery`. |
| `delivery` | What the storefront reporting pipeline has reported — see **Delivery and pacing** below. `null` means it has reported nothing; it is never a row of zeros. A buy your ad server manages is never reported here. |
| `adServerDelivery` | The running total your ad server holds for a buy it manages. Same fields as `delivery` except the two that only make sense for a routed buy: it has no `lastDeliveryDate` and no `sourcesReported`/`sourcesRouted`. `null` for a routed buy and when the ad server has not answered. |
| `reporting` | Whether delivery can be expected at all: `reported`, `awaiting_first_report`, or `blocked`. A `blocked` state always carries a `detail` naming what is in the way; every other state carries `null`. `null` for the whole block when the state could not be established — the source could not be reached or never answered, delivery reporting could not be read at all, or delivery arrived but we refused to attribute it to this buy. `awaiting_first_report` is a positive claim that nothing has been reported yet, so it is never used for any of those; the response `warnings` say what happened. |
| `goal` | The buyer's goal for this buy and how delivery compares — see **The buyer's goal** below. `null` when you hold no goal for the buy. |
| `attention` | What needs you on this buy, and how loudly — see **What needs you** below. |

### What needs you

Each buy says what needs you, in three families that answer different
questions. They are deliberately kept apart: "spend 200 a day at 80% viewable"
is two answers, and a single score would tell you to act without saying what
to act on.

| Field | Meaning |
| - | - |
| `tier` | How the buy ranks: `money_at_risk`, `waiting_on_you`, `goal_behind`, `on_track`. This is what `sort=attention` orders by. |
| `moneyAtRisk` | What the flight will not deliver at the current rate, in `commercial.currency`: the booked budget minus the total this rate projects over the whole flight. **Not** the hole accrued so far — half way through a 6,000 flight having spent 1,000, the hole to date is 2,000 and the money at risk is 4,000. `null` when there is no pace to project from. |
| `shareAtRisk` | `moneyAtRisk` as a share of the budget, 0 to 1. This is what `sort=attention` orders by: a storefront can hold buys in several currencies and the ordering carries no exchange rate, so a raw 10,000 JPY hole would otherwise outrank a 500 USD one. |
| `flags[]` | At most one per family: `task` (a person is waiting), `delivery` (the money is not moving as the flight implies), `goal` (the buyer's goal is being missed). Each carries a `level` and one sentence naming the problem. |

`level` is the platform's status colour language:

* **`critical`** (red) — the flight is running and the promise is not being
  kept. Red is earned by the flight having started: a buy that has not started
  is never red, however far behind its budget it looks, because nothing was
  promised yet.
* **`attention`** (yellow) — worth acting on, promise intact: a task waiting
  on you, reporting delayed, a source that cannot report.
* **`pending`** (neutral) — not failure. The flight has not started, or the
  evidence does not support a verdict yet.

<Note>
  A withheld verdict is never red. Too few observations, a metric your source
  has not reported, no flight recorded — all read "not enough data yet". If red
  appeared for things that are merely unknown, red would stop meaning stop.
</Note>

### Delivery and pacing

Every buy on the page carries delivery, whether or not you filtered the list to
one buyer relationship.

Delivery arrives in one of two fields, and never both. `delivery` is what the
storefront reporting pipeline reported for a routed buy, and it always names
the reporting day it covers. `adServerDelivery` is the cumulative total your
ad server holds for a buy it manages, and it never names a day — an ad server
states a total without saying which day it runs through, and we will not stamp
one on. The fields below describe both **except where a row says otherwise**:
`lastDeliveryDate` and `sourcesReported`/`sourcesRouted` are on `delivery`
alone, because a day and a routing are things only a routed buy has.

| Field | Meaning |
| - | - |
| `impressions` / `spend` | Delivered impressions and net spend. For a routed buy these come from the reporting pipeline, which provides no delivery currency at this grain; for a buy your ad server manages, they are the ad server's own running totals, denominated in `commercial.currency`. |
| `clicks` / `views` / `completedViews` / `conversions` / `leads` | The counts a buyer's goal is judged on. Each is `null` when the source reported no value for it — never a measured zero, because optimising against a metric nobody sent is worse than knowing it is missing. `views` counts **content views**, the quantity CPV pricing bills on; it is not a count of viewable impressions and must not be divided by impressions to get a viewability rate. |
| `pacingAgainstBookedBudget` | Delivered spend ÷ booked budget. `null` when the buy has no positive booked budget (missing, zero or negative), and on `delivery` also `null` until every source the buy was routed to has reported through the same day — the budget is the whole buy's, so the spend has to be too. It does not depend on the flight: a buy with no flight dates, or one that has not started, still carries it. On its own this says nothing about whether a buy is on schedule — read `pace`. |
| `sourcesReported` / `sourcesRouted` | **`delivery` only.** How many of this buy's sources are in these figures, out of how many it was routed to. They differ while a source has not sent its first report: the totals are real but cover only part of the buy. An ad-server-managed buy has no routing to count, so `adServerDelivery` carries neither field. |
| `pace` | `null` until every source the buy was routed to has reported, and `null` when the buy's sources have not reported through the same day. Pace measures one total against one stretch of flight, and a buy whose sources are in arrears of each other is complete through no single day — measuring the combined figure against either end would invent underdelivery or overdelivery nobody reported. One source's spend measured against the whole buy's budget and flight reads as underdelivery, when it is an ordinary wait for another source's first report. It returns once every source is in and level. Otherwise: delivered spend measured against **how much of the flight has run**: `flightElapsed`, `expectedSpend`, `actualSpend`, `ratio`, `spendPerDay`, `budgetPerDay`, and a `verdict` of `on_pace` (within 10% of the even-pace line), `behind` or `ahead`. When there is no verdict, `verdictWithheld` names the reason — no budget, no flight, the flight has not started, under 5% of the flight elapsed, or no reported spend. A buy three days into a thirty-day flight that has spent a tenth of its budget is on pace; a buy on its last day that has spent the same tenth is nine tenths undelivered, and only the flight window tells them apart. |
| `lastDeliveryDate` | The most recent UTC reporting day with a delivery record. On `delivery` only, where it is always present. `adServerDelivery` has no such field: an ad server reports a running total without naming the day it runs through. |

<Note>
  An ad server that has not granted us reporting access cannot report delivery no
  matter how long you wait. That buy reads `reporting.state: "blocked"` with a
  `detail` naming what to fix, rather than sitting on "awaiting first report"
  forever. Buys managed by your ad server carry no `clicks`, `views` or
  `completedViews` yet — the ad-server list does not report them, and an ad
  server that reports only one of impressions and spend reports neither here:
  a spend of zero beside real impressions is not a measurement, and pacing it
  would invent a confident verdict.
</Note>

### The buyer's goal

You see the same goal verdict the buyer sees, judged by the same code over the
delivery your own sources reported. It is on every row of the list and on the
per-buy timeline.

You see it **only from what the buyer told you**: the commitment recorded
against the buy when it was booked, and the `optimization_goals` on the AdCP
request the buyer sent you. Nothing here reads the buyer's campaign record. A
buy whose goal was never disclosed to you carries `goal: null`.

| Field | Meaning |
| - | - |
| `goal` | What is being judged: `kind` (`metric` or `event`), `subject` (the metric name, or the event types joined with `\|`), and the `target` the buyer stated. |
| `askedTarget` / `answeredTarget` | The buyer's target, and the cost or return your own terms commit or aim at, from the commitment recorded at booking. |
| `commitment` | `guaranteed`, `best_effort`, or `report_only` — what your terms tied to the goal. `null` for a buy booked before commitments were recorded. |
| `actual` | The achieved cost per unit, rate, or volume, computed from the delivery on the same row: `delivery` for a routed buy, `adServerDelivery` for a buy your ad server manages. `null` when the metric was not reported or has no observations. The ad-server list reports impressions and spend but not clicks, views or completed views yet, so a goal on one of those reads `metric_not_reported` there; an ad-server total names no reporting day, so its `freshness.dataThrough` is `null`. |
| `verdict` | `on_track`, `behind`, or `beat`, against `askedTarget` when it carries a number and `answeredTarget` otherwise (`judgedAgainst` says which). Cost targets are lower-is-better; rates are higher-is-better. `beat` needs a 10% margin. |
| `verdictWithheld` | Why there is no verdict: `no_goal`, `no_target`, `metric_unsupported`, `metric_not_reported`, or `too_few_observations`. A withheld verdict is never a bad verdict — it means the evidence does not support one yet. |
| `basis` / `freshness` | Who counted the number (`seller_attested` — it is your own reported delivery) and how fresh it is. |
| `optimizedBy` | An object, or `null`. `optimizedBy.actor` is who acts on the goal for a buy your ad server manages: `"ad_server"` when its own optimizer works towards the goal, `"seller"` when nothing automatic does and your ad operations own it. `optimizedBy.detail` is one sentence you can show a person. The whole object is `null` for a routed buy, whose third-party source's optimization this storefront cannot see, and `null` when the ad server did not answer — compare `optimizedBy?.actor`, never `optimizedBy` itself. |

<Note>
  `optimizedBy.actor === "seller"` is the case to watch. It means the ad server
  is not working towards the buyer's goal — either it has no optimizer, or your
  vendor has not granted us the access optimization needs. Hitting the goal is
  your ad operations' job, and this row is the only place that says so.
</Note>

## 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. It also carries the same `goal` block the list row does, so a drill-in answers
"is this buy hitting what the buyer asked for" alongside "where is it stuck".
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.
* **`updatesAwaitingSource`** — changes to live buys that a source accepted with a task and has not applied yet.
* **`creativesAwaitingSource`** — approved creatives not yet accepted at a source: the source is reviewing them, Apostra is still delivering them, or they wait for their media buy to be accepted there. `mediaBuyId` is `null` for a creative-library delivery.
* **`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.

Every item in `approvals`, `creativeReviews`, `awaitingSource`,
`updatesAwaitingSource`, `creativesAwaitingSource`, and `manualSourceWork`
carries `waitingOn`: who it is waiting on, since when, and the exact thing being
waited for. It is the same answer the
[source health object](/v2/storefront/inventory-sources/diagnostics#the-source-health-object)
gives in `sourceHealth.operations`, with the same meanings:

* `waitingOn.party` is `seller` (you must approve, review, or do manual work),
  `operator` (the party that runs the Agent behind the source holds a task it
  has not finished), `buyer` (the buyer must attach creatives), or `scope3`
  (Apostra must make the next move, such as retrying a forward or delivering a
  creative). Behind Apostra's own managed sales agent there is no separate
  operator, so a buy it holds waits on your own review in your ad server and
  reads `seller`.
* `waitingOn.since` is when the wait on that party began. Age never decides
  whether something is pending: an item is listed from its first minute.
* `waitingOn.target` names what is being waited for as a `kind` and `id`: an
  `approval` (the media buy id), a `creative_review`, a manual `work_item`, the
  source's `task` id, or the `creative` still to be delivered. It is `null` when
  there is no single thing to name.

A failed forward carries `waitingOn` too: `scope3` while Apostra is still
retrying it, and `null` once it has stopped (`terminalized`), because the
failure is settled and the next step is the group's recovery action.

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>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.