> ## 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 buy lifecycle

> The AdCP media-buy status values a buyer reads on the wire — what each one means, how a source's manual moderation keeps a buy pending, and how an asynchronously accepted buy resolves through a submitted task.

A [media buy](/v2/buyer/campaigns/media-buys) carries a lifecycle status on the
AdCP wire that tells a buyer whether it is delivering, waiting on something, or
finished. This page defines each status, explains why a buy a source is still
moderating stays `pending_creatives`, and shows how a buy a source accepts
out-of-band resolves through a submitted task.

<Note>
  The AdCP wire status described here is one of **three** distinct status
  surfaces a buyer can see for the same buy. They do not replace each other — see
  [Three status surfaces](#three-status-surfaces) before treating any one as
  "the" status.
</Note>

## The AdCP media-buy status

When a buyer's agent calls a storefront's AdCP agent (`create_media_buy`,
`update_media_buy`), a confirmed buy carries a per-buy `media_buy_status`. It is
one of seven values:

| Status              | What it means to a buyer                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending_creatives` | Accepted, but no approved creative is assigned yet, so it cannot launch. The buy waits here until a creative is approved and attached.                                                                                                                                                                                                                                                                        |
| `pending_start`     | Accepted with creatives ready, but the flight has not started. It launches automatically when the start date arrives.                                                                                                                                                                                                                                                                                         |
| `active`            | Live and delivering against the flight.                                                                                                                                                                                                                                                                                                                                                                       |
| `paused`            | Delivery is halted and resumable — by the buyer, or because the buy was created paused.                                                                                                                                                                                                                                                                                                                       |
| `completed`         | The flight finished or the budget fully delivered. **Extendable** — a buyer may increase the budget or extend the flight date, which stages a new version for seller approval. Once the seller approves, the new values take effect; the buy status stays `completed`. If the owning campaign has also completed, updates are blocked. `canceled` and `rejected` are the truly irrecoverable terminal states. |
| `rejected`          | The source declined the buy. Terminal — resubmit only after addressing the reason.                                                                                                                                                                                                                                                                                                                            |
| `canceled`          | Stopped before completion, by the buyer or the source. Terminal. Also covers a buy the storefront could not place with its source at all — see below.                                                                                                                                                                                                                                                         |

<Note>
  **A buy that never reached its source reads `canceled`.** If `create_media_buy`
  is accepted by the storefront but the forward to the underlying source fails
  (a malformed request, an unreachable source, a credential fault), no media buy
  exists upstream and none ever will. That buy is `canceled`, with
  `cancellation.canceled_by` set to `seller` and the cause in the response
  `errors[]` — not `pending_start`, which promises a launch that cannot happen.

  `rejected` is reserved for a source that actually considered the buy and
  declined it. A transport or credential failure is not a seller's "no", so it
  never reads as one: retrying the same request may well succeed.
</Note>

<Note>
  There is no `pending_approval` value in the AdCP media-buy status. Insertion-order
  signing, governance review, and other pre-issuance steps are not modeled as a
  buy status — they happen at the **task** layer, before a `media_buy_id` exists.
  See [Asynchronous acceptance](#asynchronous-acceptance-the-submitted-task).
</Note>

### Why a buy reads `pending_creatives`

`pending_creatives` is the default landing state for a buy that has been
accepted but has nothing launch-ready attached. Two distinct situations both
surface as `pending_creatives`:

* **No creative is assigned yet.** The buy exists, but the buyer has not synced
  or assigned an approved creative to it.
* **The source is moderating the buy.** Some sources accept a buy and then park
  it for manual review — checking creatives, signing an insertion order, or
  applying a content policy — before it is launch-eligible. While that review is
  outstanding the buy is accepted but **not launch-ready**, and reads
  `pending_creatives`.

<Tip>
  `pending_creatives` is not a failure. It means "accepted, not yet launchable."
  Resolve it by assigning an approved creative (and, where a source moderates,
  waiting for that review to clear), not by resubmitting the buy.
</Tip>

### Paused buys still show setup blockers

Creating a buy with `paused: true`, or pausing it before its creative arrives,
does not replace a more useful setup status. The pause is retained as a latent
delivery hold:

1. With no launch-ready creative, the buy reads `pending_creatives`.
2. With creatives ready but a future flight, it reads `pending_start`.
3. Once the buy is otherwise ready to launch, it reads `paused` instead of
   becoming `active`.

Resuming during either pending state clears the latent hold but does not bypass
the blocker. The buy remains pending until its creative and flight are ready.
An underlying ad platform may show its native campaign as paused throughout
this setup period to prevent spend; the AdCP status reports the actionable
blocker rather than copying that provider control.

## Asynchronous acceptance: the submitted task

A source does not always confirm a buy before the response is emitted. When a
source accepts a buy out-of-band — parking it for manual moderation, queuing it
for batch processing, or awaiting an insertion-order signature — the storefront
returns a **submitted task envelope** instead of a confirmed buy:

```json theme={null}
{
  "status": "submitted",
  "task_id": "task_8f3c1a90-4d2e-4b77-9c1a-3e9b5d2f0a11",
  "message": "Awaiting manual review by the source."
}
```

There is no `media_buy_id` on this response — the buy is not confirmed yet. The
buyer resolves it the same way as any async operation: **poll** the task (the
AdCP `tasks_get` operation) with the `task_id`, or **await a webhook** if a
push-notification config was attached to the original call. See
[Tasks](/v2/guides/tasks) for the concrete polling endpoint, webhook setup, and
back-off mechanics.

`submitted` and `working` mean the task is still processing — keep polling.
Otherwise it resolves to one of three terminal task states, and the outcome
lands on its completion artifact:

| Task status      | What you get                                                                                                                                                                                             |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `completed`      | The confirmed `media_buy_id` plus its `media_buy_status` — typically `pending_creatives` or `pending_start` (or `rejected` / `canceled` if the source accepted the buy and then declined or stopped it). |
| `input-required` | The source needs something more from you before it can finish. Inspect the response and `error.suggestion`, then resubmit the call with the correction. See [Tasks](/v2/guides/tasks).                   |
| `failed`         | The operation could not complete — including a source that declined the buy outright; the `error` object explains why.                                                                                   |

<Note>
  An `update_media_buy` edit that a source queues for moderation behaves
  identically: the storefront returns a `submitted` task envelope, and the edit's
  result lands on the task's completion artifact. The same poll-or-webhook pattern
  applies.
</Note>

<Warning>
  A `submitted` envelope is not a confirmation. Do not treat the `task_id` as a
  `media_buy_id`, and do not assume the buy is live until the task completes with a
  `media_buy_status` of `active`.
</Warning>

### Receiving the async result without polling

Instead of polling, a buyer can register a `push_notification_config` on the originating call so the platform delivers the completion event to a webhook when the task resolves:

* **AdCP/MCP surface** — buyers calling a storefront's `create_media_buy` or `update_media_buy` tool directly can include a `push_notification_config` field in the request body. The platform stores it and calls the buyer's webhook when the task reaches a terminal state, eliminating the need to poll.

* **REST API surface** (`execute_campaign`) — the platform manages the AdCP-layer webhook on the buyer's behalf. Buyers receive media buy state transitions through Apostra webhook subscriptions: register one via `create_webhook_subscription` and subscribe to `media_buy.approved`, `media_buy.rejected`, and related event types. See [Notifications](/v2/guides/notifications).

The two paths are complementary — a buyer on the REST API does not need to configure `push_notification_config` directly; that handshake happens internally between the platform and the seller.

## Cancellation

A buyer cancels a media buy by requesting deletion (the AdCP `delete` operation
or `update_campaign` with `action: delete`). What happens next depends on whether
the buy has been committed to a source and, if so, whether the inventory is
guaranteed.

### Retraction: uncommitted buys

When the buy has **not** been forwarded to a source (no reserved inventory, no
live leg), the cancel is a **retraction**: the pending request is withdrawn and
forwarded directly as a cancel. No operator approval is needed. The buy
transitions to `canceled`.

If the buy is still waiting for the seller to approve it, the retraction also
withdraws that approval request. The seller's approval task closes, and the
request can no longer be approved or sent to any source, so a canceled buy
never becomes live afterwards. The buy reads `canceled` only after the
withdrawal has been recorded; if the cancel fails, the buy stays pending and
you can retry.

If the seller approves the buy just before your cancel arrives, the cancel is
refused and the buy stays pending while the storefront sends it to the source.
Retry the cancel shortly; once the buy has reached its source, the cancel
follows the direct or approval path below.

Non-guaranteed committed buys (buys that have been forwarded but are not
guaranteed delivery) also cancel directly with no approval, following the same
path as a retraction.

### Cancellation approval: committed guaranteed buys

When the buy **is** committed and the inventory is **guaranteed** (the buy has
been forwarded and reserved with the source as guaranteed delivery), a
storefront configured for manual media-buy approval files a seller decision.
Storefronts configured for automatic approval, storefronts without the approval
capability, and pure pass-through storefronts
forward the cancel directly.
On the manual path, the storefront operator sees an
`APPROVE_MEDIA_BUY_CANCELLATION` item in their
[approvals queue](/v2/storefront/media-buy-approvals/overview), and the buy
**keeps delivering** while the operator decides.

| Decision    | What happens                                                                                                                       |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **Approve** | The cancel is forwarded asynchronously. The buy terminates (`canceled`) after every source leg accepts it.                         |
| **Reject**  | The buy stays `active`. The buyer is notified the cancellation was declined, with the operator's reason. The buyer may re-request. |

<Note>
  A buy with **any** guaranteed leg is treated as guaranteed for cancellation
  purposes. There is no partial (per-leg) cancel: approval or rejection applies to
  the entire buy.
</Note>

While a cancellation is awaiting the operator's decision, the buy keeps its
normal AdCP status and continues delivering. Current buyer read surfaces do not
yet expose a dedicated cancellation-wait `pendingReason`; use the cancellation
request/decision notifications to follow this transition.

<Warning>
  If the result has no pending leg and is not wholly completed after the operator
  approves—including all-failed, precondition-failed, or partial completed+failed
  outcomes—the buy is **not** marked canceled. The work item moves to `BLOCKED`
  with the failure reason, and the operator can retry. A mixed outcome where one
  leg is pending while another fails is a current limitation: the item can close
  before every leg is confirmed canceled. Ask the storefront operator or support
  to confirm the whole-buy outcome before treating that buy as fully terminated.
</Warning>

| Buy state                                                                                  | Cancellation behavior                                                                         |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| Not committed (no forwarded leg)                                                           | **Retraction**: forwarded directly as a cancel, no approval.                                  |
| Committed, non-guaranteed                                                                  | **Direct cancel**: forwarded to the source immediately, no approval.                          |
| Any guaranteed committed leg on a manual-approval storefront                               | **Cancellation approval**: operator decides; buy keeps delivering until approved or rejected. |
| Guaranteed committed leg on auto/no-capability topology, or a pure pass-through storefront | **Direct cancel**: no operator decision is opened.                                            |

## `pendingReason`: whose side owns the wait

Statuses say *that* a buy is waiting; they do not say *on whom*. For
storefront-routed buys the platform derives a **`pendingReason` annotation**
from its forwarding records — per source leg, rolled up to the most-blocking
wait — and surfaces it on the buyer's read surfaces
(`GET /media-buys/:mediaBuyId`, the nested media buy in
`GET /campaigns/:id`, and the status poll as `pending_reason`).

<Warning>
  `pendingReason` is an annotation, **never a status**. AdCP's media-buy status
  is a closed enum and pre-issuance review is deliberately not a status value —
  these reasons explain a wait without changing any status field.
</Warning>

| `pendingReason`                   | Who owns the wait | Meaning                                                                                                                                                   |
| --------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `awaiting_storefront_approval`    | Seller            | The storefront operator has your buy in their review queue                                                                                                |
| `awaiting_source_moderation`      | Seller            | The inventory source accepted the buy asynchronously and is still moderating it — no upstream buy exists yet (task-layer wait)                            |
| `no_creatives_attached`           | You               | The buy reached the source with no creatives attached — nothing is in review anywhere, so the wait cannot resolve on its own. Attach creatives to the buy |
| `source_rejected_creatives`       | You               | Every creative assigned to the buy was rejected by at least one source agent — replace or fix the creatives                                               |
| `creative_processing_at_source`   | Seller            | The upstream buy exists; the source is processing/moderating its creatives before it is launch-eligible                                                   |
| `awaiting_creative_approval`      | Seller / You      | The upstream buy exists, creatives are attached, and a creative review or assignment is genuinely pending                                                 |
| `forward_failed_retrying`         | Platform          | Forwarding to the source failed transiently; the platform is retrying                                                                                     |
| `forward_failed_needs_correction` | Platform          | Forwarding failed structurally; a correction is needed before it can be retried — see `errorCode`                                                         |
| `accepted_awaiting_trafficking`   | Seller            | Accepted end-to-end; the source has not started trafficking it yet                                                                                        |
| `scheduled_not_started`           | Nobody            | Everything is booked; the flight start date has not arrived                                                                                               |

`pendingSince` accompanies the reason with when the current wait began. When a
wait turns into a failure or rejection, the buyer-safe `errorCode` (and the
source's sanitized message) carries the why — see
[the error-code table](/v2/buyer/campaigns/media-buys#errorcode--structured-failure-codes).

Acceptance confirms that the source accepted the transaction; it is not proof
of trafficking or delivery. When creative sync, manual booking, or trafficking
work remains, the seller surface keeps that work and its current owner visible
until the source boundary reports the later lifecycle state.

## Three status surfaces

The same buy can be described by three different status vocabularies depending
on which surface you read. They answer different questions and use different
value sets — keep them distinct.

| Surface                  | Field                | Value set                                                                                                                    | What it describes                                                                                                         |
| ------------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| AdCP per-buy (this page) | `media_buy_status`   | `pending_creatives`, `pending_start`, `active`, `paused`, `completed`, `rejected`, `canceled`                                | One buy's lifecycle on the AdCP wire.                                                                                     |
| AdCP account rollup      | `operational_status` | `no_media_buys`, `draft`, `pending_creatives`, `pending_start`, `active`, `paused`, `completed`, `attention_required`        | An account-level rollup across all of a buyer's buys. `no_media_buys`, `draft`, and `attention_required` are rollup-only. |
| Apostra buyer rollup     | `status`             | `DRAFT`, `PENDING_APPROVAL`, `INPUT_REQUIRED`, `ACTIVE`, `PAUSED`, `COMPLETED`, `CANCELED`, `FAILED`, `REJECTED`, `ARCHIVED` | The higher-level Apostra campaign and media-buy rollup. See [Media Buy → Status](/v2/buyer/campaigns/media-buys#status).  |

Apostra buyer rollup is a **coarser, derived** view of the AdCP per-buy
status — not an independent one. Each AdCP value maps to one Apostra value,
and `pending_creatives` and `pending_start` both collapse to `PENDING_APPROVAL`:

| AdCP `media_buy_status`              | Apostra `status`   |
| ------------------------------------ | ------------------ |
| `pending_creatives`, `pending_start` | `PENDING_APPROVAL` |
| `active`                             | `ACTIVE`           |
| `paused`                             | `PAUSED`           |
| `completed`                          | `COMPLETED`        |
| `rejected`                           | `REJECTED`         |
| `canceled`                           | `CANCELED`         |

For a multi-agent campaign the rollup also surfaces the **most restrictive**
value across agents (see
[Most-restrictive rule](/v2/buyer/campaigns/media-buys#most-restrictive-rule)),
so a campaign with one `pending_creatives` buy and one `active` buy reports
`PENDING_APPROVAL`. Apostra also has states from its own flow that no AdCP
wire value maps to — `DRAFT` (before execution), `INPUT_REQUIRED`, `FAILED`, and
`ARCHIVED`.

<Tip>
  For Apostra rollup and how to poll it, read
  [Get media buy status](/v2/buyer/campaigns/tasks/get-media-buy-status). The
  `adcp_status` field on that response carries the raw AdCP wire status this page
  defines.
</Tip>

## Pending changes: what is live vs what you asked for

When you update a running buy — or extend a completed one — the change is recorded immediately but does not take effect until it is accepted upstream. During that window the buy has two states at once: the one that is live, and the one you asked for.

Updating a `completed` buy follows the same path: the live values (including `status: "COMPLETED"`) remain unchanged, a new version enters `PENDING_APPROVAL`, and once the seller approves it the new budget and dates take effect — the buy status stays `completed`. This is how a buyer corrects or extends a budget or flight date after a buy has finished.

**Every read surface reports what is live.** `status`, `endTime`, budgets and
packages always describe the buy as it is currently delivering. A submitted
change never overwrites those values, because a number you cannot tell is live
is a number you cannot act on.

The change surfaces separately, as `pendingChange`:

```json theme={null}
{
  "mediaBuyId": "mb_QCW9XsBApq",
  "status": "ACTIVE",
  "endTime": "2026-07-27T23:59:59.000Z",
  "pendingChange": {
    "status": "PENDING_APPROVAL",
    "pendingAt": "storefront",
    "reason": "Extended to 8/11 with W5+W6 gross budget",
    "differences": {
      "endTime": {
        "live": "2026-07-27T23:59:59.000Z",
        "proposed": "2026-08-11T23:59:59Z"
      },
      "budget": { "live": 33526.33, "proposed": 50105 }
    }
  }
}
```

Read it like this:

* **`pendingChange` absent**: nothing is queued. Everything you see is live.
* **`pendingChange` present**: a change was accepted by Apostra but has
  **not** reached the delivering buy. `differences` lists only the fields whose
  proposed value differs from live. An empty `differences` means the change is
  redundant against what is already live.

<Warning>
  A buy reading `status: "ACTIVE"` with a `pendingChange` is delivering the **old**
  values. Do not treat a successful update response as confirmation that the new
  values are live. Poll until `pendingChange` disappears, and confirm delivery
  with `has_delivery` on
  [get media buy status](/v2/buyer/campaigns/tasks/get-media-buy-status).
</Warning>

### Where each surface reports it

| Surface                                                                  | Reports                                                     | Pending change                                                                                                                       |
| ------------------------------------------------------------------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| [`get_media_buy`](/v2/buyer/campaigns/media-buys)                        | The live buy                                                | `pendingChange`, including field-level `differences`                                                                                 |
| `get_campaign` → `mediaBuyRefs[]`                                        | Current status per buy, including `ARCHIVED` after archival | `pendingChange` with status and layer only, to keep the reference small; archived substantive details are omitted from `mediaBuys[]` |
| `get_campaign` → `mediaBuys[].packages[]`                                | Live packages and budgets                                   | Not carried, read `get_media_buy`                                                                                                    |
| [`get_media_buy_status`](/v2/buyer/campaigns/tasks/get-media-buy-status) | Live delivery state                                         | Not carried, this surface answers "is it delivering"                                                                                 |

A buy awaiting its **first** approval is a different case: that buy is itself
pending, so `status` is `PENDING_APPROVAL` and there is no `pendingChange`,
because nothing is live behind it yet. `pendingChange` only ever describes a
change queued behind an already-established buy.

## Related

<CardGroup cols={2}>
  <Card title="Media Buy" href="/v2/buyer/campaigns/media-buys" icon="receipt">
    The media-buy object and Apostra rollup status table
  </Card>

  <Card title="Tasks" href="/v2/guides/tasks" icon="list-check">
    Polling, webhooks, and back-off for the submitted task envelope
  </Card>

  <Card title="Get media buy status" href="/v2/buyer/campaigns/tasks/get-media-buy-status" icon="signal-stream">
    Poll live AdCP status across sales agents
  </Card>

  <Card title="Glossary" href="/v2/concepts/glossary" icon="book-a">
    One-line definitions of every v2 term
  </Card>
</CardGroup>
