Skip to main content
A media buy 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.
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.
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.

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.
Both are supported. Choose the second when the flight should be booked now and the creative will follow. A launched buy with no creative attached reads as waiting for creative: its 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 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.

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 at pending_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:
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 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.
A source behind a storefront can also pause a create or an update for more input (the AdCP 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.
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.

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 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.
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 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 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 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 an APPROVE_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.
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.
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.

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).
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.
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.
For Apostra rollup and how to poll it, read Get media buy status. The adcp_status field on that response carries the raw AdCP wire status this page defines.

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 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:
If a later update changes a different part of the buy, the queued change carries forward unchanged and remains under 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:
  • 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.
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.

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.
Ask support if a media buy appears in a campaign that you did not expect.

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