pending_creatives, and shows how a buy a source accepts
out-of-band resolves through a submitted task.
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 before treating any one as
“the” status.
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:
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.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.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.
Paused buys still show setup blockers
Creating a buy withpaused: 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:
- With no launch-ready creative, the buy reads
pending_creatives. - With creatives ready but a future flight, it reads
pending_start. - Once the buy is otherwise ready to launch, it reads
pausedinstead of becomingactive.
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: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 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:
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.Receiving the async result without polling
Instead of polling, a buyer can register apush_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_buyorupdate_media_buytool directly can include apush_notification_configfield 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 Interchange webhook subscriptions: register one viacreate_webhook_subscriptionand subscribe tomedia_buy.approved,media_buy.rejected, and related event types. See Notifications.
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 AdCPdelete 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 tocanceled.
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), the cancel requires seller approval. The storefront operator sees the request as anAPPROVE_MEDIA_BUY_CANCELLATION item in their
approvals queue, and the buy
keeps delivering while the operator decides.
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.
pendingReason of
awaiting_cancellation_approval, not as a status change.
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).
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.
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.
The Interchange buyer rollup is a coarser, derived view of the AdCP per-buy
status — not an independent one. Each AdCP value maps to one Interchange value,
and
pending_creatives and pending_start both collapse to PENDING_APPROVAL:
For a multi-agent campaign the rollup also surfaces the most restrictive
value across agents (see
Most-restrictive rule),
so a campaign with one
pending_creatives buy and one active buy reports
PENDING_APPROVAL. Interchange also has states from its own flow that no AdCP
wire value maps to — DRAFT (before execution), INPUT_REQUIRED, FAILED, and
ARCHIVED.
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 acompleted 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:
pendingChangeabsent: nothing is queued. Everything you see is live.pendingChangepresent: a change was accepted by Interchange but has not reached the delivering buy.differenceslists only the fields whose proposed value differs from live. An emptydifferencesmeans the change is redundant against what is already live.
Where each surface reports it
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
Media Buy
The media-buy object and the Interchange rollup status table
Tasks
Polling, webhooks, and back-off for the submitted task envelope
Get media buy status
Poll live AdCP status across sales agents
Glossary
One-line definitions of every v2 term