Module adcp.decisioning.specialisms.creative_ad_server
CreativeAdServerPlatform Protocol — covers creative-ad-server.
A platform claiming creative-ad-server (Innovid, Flashtalking,
GAM-creative, CMP-style platforms) implements the methods on this
Protocol. The slug mirrors schemas/cache/enums/specialism.json.
Distinct from :class:CreativeBuilderPlatform (stateless transform /
brief-driven generation):
- Stateful — adopter persists creatives in a library;
sync_creativespushes assets in,list_creativesreads them back,build_creativeeither looks up an existing creative by id OR pushes a new one. - Pricing per creative — vendor pricing options on each creative;
pricing_option_idselected at activation, billed viareport_usage. - Tag generation —
build_creativereturns ad-server tags (VAST, placement-specific tracking pixels, macro-substituted creative HTML) when invoked withmedia_buy_id+package_idcontext. - Per-creative delivery reports —
get_creative_deliveryreturns pacing data per creative across the library.
Required methods (every creative-ad-server adopter):
- :meth:
build_creative— library lookup OR inline build with tag generation - :meth:
preview_creative— preview-only variant (NOT optional here, unlike :class:CreativeBuilderPlatform) - :meth:
list_creatives— read creatives from the library - :meth:
get_creative_delivery— per-creative delivery actuals
Optional:
- :meth:
sync_creatives— push creatives; hybrid sync/handoff for brand-suitability / S&P review
Mirrors the JS-side CreativeAdServerPlatform interface at
src/lib/server/decisioning/specialisms/creative-ad-server.ts.
Multi-archetype omni agents (a single tenant doing both stateless
transform AND library + tag generation) are rare in the wild; the
recommended pattern is to front each archetype as a separate tenant
via TenantRegistry.
Classes
class CreativeAdServerPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class CreativeAdServerPlatform(Protocol, Generic[TMeta]): """Stateful creative library + per-creative pricing + tag generation. Methods may be sync (return ``T`` directly) or async (return ``Awaitable[T]``); the dispatch adapter detects via :func:`asyncio.iscoroutinefunction` and runs sync methods on a thread pool. Throw :class:`adcp.decisioning.AdcpError` for buyer-fixable rejection. """ def build_creative( self, req: BuildCreativeRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[BuildCreativeSuccessResponse | Sequence[CreativeManifest] | CreativeManifest]: """Build / retrieve creative tags. Two invocation modes: * **Library lookup**: ``req.creative_id`` references an existing creative; return the manifest with tag fields populated (``vast_tag``, click trackers, etc.). When ``req.media_buy_id`` + ``req.package_id`` are also set, generate placement-specific tags with macro substitution baked in. * **Inline build**: ``req.creative_manifest`` is provided directly; transform / wrap it (similar to template archetype but with ad-server side effects: register the creative in the library, generate the tag, etc.). Sync at the wire level — the per-tool ``build-creative-response.json`` ``oneOf`` doesn't include a ``Submitted`` arm (spec inconsistency tracked as ``adcontextprotocol/adcp#3392``). Until the spec rolls Submitted into the ``oneOf``, slow tag-generation pipelines await in-request; status changes flow via ``ctx.publish_status_change``. Return shape: see :meth:`CreativeBuilderPlatform.build_creative` for the discriminated-arm rules — single :class:`CreativeManifest`, ``Sequence[CreativeManifest]`` for multi-format, or :class:`BuildCreativeSuccessResponse` envelope when you need ``sandbox``/``expires_at``/``preview``. """ ... def preview_creative( self, req: PreviewCreativeRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[PreviewCreativeResponse]: """Preview-only variant — sandbox URL or inline HTML, expires. Always sync. NOT optional for ad-server adopters (distinct from :class:`CreativeBuilderPlatform.preview_creative`, which is optional) — buyers expect preview surface from any stateful creative library. """ ... def list_creatives( self, req: ListCreativesRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListCreativesResponse]: """Read creatives from the library. Filters + pagination. When ``req.include_assignments``, include the buyer's package-assignment graph. When ``req.include_pricing``, include vendor pricing options on each creative. """ ... def get_creative_delivery( self, req: GetCreativeDeliveryRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetCreativeDeliveryResponse]: """Per-creative delivery actuals (impressions, spend, pacing). Sync — report-running platforms with manual report cycles return the latest cached actuals and emit ``delivery_report`` status changes via ``ctx.publish_status_change`` when fresh reports are available. """ ... def sync_creatives( self, req: SyncCreativesRequest, ctx: RequestContext[TMeta], ) -> SalesResult[SyncCreativesSuccessResponse]: """Push creatives. Optional — present-or-absent. Return the typed :class:`SyncCreativesSuccessResponse` for the sync fast path OR ``ctx.handoff_to_task(fn)`` for HITL — brand-suitability, S&P review. ``action: 'created'`` for new entries, ``'updated'`` for replacements, ``'unchanged'`` when matching. Optional ``status: 'pending_review'`` for sync-arm rows awaiting manual review. Same wire request type as the sales-* and creative-builder archetypes use (``SyncCreativesRequest`` — shared spec shape). """ ...Stateful creative library + per-creative pricing + tag generation.
Methods may be sync (return
Tdirectly) or async (returnAwaitable[T]); the dispatch adapter detects via :func:asyncio.iscoroutinefunctionand runs sync methods on a thread pool.Throw :class:
AdcpErrorfor buyer-fixable rejection.Ancestors
- typing.Protocol
- typing.Generic
Methods
def build_creative(self, req: BuildCreativeRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[BuildCreativeSuccessResponse | Sequence[CreativeManifest] | CreativeManifest]-
Expand source code
def build_creative( self, req: BuildCreativeRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[BuildCreativeSuccessResponse | Sequence[CreativeManifest] | CreativeManifest]: """Build / retrieve creative tags. Two invocation modes: * **Library lookup**: ``req.creative_id`` references an existing creative; return the manifest with tag fields populated (``vast_tag``, click trackers, etc.). When ``req.media_buy_id`` + ``req.package_id`` are also set, generate placement-specific tags with macro substitution baked in. * **Inline build**: ``req.creative_manifest`` is provided directly; transform / wrap it (similar to template archetype but with ad-server side effects: register the creative in the library, generate the tag, etc.). Sync at the wire level — the per-tool ``build-creative-response.json`` ``oneOf`` doesn't include a ``Submitted`` arm (spec inconsistency tracked as ``adcontextprotocol/adcp#3392``). Until the spec rolls Submitted into the ``oneOf``, slow tag-generation pipelines await in-request; status changes flow via ``ctx.publish_status_change``. Return shape: see :meth:`CreativeBuilderPlatform.build_creative` for the discriminated-arm rules — single :class:`CreativeManifest`, ``Sequence[CreativeManifest]`` for multi-format, or :class:`BuildCreativeSuccessResponse` envelope when you need ``sandbox``/``expires_at``/``preview``. """ ...Build / retrieve creative tags. Two invocation modes:
- Library lookup:
req.creative_idreferences an existing creative; return the manifest with tag fields populated (vast_tag, click trackers, etc.). Whenreq.media_buy_id+req.package_idare also set, generate placement-specific tags with macro substitution baked in. - Inline build:
req.creative_manifestis provided directly; transform / wrap it (similar to template archetype but with ad-server side effects: register the creative in the library, generate the tag, etc.).
Sync at the wire level — the per-tool
build-creative-response.jsononeOfdoesn't include aSubmittedarm (spec inconsistency tracked asadcontextprotocol/adcp#3392). Until the spec rolls Submitted into theoneOf, slow tag-generation pipelines await in-request; status changes flow viactx.publish_status_change.Return shape: see :meth:
CreativeBuilderPlatform.build_creativefor the discriminated-arm rules — single :class:CreativeManifest,Sequence[CreativeManifest]for multi-format, or :class:BuildCreativeSuccessResponseenvelope when you needsandbox/expires_at/preview. - Library lookup:
def get_creative_delivery(self, req: GetCreativeDeliveryRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetCreativeDeliveryResponse]-
Expand source code
def get_creative_delivery( self, req: GetCreativeDeliveryRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetCreativeDeliveryResponse]: """Per-creative delivery actuals (impressions, spend, pacing). Sync — report-running platforms with manual report cycles return the latest cached actuals and emit ``delivery_report`` status changes via ``ctx.publish_status_change`` when fresh reports are available. """ ...Per-creative delivery actuals (impressions, spend, pacing).
Sync — report-running platforms with manual report cycles return the latest cached actuals and emit
delivery_reportstatus changes viactx.publish_status_changewhen fresh reports are available. def list_creatives(self, req: ListCreativesRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ListCreativesResponse]-
Expand source code
def list_creatives( self, req: ListCreativesRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListCreativesResponse]: """Read creatives from the library. Filters + pagination. When ``req.include_assignments``, include the buyer's package-assignment graph. When ``req.include_pricing``, include vendor pricing options on each creative. """ ...Read creatives from the library.
Filters + pagination. When
req.include_assignments, include the buyer's package-assignment graph. Whenreq.include_pricing, include vendor pricing options on each creative. def preview_creative(self, req: PreviewCreativeRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[PreviewCreativeResponse]-
Expand source code
def preview_creative( self, req: PreviewCreativeRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[PreviewCreativeResponse]: """Preview-only variant — sandbox URL or inline HTML, expires. Always sync. NOT optional for ad-server adopters (distinct from :class:`CreativeBuilderPlatform.preview_creative`, which is optional) — buyers expect preview surface from any stateful creative library. """ ...Preview-only variant — sandbox URL or inline HTML, expires.
Always sync. NOT optional for ad-server adopters (distinct from :class:
CreativeBuilderPlatform.preview_creative, which is optional) — buyers expect preview surface from any stateful creative library. def sync_creatives(self, req: SyncCreativesRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[SyncCreativesSuccessResponse]-
Expand source code
def sync_creatives( self, req: SyncCreativesRequest, ctx: RequestContext[TMeta], ) -> SalesResult[SyncCreativesSuccessResponse]: """Push creatives. Optional — present-or-absent. Return the typed :class:`SyncCreativesSuccessResponse` for the sync fast path OR ``ctx.handoff_to_task(fn)`` for HITL — brand-suitability, S&P review. ``action: 'created'`` for new entries, ``'updated'`` for replacements, ``'unchanged'`` when matching. Optional ``status: 'pending_review'`` for sync-arm rows awaiting manual review. Same wire request type as the sales-* and creative-builder archetypes use (``SyncCreativesRequest`` — shared spec shape). """ ...Push creatives. Optional — present-or-absent.
Return the typed :class:
SyncCreativesSuccessResponsefor the sync fast path ORctx.handoff_to_task(fn)for HITL — brand-suitability, S&P review.action: 'created'for new entries,'updated'for replacements,'unchanged'when matching. Optionalstatus: 'pending_review'for sync-arm rows awaiting manual review.Same wire request type as the sales-* and creative-builder archetypes use (
SyncCreativesRequest— shared spec shape).