Skip to main content
When a buyer submits a media buy to your storefront, it lands in your approval queue as a 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 pending create 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. assignedCreativeIds empty 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; packageCount is the buy’s true package total and creativelessPackageCount counts 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 empty assignedCreativeIds means the buyer’s campaign has no creatives at all. The list is capped at five entries; unusableCreativeCount carries 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; creativeSyncFailureCount carries the total.
Readiness is disclosure, not a gate. An unready buy still forwards when you approve it — a media buy never fails to forward on creative grounds, because a creative can be attached after booking. What readiness changes is that you see the state before spending a decision, and can go back to the buyer instead. 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 original create_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.
Use 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.
All examples use the storefront base URL:
Authenticate every request with Authorization: Bearer $SCOPE3_API_KEY.

Task reference

List approvals

GET /media-buy-approvals — the approval queue, newest first

Get an approval

GET /media-buy-approvals/{mediaBuyId} — one queue entry

Decide an approval

POST /media-buy-approvals/{mediaBuyId}/decide — approve or reject

Retry forwarding

POST /media-buy-approvals/{mediaBuyId}/retry-forward — re-send an approved buy