Skip to main content
When a buyer submits creatives to your storefront, each immutable content version lands in your creative review queue as a pending row. You inspect the submitted creative payload and record a terminal decision: approved or rejected. An approval can be reused when the identical creative appears on a later media buy; revised content creates a separate pending review and cannot inherit the old decision. The row’s submittedPayload is the credential-stripped submission snapshot used for review and delivery. It contains the creative object plus any request-level account, buyer_ref, and context. Delivery projects the creative fields downstream, uses account for request routing when needed, and preserves buyer_ref and context for callback correlation. contentDigest identifies only the creative object, excluding those three request-level fields, so changing correlation data does not create a different content decision or overwrite a decided snapshot. Webhook configuration is stored separately and is never returned in submittedPayload. After approval, the platform separately delivers the creative for each media buy, package assignment, route, and source. The reusable operator decision is not proof that a particular assignment reached the source or passed source-side review. For when this queue is used — the creativeApproval setting, the lowest-risk “review everything” posture, and how you find out something is waiting — see Reviewing buyer transactions. For how a creative is judged against your rules — the rendered-image check and the deterministic locked-asset fidelity guarantees that hold an exact-fidelity product, logo, or cover for human review when it isn’t deterministically preserved — see AI Business Rules. A review moves through pending → approved or pending → rejected. The revoked state — pulling a previously-approved creative — is a separate gesture and cannot be set on the decide endpoint. Each response preserves the legacy numeric id and also returns a collision-free reviewRef such as review:42. Use reviewRef for review actions. Bare identifiers always select the AdCP creative-id namespace, including when numeric; creative:<id> is its explicit form. A bare numeric creative id is never interpreted as the surrogate id. When deciding, also echo the rendered row’s contentDigest as expected_content_digest and, when present, its sourceTrust.reviewVersionDigest as expected_review_version_digest. This optimistic version fence prevents a decision made against an older payload from approving revised creative content. During the mixed-client rollout, a legacy caller may omit the digest only while that review remains on its original content generation and the frozen approval work item still matches. Once content changes, omission is refused with 409 CONFLICT; reload the row and send its digest.

Source trust and bounded preview behavior

When the server has receipt-bound provenance, a review version carries a versioned sourceTrust projection. Its classification is one of platform_managed_media, platform_assembled_static, or buyer_supplied_active; its evidence records a kind, digest, and policy version. Legacy or unproven source trust is returned as null (and can be omitted by an N-1 response), not as a classification. An active-source label is issued only when the server has authenticated buyer custody; tag-shaped data or platform-supplied metadata alone leaves provenance unavailable. Preview authority is a separate question: a provider selected for a placement does not make buyer source safe to execute. Interchange never renders response-provided image, video, audio, source, or other network-active asset URLs inside its own pages or a chat host; the live preview runs them only on the separate render origin. sourceTrust SHA-256 values are consistency checks, not signatures or rendering authority. The only response-provided presentation that may appear locally is bounded plain text from a platform_assembled_static classification, escaped as text. SVG, HTML, JavaScript, VAST/DAAST, trackers, executable URL assets, and source with unavailable provenance are never rendered in Interchange. A reviewer can explicitly request an interactive placement preview; that sends the exact creative manifest to the authorized provider, where network activity or code execution may occur. Interchange can receive and retain the complete provider response as opaque inert data, including HTML or render envelopes, but never mounts, executes, or prefetches it. Only the separately validated externalOpen envelope can be passed to the standard MCP host openLink bridge after a second explicit reviewer gesture. Static inspection displays source as text, bounded referenced domains, macros, and detected capabilities such as scripts, external resources, pixels, frames, forms, navigation, storage, service workers, and VAST/DAAST wrappers. It does not fetch, resolve, execute, use a browser, determine runtime behavior, or claim that a source is safe, clean, or malware-free. A truncated result is explicitly incomplete.

Live preview

When the live preview is enabled for your storefront, each pending review row carries a renderPreview link: a signed URL on Interchange’s dedicated render origin (render.interchange.io, or render.staging.interchange.io in staging) that shows the exact submitted creative running at placement size. In the Approvals page, Show preview mounts that link inside a sandboxed frame and Open full size opens it in a new tab through your chat host. Third-party display tags, hosted HTML, VAST video, DAAST audio, and hosted images, video, and audio all render there. The render origin is a separate, cookieless site with no access to Interchange, your chat host, or your session. The creative document is served as an opaque sandboxed origin, so the tag can load scripts, fire pixels, and animate, but cannot read anything outside its frame or navigate the page. Click macros in the tag are rewritten to an interstitial that shows the destination URL instead of following it. The creative may open new windows, because watching where a tag sends a user is part of the review, and every window it opens inherits the sandbox: it runs as an opaque origin that cannot script, read, or navigate the page you are reviewing from and cannot read cookies or storage itself. It is still an ordinary navigation to that site, so the site receives whatever cookies your browser already holds for it, exactly as the tag’s own pixels and scripts do. Treat every window the preview opens as buyer-controlled and do not sign in or act inside it. Preview macros are filled with test values (a cachebuster, GDPR applies = 0, an empty consent string, 1--- for US privacy). Each link expires after fifteen minutes and stops working the moment the buyer resubmits the creative or the review is decided, because the token is bound to the pending row’s contentDigest. Refreshing the queue issues a new one. If you build your own reviewer UI, embed the URL only in an iframe with sandbox="allow-scripts allow-popups" and referrerpolicy="no-referrer", or open it in a new tab; never fetch it server-side or render its contents yourself. Seeing a creative run is not a safety verdict. The static inspection below the preview (source, referenced domains, macros, detected behaviours) stays available so you can judge what the tag does as well as how it looks. Rows without a renderPreview (the origin is not enabled for your environment) keep source inspection, the publisher-provider request, and approval exactly as before.

