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 eight values:
A buy that never reached its source can read
canceled. If create_media_buy
is accepted by the storefront but the forward fails because of a malformed
request or credential fault, no media buy exists upstream and the buy is
canceled. A temporary source outage is retried for up to six hours before it
becomes terminal, 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.
Launching before the creative is ready
Before a buy goes to a seller, Apostra checks that every creative attached to it is already in that seller’s creative library. When a creative has not reached the seller yet, the launch is refused, and you have two choices:- Sync the creative and launch again. The buy goes to the seller with its creative attached.
- Launch without the creative. Remove the creative from the media buy and
launch it. The seller accepts the buy as
pending_creatives, and you attach the creative once the seller has it. Nothing delivers until a creative is approved and attached.
pendingReason is
no_creatives_attached, on the media buy and on the same buy inside its
campaign. This holds whatever lifecycle the seller reports for it, including
active or pending_start, so a buy the seller shows as live without a
creative does not read as delivering or merely scheduled. Attach a creative and
the reason clears.
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.
The status follows what the sales agent reports
A buy’s status, and each package’s status, is what the seller’s sales agent reports for it through AdCP. This is the same for Scope3’s managed sales agents and for third-party sales agents. Apostra does not read the seller’s ad server: it never sees line items, goals, pacing settings or ad-server statuses, and it does not hold a buy atpending_start, or pause a package, because of how
objects in an ad server are set up. A buy the sales agent reports active reads
active.
Keeping spend inside the budget is the sales agent’s job. A sales agent
guarantees that the ad-server objects behind a package cannot together spend
more than the budget it accepted. It refuses to activate, or pauses on its own,
a package it cannot cap, and says so as a status or an error. It also reports
each package’s effective budget, rate, pacing and flight as read from its ad
server, counting every object behind the package. Two line items that can each
spend the whole budget report twice the budget. Certification checks this for
every sales agent: see
How certification works.
Apostra compares what the sales agent reports with what you booked: budget,
rate, flight, targeting and creatives. A difference does not change the buy’s
status. It is shown to the seller as out of line with the agreed terms, and
Apostra’s operations team is alerted when the difference is financial and the
buy is live. See the agreed-terms check.
The budget compared is the amount sent to the seller, net of buyer fees and
converted into the seller’s currency where the buy crosses currencies.
A buy’s status follows the seller’s own status read
Apostra reads each seller two ways: a status read (get_media_buys) and a
delivery read (get_media_buy_delivery). Some sellers derive the status in
their delivery read from flight dates alone, so it can say active for a buy
the seller has not started. The status read is the authority. Whenever Apostra
holds a status read taken beside a delivery read, that part takes the status the
read reports, whether it is pending, active, paused or ended, and the
delivery read never overrides it. Apostra takes the status read immediately
before it ingests the delivery read, and an older status read does not decide,
because it may predate a change the delivery read reports, such as a pause.
Without a current status read, the delivery read’s status stands, except that a delivery read of active with no impressions or spend never overrides an older status read of pending, because that delivery status can be derived from flight dates alone.
If the delivery read shows impressions or spend while the status read says the
buy is pending, paused or ended, the buy keeps the status read’s status and
Apostra’s operations team is alerted once, so the seller can be asked which
read is right.
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.input-required status). On that call the caller is Apostra,
not you, and Apostra cannot supply the input yet, so the operation fails as
soon as Apostra sees the pause: either in the create_media_buy or
update_media_buy response, or as a failed task if the source paused it
later. Nothing is left waiting on the source. Notifications report the failure
with errorCode: platform_error. Sending the same request again will be paused
the same way.
Provider readback on a media buy
For a media buy on an adapter that supports readback (today Meta and OpenAI Ads),get(kind: "media_buy") can show the latest retained provider readback beside
the lifecycle state. readback.checkedAt says when we checked the provider,
while confirmedAt dates the provider value itself. A seller’s readback is
retained only once it is switched on for that seller account. A failed
check remains visible as outcome: "failed"; it does not make an older value
look current. If the last confirmed value is more than 15 minutes old,
stale: true makes that clear. A check that is still running has
outcome: "in_progress" and no confirmed provider value.
The read can also carry current indicators[]. interchange.provider_drift
means the provider and Interchange disagree about a tracked setting. Financial
drift names a budget or flight difference. interchange.discovered_object
means the provider reports a creative that Interchange did not dispatch. These
are signals to inspect the buy, not actions and not a hidden change at the
provider, and they never change the buy’s status: the seller owns that. When no
provider readback has been retained, the readback and indicator fields are absent rather
than inferred.
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 Apostra 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 accepted by a source (no upstream media-buy id and no seller task id), the cancel is a retraction: the pending request is withdrawn locally and no seller cancel is sent. The buy transitions tocanceled.
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 send their cancellation directly with no approval.
If the storefront returns an accepted task rather than terminal seller evidence,
the buy reads ending and its budget remains committed until source readback
confirms a terminal result.
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 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; use the cancellation
request/decision notifications to follow this transition.
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.
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.
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:
Buyer campaign and single-media-buy reads keep the seller’s more specific
value in
sourceStatus. Use it when displaying a buy: pending_start means
the buy was accepted and is waiting for its flight date, not that it is waiting
for approval. The coarser status remains available for filtering and older
clients.
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. Apostra 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 while a new version enters PENDING_APPROVAL. Once the seller approves it, the new budget and dates take effect. A historical correction stays completed. A later end date that puts the flight back in progress becomes active when the seller also reports the buy as active. A material budget increase can become active only while the current flight is still in progress, every seller source accepted the update, and its update response reports the buy as active. The earlier completed version remains in the buy’s history.
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:
pendingChange. For example, extending a
flight does not remove a queued creative change. An explicit creative_ids
update that differs from the queued creative change is refused with CONFLICT;
resolve or withdraw the queued change before replacing its creative set.
A creative change the seller refuses outright (for example a tag with a
placeholder the seller cannot fill) does not stay queued: the refused change is
closed, so the next creative change on the buy goes through. Archiving or
detaching a creative also removes it from any queued change.
A creative change whose seller call failed before the seller recorded an
approval is closed the same way when the next explicit creative_ids update
arrives, because that update replaces the whole creative set. A change the
seller may still be processing, or that is waiting for approval, stays queued
and still refuses a conflicting creative_ids update.
Read it like this:
pendingChangeabsent: nothing is queued. Everything you see is live.pendingChangepresent: a change was accepted by Apostra 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.
A buy found running at the seller
Apostra can ask each seller it routes buys to for every media buy the seller holds for those routes, and compare the answer with its own records. A buy the seller is running that Apostra has no record of, for example because the seller’s acceptance never reached us, is reviewed by Apostra staff. It is never added to your account automatically. If staff confirm with you that the buy is yours, they add it to the campaign you name. From then on it is an ordinary media buy in that campaign:- Its budget, currency and flight are the seller’s at the moment it is added. To change them, update the media buy as usual.
- Its status follows the seller, and reporting reads delivery from the seller like any other buy.
- Nothing is sent to the seller when it is added. The seller already holds the buy.
- Delivery from before the buy was added is billed to you only if you agreed to it, and then reporting reads back to the start of the flight. Otherwise you are billed from the day (UTC) after the buy was added, so neither that day nor any earlier day is billed to you. Reporting does not go back for earlier delivery, although it can show the day before the buy was added.
Related
Media Buy
The media-buy object and Apostra 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