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 versionedsourceTrust 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 arenderPreview 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’sobservation 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 hostopenLink
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 AdCPsync_creatives. In manual-review mode the first response can return pending_review for each queued creative:
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:
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 alwaysstatus: "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.Authorization: Bearer $SCOPE3_API_KEY.
Task reference
List reviews
GET /creative-reviews — the review queue, newest firstGet a review
GET /creative-reviews/{creativeId} — one creative review rowPreview a review
POST /creative-reviews/{creativeId}/preview — resolve a placement-aware previewDecide a review
POST /creative-reviews/{creativeId}/decide — approve or reject