pending entry holding the raw AdCP create_media_buy payload. You inspect it and record a decision: approved or rejected. Approved entries are forwarded upstream to the underlying sales agent by the storefront’s MCP layer; the forwardedAt stamp tells you when that succeeded.
For when this queue is used — the mediaBuyApproval setting, the manual-review
posture, and how you find out something is waiting — see
Reviewing buyer transactions.
What the pre-screen verdict means
Each queued entry carries one of three pre-screen verdicts: clearly on policy, needs a look, or clearly against an explicit rule. Deterministic hard findings supply a strong operator-facing rejection signal; AI-judged conflicts can only escalate. For the full verdict model, the one-way AI second opinion, and how the agent reads your written policy, see AI Business Rules.Delivery readiness
The pre-screen verdict answers “may I sell this?” It does not answer “can this buy actually run?” A buy can be perfectly on policy and still be unable to deliver — most often because no creative reached its packages. Each pendingcreate entry therefore also carries a readiness block, computed from the
buyer’s live media buy rather than the payload frozen at submit time:
packages[]— one entry per package as it will actually be forwarded.assignedCreativeIdsempty means that package will be sent with no creative, which the destination will usually refuse. Capped at 20 entries with creative-less packages listed first, so the cap can never be what hides one;packageCountis the buy’s true package total andcreativelessPackageCountcounts the unfilled packages across all of them.unusableCreatives[]— for a package with no creative, each creative on the buyer’s campaign that could not satisfy it and why: the measured value against the seller’s declared requirement (“requires a duration of at least 30000ms but asset is 15047ms”), or an ambiguous format identity naming the candidate options the buyer has to choose between. An empty list alongside an emptyassignedCreativeIdsmeans the buyer’s campaign has no creatives at all. The list is capped at five entries;unusableCreativeCountcarries the true total, so a truncated list is never mistaken for the whole answer.compatibleUnattachedCreativeIds[]— creatives on the campaign that do fit the package and still never attached to it. This is our failure, not the buyer’s: there is nothing for them to change. Report it rather than rejecting the buy. Kept separate so a package that demonstrably fits is never described as one the campaign has no creative for.creativeSyncFailures[]— creatives on that campaign whose sync to your storefront failed, with whatever reason the sync recorded. This reflects each creative’s latest sync state, so a failure the buyer has since fixed drops off. Capped at 10 entries;creativeSyncFailureCountcarries the total.
readiness is null on decided entries, on update approvals, when the check
could not run, and past the first 50 entries of a very long queue. Null always
means “not computed” — never “checked and healthy”.
An entry moves through pending → approved or pending → rejected. Decisions are terminal — deciding an entry that is no longer pending is rejected, so a double-decide cannot slip through. The revoked state covers buyer- or system-initiated cancellation after a decision and is not something you set on the decide endpoint. Each entry is keyed by the buyer’s AdCP mediaBuyId.
The portable Approvals Page keeps the decision and its forwarding result
separate. If your approval is recorded but delivery to one or more sources is
incomplete, it shows a partial-success notice and lets you explicitly confirm a
typed retry without leaving the Page. The same idempotency key is reused, so a
source that already received the buy cannot create a duplicate booking. A
source response that means “accepted and still processing” does not offer a
duplicate retry.
A forward a source refuses structurally is usually not a dead end. The entry is
stamped forwardedAt and the platform stops re-attempting it — re-sending an
identical payload to a source that refused it would fail the same way every
time. Once the underlying cause is fixed, the API can recover the buy in place:
Retry forwarding with
forceTerminal re-sends it under the same mediaBuyId with your approval
intact, so the buyer does not need to resubmit under a new id. This is an API
control; the chat and Pending operations surfaces still offer escalation rather
than a retry for these buys. A buy terminalized before any source was
contacted because of an unconfigured settlement currency, an expired FX quote,
or another permanent prerequisite cannot be recovered this way and must be
resubmitted. A storefront intake hold is different: the approved buy remains
queued with forwardedAt: null and forwards automatically after intake resumes.
Time spent on hold does not use up the forwarding retry window, and the original
approval time remains unchanged.
Buyer task callbacks
Buyer agents can attach AdCP push-notification config to the originalcreate_media_buy call. The storefront stores that callback configuration separately from the operator-facing payload and uses it when the approval resolves.
Storefronts now use the SDK 14 compatibility projection for direct AdCP
responses. Custom capability metadata is canonical at
ext.scope3.storefront; the deprecated extensions.scope3.storefront path is
temporarily dual-emitted in both MCP response representations while AdCP 3.0
and 3.1 buyers migrate. Demo disclosures also live below the canonical ext
path. See the migration guide for the complete
format, pricing, task-protocol, and response-field mapping.
push_notification_config for canonical AdCP requests. The storefront also accepts pushNotificationConfig from clients that pass camelCase JSON through a REST/MCP proxy. Callback URLs must be public HTTPS endpoints; localhost, private-network, link-local, metadata, and internal hostnames are rejected. Supported authentication schemes are Bearer and HMAC-SHA256.
When the operator approves and every underlying source accepts the forwarded buy, the buyer webhook receives a signed ADCP task event with status: "completed" and a result containing the accepted media_buy_id. When the operator rejects, or approval succeeds but source forwarding fails, the webhook receives status: "failed" with an AdCP-compatible error object. Polling the submitted task remains the fallback if no callback is supplied or delivery fails.
Media-buy task events use the AdCP task protocol media-buy; creative approval task events use creative. These webhook protocol values are distinct from the snake-case media_buy value advertised by storefront capabilities.
The approval row returned to storefront operators omits
push_notification_config and pushNotificationConfig from submittedPayload so webhook credentials are not exposed in the queue UI or REST responses.Authorization: Bearer $SCOPE3_API_KEY.
Task reference
List approvals
GET /media-buy-approvals — the approval queue, newest firstGet an approval
GET /media-buy-approvals/{mediaBuyId} — one queue entryDecide an approval
POST /media-buy-approvals/{mediaBuyId}/decide — approve or rejectRetry forwarding
POST /media-buy-approvals/{mediaBuyId}/retry-forward — re-send an approved buy