Observed behaviour and thumbnails

When observation is enabled for your storefront, Interchange runs each newly submitted creative version once, headlessly, on its own render origin and records what the creative actually did. The review row’s observation field carries the result with a status (queued, running, completed, timed_out, or failed), the time the run finished, and, once completed, a record of the registrable domains the creative contacted (with request counts and kinds), pixel-shaped requests, redirects, popups, attempts to navigate the top window, console errors, bytes transferred, and any media it fetched. The record never contains a URL, the render token, or the creative’s own bytes; the pixel and navigation counts are heuristics and are labelled as such. thumbnailUrl is an app-relative, authenticated path to a PNG of the creative frame from that run, bounded to 600 px on its longest edge. It is served only to the storefront that owns the review and is never a public URL; a resubmitted creative gets a fresh observation and the previous picture stops being served, even to a caller holding the older version’s reviewRef. A row without an observation reads null for both fields. Observation is evidence from one run at one time from Interchange’s network, not a safety verdict, and it never gates review, the live preview, or your decision. In the Approvals page, the row’s Does line reads from that run when one has completed: the external domains the creative called (most-called first), tracking pixels fired, redirects followed and where to, popups and page navigations attempted, and media fetched, marked “observed” with how long ago. While a run is queued or running the line says “observing now”; when no run exists, or it timed out or failed, the line falls back to what can be read from the source and is marked “from source” with the outcome. The observed thumbnail appears above the row’s stage once it is available, before you open the live preview.

Placement-aware previews

When a requested review receives a valid authority-resolved HTTPS preview URL, the review UI shows its hostname, authority, fidelity, and expiry. An active source or one with unavailable provenance may be sent to the authorized provider only after the reviewer requests an interactive preview. A second View provider preview gesture revalidates the URL/origin/expiry and calls the standard MCP host openLink bridge; the widget does not render an anchor, iframe, or provider bytes. The destination may execute code, track, or redirect outside Interchange. The separate server-owned, zero-egress artifact renderer remains required for an inline rendered preview. Preview availability never prevents an approval. Current review rows can include previewContexts, one opaque context for each exact package and placement that can be resolved without guessing. Pass a context’s contextRef, the row’s contentDigest, and its sourceTrust.reviewVersionDigest when it is present to the preview endpoint. The server issues interactivePreviewAvailable when the deployment kill switch allows interactive preview; only true offers the explicit provider-dispatch action. A missing or false value is fail-closed: the review stays available for source inspection and approval or rejection. Legacy, standalone, defaulted, ambiguous, stale, or superseded routes have no preview context and remain source-inspection only. Preview authority is placement-specific. A publisher-designated preview provider is preferred for its exact placement, followed by that placement’s digest-verified presentation metadata. Provider output is never rendered inside Interchange, including secondary serving-platform output. When no authorized external URL is available, the reviewer sees the submitted manifest or inert tag/VAST source and an explicit no-preview state. Interchange never executes submitted tag code or remote VAST to manufacture an ad screenshot.

Buyer status and callbacks

Buyer agents submit creatives with AdCP sync_creatives. In manual-review mode the first response can return pending_review for each queued creative:
If the buyer calls sync_creatives again for unchanged content that has already been approved, the storefront reuses the operator decision. The per-creative status remains processing until every required source confirms the current media-buy assignment. It returns approved only with that delivery evidence. An operator rejection returns rejected with action: "failed" and the reviewer note as message when one was supplied. Buyers can attach AdCP push-notification config to the original sync_creatives request:
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 rejects the creative, or when an approved creative reaches a terminal delivery outcome, the buyer webhook receives a signed ADCP task event with status: "completed". Each buyer operation keeps its own callback and correlation fields. Failed webhook deliveries remain pending and are retried; one operation cannot overwrite another operation’s callback.

Per-creative webhook outcomes

The task envelope is always status: "completed" — the per-creative outcome lives in result.creatives[] as an action and status pair. Read both fields: action: "failed" on its own does not mean the operator declined the creative. action: "failed" with status: "approved" means the operator approved the creative but the storefront could not deliver it to every required inventory source after bounded retries — the creative is not live. Treat it as an actionable delivery failure (re-submit, or contact the operator), distinct from a content rejection: the discriminator is errors[].code: "delivery_failed" together with status: "approved". The inventory source is never named, and a permanent failure and a retry-deadline failure are indistinguishable on the wire — both surface as this row. Partial multi-source delivery remains incomplete and is never rolled up as approved. An observable source task may remain processing beyond the 30-minute network and route retry window while a human or ad server reviews it. The storefront continues polling that durable task for up to seven days from the first source delivery attempt. If the source still has not returned a terminal decision, the storefront records the terminal delivery_failed outcome above and sends the buyer callback; a source task cannot remain callback-silent indefinitely.
The creative review 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 reviews

GET /creative-reviews — the review queue, newest first

Get a review

GET /creative-reviews/{creativeId} — one creative review row

Preview a review

POST /creative-reviews/{creativeId}/preview — resolve a placement-aware preview

Decide a review

POST /creative-reviews/{creativeId}/decide — approve or reject