Skip to main content
“Why isn’t my buy live?” used to mean campaign archaeology — cross-referencing status polls, guessing who was holding things up, and opening a support ticket with nothing to quote. It doesn’t anymore. Every storefront-routed media buy carries a shared pendingReason vocabulary that says what it’s waiting on and whose side owns the wait, a buyer-safe errorCode when something failed, and a reference each party can quote to the other. This guide walks the same buy from both sides.
  • Buyers get there in one call — see I’m a buyer.
  • Sellers get three read-only surfaces in chat or REST — see I’m a seller.
  • Either side can escalate to Scope3 with a reference the other side already has — see What Scope3 sees.

I’m a buyer — why isn’t my buy live?

One-call lookup

GET /api/v2/buyer/media-buys/{mediaBuyId} (the get_media_buy operation, callable via api_call) returns one media buy with its full why-visibility field set. Use this first — it answers in one call instead of re-reading the campaign.
(mb_h3ctv001 and the sf: reference are illustrative — substitute your own media buy’s id.) These fields also appear on the nested media buys in GET /campaigns/:id and on the media-buy status poll — this is just the fastest single-buy path to them.

pendingReason — what it’s waiting on, and whose side owns it

pendingReason is a derived annotation, never a status — the buy’s status is unchanged; this just explains the wait. See Media buy lifecycle for the full model, including how a buy’s AdCP status, this annotation, and cancellation approval interact.

errorCode — ownership and the seller’s own words

When forwarding failed or the buy was rejected, errorCode carries one of a small buyer-safe set, paired with errorOwner (who owns the fix) and — when the source or reviewer provided one — their sanitized sourceMessage: sourceMessage is the first thing to read on a rejection — it’s the source’s or reviewer’s own explanation, not a generic code.

forwardedAt and buyerReference — what to quote to support

forwardedAt is when the buy was sent to its inventory source(s) — absent if it hasn’t been forwarded yet. buyerReference (sf:<storefrontId>:<mediaBuyId>) identifies this buy’s exchange with the seller. Quote it, together with forwardedAt, when contacting the seller or Scope3 support about a stuck buy.

Transition notifications

The same facts push to your notification stream as media_buy.* events, fired once per transition: Each payload carries mediaBuyId, buyerReference, and the applicable pendingReason / errorCode / errorOwner / sourceMessage — a reacting agent doesn’t need a second call to learn why.

Sample queries

Sample agent prompts

“Check why media buy mb_h3ctv001 isn’t delivering and tell me whose side it’s on.” “Use api_call to get media buy mb_h3ctv002 — what’s the errorCode, who owns fixing it, and what does the source’s message say?” “Is my CTV buy on Premium News actually live yet? If not, tell me the pendingReason and when it started waiting.”

I’m a seller — what needs my attention?

Three read-only surfaces answer “what do I have, where is one stuck, and what needs work” — each available as a REST endpoint and as an in-chat surface. Open them from the rail’s Operate section, or just ask Murph.

Media buys — every buy on the storefront

Ask “show me all my media buys” or “show me everything that’s up”, or call GET /media-buys. It returns every buy — routed and ESA-managed — sorted by urgency: buys still waiting on someone whose flight starts within 48 hours (or has already started) come first. Media buys list, urgency-sorted, showing a forward-failed buy, one awaiting source moderation, one pending approval, and completed buys Each row carries the seller-facing status (a platform list-view convenience, never an AdCP status), the raw sourceStatus, the same pendingReason vocabulary buyers see, the errorCode of the latest failed exchange, and the forwardOutcome.

Media buy timeline — what was sent, when, and where it’s stuck

Ask “what did you send Meridian Ads for buy sf_mb_example_9f2a1c and when?”, or open the buy’s timeline directly. GET /media-buys/{mediaBuyId}/timeline traces one buy end to end:
Media buy timeline showing a buy approved, forwarded, and parked in source moderation, with the source's own reference and the platform sf: reference The forwarded payload is yours to inspect — you’re the counterparty — minus platform-internal fields (webhook config and signing material are removed). A payload shown as {"pruned": true} means retention removed the stored bytes; the slim event record stays indefinitely. Whose reference to quote to whom:

Pending operations — everything waiting on someone

Ask “show me everything pending”, or call GET /pending-operations. It opens the portable Pending Operations Page and unions work in flight, grouped. A group appears only when it’s non-empty: approvals, creativeReviews, failedForwards (grouped by error code), awaitingSource, sourceDegradations, and gamCleanups. Row actions open the exact Approvals, timeline, retry, or source-diagnostics surface without generating another chat prompt. A listed GAM cleanup uses an explicit two-step archive or manual-cleanup confirmation in the Page. Pending operations view showing approvals waiting, a creative review, a structural failed-forward group offering only Escalate to Scope3, a transient failed-forward group offering Retry, and a buy waiting on source moderation

Recovery classes: why some failures have no retry

Every forwarding failure carries a recovery class, and the class — not optimism — decides which action is offered: A transient failure whose retry window lapses is treated as terminalized and also becomes escalate-only.

Status changed directly in your ad server

For an ad-server-backed (ESA-managed) source, your ad server holds the truth. If you approve an order directly in the ad server — say you approve a GAM order outside the storefront’s approval flow — the storefront won’t see that change until the source next syncs, so the buy can keep showing pending_approval while the order is actually approved and delivering. A source refresh reconciles it: it re-checks every non-terminal media buy (pending_approval, pending_start, paused) against the ad server and corrects any status that drifted. Ask Murph to “refresh my [source] source” (or “the GAM order is approved but the buy still shows pending — refresh it”); Murph triggers the refresh and reports the reconciled status. You don’t need to escalate for a status that the ad server has already resolved. This does not cover a buy that already shows a terminal buyer-facing status like REJECTED — that’s outside the refresh’s non-terminal scope and needs a separate retry/reconciliation action, not just a source refresh.
You don’t 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) — fired once, on the transition, never repeatedly.

Sample agent prompts

“Show me all buys that are up.” “Show me everything pending.” “What did you send Premium News Network for buy sf_mb_example_9f2a1c, and when?”

What Scope3 sees

Every exchange between the platform and an inventory source is durably recorded, so support can trace any buy end to end from the same references you already have — no ticket that starts from scratch. When you escalate, quote your buyerReference (buyers) or the sf: idempotency key together with its request timestamp (sellers); that’s the same handle Scope3 uses to pull up the exchange.

Media buy lifecycle

The AdCP status enum, pendingReason, and how the three status surfaces relate

Media Buy

The buyer media-buy object, including the full why-visibility field reference

Media buys & pending operations

The seller-side REST reference for all three surfaces in this guide

Notifications

Configuring where transition events are delivered