Module adcp.decisioning.specialisms
Per-specialism Protocol classes.
Adopters claim specialisms via DecisioningCapabilities.specialisms
and implement the matching Protocol's methods on their
:class:DecisioningPlatform subclass. Method names are unified
across specialisms — a platform claiming both sales-non-guaranteed
and sales-broadcast-tv implements create_media_buy once and
returns a hybrid :class:SalesResult that branches per call.
Public surface re-exported from :mod:adcp.decisioning.specialisms:
- :class:
SalesPlatform— covers the specsales-*slugs (non-guaranteed, guaranteed, broadcast-tv, social, proposal-mode, catalog-driven) under one unified hybrid shape. - :class:
SignalsPlatform— coverssignal-marketplace. Two methods:get_signals(catalog discovery) andactivate_signal(provisioning onto destination platforms). - :class:
OwnedSignalsPlatform— coverssignal-owned. One method:get_signals(publisher-owned signal catalog discovery; signals are already usable on seller inventory). - :class:
AudiencePlatform— coversaudience-sync. Two methods:sync_audiences(push first-party CRM audiences with delta upsert) andpoll_audience_statuses(batch state read). - :class:
CreativeBuilderPlatform— coverscreative-template+creative-generative. Requiredbuild_creative; optionalpreview_creative,sync_creatives. Unified shape per JS commit841616d7(F13) — wire spec doesn't distinguish template-driven transform from brief-to-creative generation. (No separaterefine_creativemethod — refinement is invoked viabuild_creativewithcreative_idreferencing the prior build, perschemas/cache/media-buy/build-creative-request.json.) - :class:
CreativeAdServerPlatform— coverscreative-ad-server. Stateful library + per-creative pricing + tag generation. Requiredbuild_creative,preview_creative,list_creatives,get_creative_delivery; optionalsync_creatives. - :class:
CampaignGovernancePlatform— coversgovernance-spend-authority+governance-delivery-monitor. Requiredcheck_governance,sync_plans,report_plan_outcome,get_plan_audit_logs. NOTE: a third governance slug,governance-aware-seller, names a SELLER claim (sales-* archetype that composes with a governance agent) — it does NOT implement this Protocol; it integrates WITH a platform that does. That slug stays unenforced until sync_governance handler shim wiring lands for sales adopters. - :class:
BrandRightsPlatform— coversbrand-rights. Requiredget_brand_identity,get_rights,acquire_rights(multi-arm acquisition result), andupdate_rights(inline or handoff). - :class:
ContentStandardsPlatform— coverscontent-standards. 6 required CRUD + calibration + validation methods; 2 optional analyzer reads (get_media_buy_artifacts,get_creative_features). - :class:
PropertyListsPlatform— coversproperty-lists. Standard 5-method CRUD with fetch-token issuance. - :class:
CollectionListsPlatform— coverscollection-lists. Parallel CRUD shape on collection-list types.
The breadth sprint is now COMPLETE — every spec specialism slug
except governance-aware-seller has REQUIRED_METHODS coverage
and a Protocol class. governance-aware-seller stays unenforced
by design (it's a SELLER composition claim, not a wire-implementor
claim — see :class:CampaignGovernancePlatform docstring).
Sub-modules
adcp.decisioning.specialisms.audience-
AudiencePlatform Protocol — covers the
audience-syncspecialism … adcp.decisioning.specialisms.brand_rights-
BrandRightsPlatform Protocol — covers
brand-rights… adcp.decisioning.specialisms.content_standards-
ContentStandardsPlatform Protocol — covers
content-standards… adcp.decisioning.specialisms.creative-
CreativeBuilderPlatform Protocol — covers
creative-template+creative-generative… adcp.decisioning.specialisms.creative_ad_server-
CreativeAdServerPlatform Protocol — covers
creative-ad-server… adcp.decisioning.specialisms.governance-
CampaignGovernancePlatform Protocol — covers
governance-spend-authorityandgovernance-delivery-monitor… adcp.decisioning.specialisms.lists-
PropertyListsPlatform + CollectionListsPlatform — list-publishing specialisms …
adcp.decisioning.specialisms.sales-
SalesPlatform Protocol — covers every
sales-*specialism … adcp.decisioning.specialisms.signals-
SignalsPlatform Protocols for marketplace and owned signals …
Classes
class AudiencePlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class AudiencePlatform(Protocol, Generic[TMeta]): """Sync first-party CRM audiences with delta upsert semantics. 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 (``AUDIENCE_TOO_SMALL``, ``REFERENCE_NOT_FOUND``, etc.); the framework projects to the wire structured-error envelope. """ def sync_audiences( self, audiences: Sequence[SyncAudiencesAudience], ctx: RequestContext[TMeta], ) -> MaybeAsync[SyncAudiencesSuccessResponse]: """Push audiences to the platform. Framework handles batching, idempotency, and cross-tenant scoping; the adopter handles match-rate computation and activation lifecycle. Sync acknowledgment with status changes via ``ctx.publish_status_change``: return per-audience result rows immediately (``'pending'`` / ``'matching'`` are valid sync outcomes). The match-rate computation and activation pipeline run in the background — call ``ctx.publish_status_change(resource_type='audience', ...)`` from the platform's webhook handler / job queue / cron when each audience reaches a terminal state. :param audiences: List of audience rows projected from the wire ``SyncAudiencesRequest.audiences[]`` field. Adopter ergonomic — receives the list directly rather than the full request. :raises adcp.decisioning.AdcpError: for buyer-fixable rejection (e.g., ``AUDIENCE_TOO_SMALL``). """ ... def poll_audience_statuses( self, audience_ids: Sequence[str], ctx: RequestContext[TMeta], ) -> MaybeAsync[Mapping[str, str]]: """Batch-poll current status for one or more audiences. Sync — this is a state-read, not a mutating operation. Useful for buyer-side polling outside the framework's task envelope (e.g., querying long-lived audiences) and for adapter code that needs to check N audiences at once. Returns a ``dict[audience_id, AudienceStatus]``. Audiences not found are omitted from the map (callers handle missing keys); raise ``AdcpError(code='REFERENCE_NOT_FOUND')`` only when the entire batch is unresolvable for the tenant. Single-audience polling is ``poll_audience_statuses([id], ctx).get(id)``. The batch shape composes with upstream identity-graph APIs that natively return per-audience-id arrays — adopters do NOT need to wrap a single-id lookup over an N-call loop. Adopter-internal helper — not surfaced as a wire tool. Used by adopter code orchestrating cross-platform audience flows and by the framework's optional bulk-status middleware. """ ...Sync first-party CRM audiences with delta upsert semantics.
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 (AUDIENCE_TOO_SMALL,REFERENCE_NOT_FOUND, etc.); the framework projects to the wire structured-error envelope.Ancestors
- typing.Protocol
- typing.Generic
Methods
def poll_audience_statuses(self, audience_ids: Sequence[str], ctx: RequestContext[TMeta]) ‑> MaybeAsync[Mapping[str, str]]-
Expand source code
def poll_audience_statuses( self, audience_ids: Sequence[str], ctx: RequestContext[TMeta], ) -> MaybeAsync[Mapping[str, str]]: """Batch-poll current status for one or more audiences. Sync — this is a state-read, not a mutating operation. Useful for buyer-side polling outside the framework's task envelope (e.g., querying long-lived audiences) and for adapter code that needs to check N audiences at once. Returns a ``dict[audience_id, AudienceStatus]``. Audiences not found are omitted from the map (callers handle missing keys); raise ``AdcpError(code='REFERENCE_NOT_FOUND')`` only when the entire batch is unresolvable for the tenant. Single-audience polling is ``poll_audience_statuses([id], ctx).get(id)``. The batch shape composes with upstream identity-graph APIs that natively return per-audience-id arrays — adopters do NOT need to wrap a single-id lookup over an N-call loop. Adopter-internal helper — not surfaced as a wire tool. Used by adopter code orchestrating cross-platform audience flows and by the framework's optional bulk-status middleware. """ ...Batch-poll current status for one or more audiences.
Sync — this is a state-read, not a mutating operation. Useful for buyer-side polling outside the framework's task envelope (e.g., querying long-lived audiences) and for adapter code that needs to check N audiences at once.
Returns a
dict[audience_id, AudienceStatus]. Audiences not found are omitted from the map (callers handle missing keys); raiseAdcpError(code='REFERENCE_NOT_FOUND')only when the entire batch is unresolvable for the tenant.Single-audience polling is
poll_audience_statuses([id], ctx).get(id). The batch shape composes with upstream identity-graph APIs that natively return per-audience-id arrays — adopters do NOT need to wrap a single-id lookup over an N-call loop.Adopter-internal helper — not surfaced as a wire tool. Used by adopter code orchestrating cross-platform audience flows and by the framework's optional bulk-status middleware.
def sync_audiences(self, audiences: Sequence[SyncAudiencesAudience], ctx: RequestContext[TMeta]) ‑> MaybeAsync[SyncAudiencesSuccessResponse]-
Expand source code
def sync_audiences( self, audiences: Sequence[SyncAudiencesAudience], ctx: RequestContext[TMeta], ) -> MaybeAsync[SyncAudiencesSuccessResponse]: """Push audiences to the platform. Framework handles batching, idempotency, and cross-tenant scoping; the adopter handles match-rate computation and activation lifecycle. Sync acknowledgment with status changes via ``ctx.publish_status_change``: return per-audience result rows immediately (``'pending'`` / ``'matching'`` are valid sync outcomes). The match-rate computation and activation pipeline run in the background — call ``ctx.publish_status_change(resource_type='audience', ...)`` from the platform's webhook handler / job queue / cron when each audience reaches a terminal state. :param audiences: List of audience rows projected from the wire ``SyncAudiencesRequest.audiences[]`` field. Adopter ergonomic — receives the list directly rather than the full request. :raises adcp.decisioning.AdcpError: for buyer-fixable rejection (e.g., ``AUDIENCE_TOO_SMALL``). """ ...Push audiences to the platform.
Framework handles batching, idempotency, and cross-tenant scoping; the adopter handles match-rate computation and activation lifecycle.
Sync acknowledgment with status changes via
ctx.publish_status_change: return per-audience result rows immediately ('pending'/'matching'are valid sync outcomes). The match-rate computation and activation pipeline run in the background — callctx.publish_status_change(resource_type='audience', ...)from the platform's webhook handler / job queue / cron when each audience reaches a terminal state.:param audiences: List of audience rows projected from the wire
SyncAudiencesRequest.audiences[]field. Adopter ergonomic — receives the list directly rather than the full request. :raises adcp.decisioning.AdcpError: for buyer-fixable rejection (e.g.,AUDIENCE_TOO_SMALL).
class BrandRightsPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class BrandRightsPlatform(Protocol, Generic[TMeta]): """Brand identity discovery + rights licensing. 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 REQUEST rejection (``REFERENCE_NOT_FOUND``, ``INVALID_REQUEST``, ``BUDGET_TOO_LOW``). For spec-defined GRANT rejection (rights unavailable in jurisdiction, talent dispute pending) return the :class:`AcquireRightsRejectedResponse` arm so the buyer sees the structured wire response with ``reason`` + ``suggestions``. """ def get_brand_identity( self, req: GetBrandIdentityRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetBrandIdentitySuccessResponse]: """Read brand identity record — ``brand_id``, ``house``, localized ``names``, optional logos / industries / ``keller_type``. Sync; no async ceremony. :raises adcp.decisioning.AdcpError: ``code='REFERENCE_NOT_FOUND'`` when the brand reference doesn't resolve to an identity the platform tracks. """ ... def get_rights( self, req: GetRightsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetRightsSuccessResponse]: """List rights matching a brand + use query. Sync read; framework wraps the response in the wire envelope. Returning an empty ``rights`` array is valid (= "no rights available for the requested terms"); throw ``AdcpError`` only for buyer-fixable rejection (e.g., unsupported jurisdiction). Note: the wire field is ``rights``, NOT ``offerings``. Adopters who named their internal model ``offerings`` translate at this seam. """ ... def acquire_rights( self, req: AcquireRightsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[AcquireRightsResponse]: """Acquire rights — buyer commits to an offering. Three discriminated wire arms: * :class:`AcquireRightsAcquiredResponse` — rights granted immediately. Carries ``rights_id``, ``status: 'acquired'``, ``brand_id``, ``terms``, ``generation_credentials`` (scoped per-LLM-provider keys), and ``rights_constraint`` so the buyer can plumb the grant directly into creative generation. * :class:`AcquireRightsPendingResponse` — clearance pending counter-signature, legal review, or rights-holder approval. Carries ``rights_id``, ``status: 'pending_approval'``, ``brand_id``, plus optional ``detail`` and ``estimated_response_time``. **Async delivery is webhook-only** — the buyer's ``push_notification_config.url`` receives the eventual ``Acquired`` or ``Rejected`` outcome. The spec does NOT define a polling tool for ``acquire_rights``; do not reach for ``tasks_get`` here. * :class:`AcquireRightsRejectedResponse` — terminal rejection. Carries ``rights_id``, ``status: 'rejected'``, ``brand_id``, ``reason``, and optional ``suggestions[]`` for buyer remediation. Pre-flight (catalog availability, agency authorization) MUST run sync regardless of arm — invalid requests reject before allocating any state. :raises adcp.decisioning.AdcpError: only for buyer-fixable REQUEST rejection (``INVALID_REQUEST``, ``BUDGET_TOO_LOW``). For GRANT rejection return the :class:`AcquireRightsRejectedResponse` arm — that's the structured business outcome path. """ ... def update_rights( self, req: UpdateRightsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdateRightsResponse]: """Update an existing rights grant. Return the update result inline. The beta.5 response schema has no Submitted arm, so task/workflow handoffs are rejected before launch. A buyer-supplied push configuration is reserved for later rights notifications, not a pollable task lifecycle for this response. """ ... def verify_brand_claim( self, req: VerifyBrandClaimRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[VerifyBrandClaimResponse]: """Verify one brand claim. Optional beta 3 surface. Implement when the brand agent can authoritatively confirm or reject parent, subsidiary, property, or trademark claims. """ ... def verify_brand_claims( self, req: VerifyBrandClaimsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[VerifyBrandClaimsResponseBulk]: """Verify many brand claims in one call. Optional bulk companion to :meth:`verify_brand_claim`. """ ...Brand identity discovery + rights licensing.
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 REQUEST rejection (REFERENCE_NOT_FOUND,INVALID_REQUEST,BUDGET_TOO_LOW). For spec-defined GRANT rejection (rights unavailable in jurisdiction, talent dispute pending) return the :class:AcquireRightsRejectedResponsearm so the buyer sees the structured wire response withreason+suggestions.Ancestors
- typing.Protocol
- typing.Generic
Methods
def acquire_rights(self, req: AcquireRightsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[AcquireRightsResponse]-
Expand source code
def acquire_rights( self, req: AcquireRightsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[AcquireRightsResponse]: """Acquire rights — buyer commits to an offering. Three discriminated wire arms: * :class:`AcquireRightsAcquiredResponse` — rights granted immediately. Carries ``rights_id``, ``status: 'acquired'``, ``brand_id``, ``terms``, ``generation_credentials`` (scoped per-LLM-provider keys), and ``rights_constraint`` so the buyer can plumb the grant directly into creative generation. * :class:`AcquireRightsPendingResponse` — clearance pending counter-signature, legal review, or rights-holder approval. Carries ``rights_id``, ``status: 'pending_approval'``, ``brand_id``, plus optional ``detail`` and ``estimated_response_time``. **Async delivery is webhook-only** — the buyer's ``push_notification_config.url`` receives the eventual ``Acquired`` or ``Rejected`` outcome. The spec does NOT define a polling tool for ``acquire_rights``; do not reach for ``tasks_get`` here. * :class:`AcquireRightsRejectedResponse` — terminal rejection. Carries ``rights_id``, ``status: 'rejected'``, ``brand_id``, ``reason``, and optional ``suggestions[]`` for buyer remediation. Pre-flight (catalog availability, agency authorization) MUST run sync regardless of arm — invalid requests reject before allocating any state. :raises adcp.decisioning.AdcpError: only for buyer-fixable REQUEST rejection (``INVALID_REQUEST``, ``BUDGET_TOO_LOW``). For GRANT rejection return the :class:`AcquireRightsRejectedResponse` arm — that's the structured business outcome path. """ ...Acquire rights — buyer commits to an offering.
Three discriminated wire arms:
- :class:
AcquireRightsAcquiredResponse— rights granted immediately. Carriesrights_id,status: 'acquired',brand_id,terms,generation_credentials(scoped per-LLM-provider keys), andrights_constraintso the buyer can plumb the grant directly into creative generation. - :class:
AcquireRightsPendingResponse— clearance pending counter-signature, legal review, or rights-holder approval. Carriesrights_id,status: 'pending_approval',brand_id, plus optionaldetailandestimated_response_time. Async delivery is webhook-only — the buyer'spush_notification_config.urlreceives the eventualAcquiredorRejectedoutcome. The spec does NOT define a polling tool foracquire_rights; do not reach fortasks_gethere. - :class:
AcquireRightsRejectedResponse— terminal rejection. Carriesrights_id,status: 'rejected',brand_id,reason, and optionalsuggestions[]for buyer remediation.
Pre-flight (catalog availability, agency authorization) MUST run sync regardless of arm — invalid requests reject before allocating any state.
:raises adcp.decisioning.AdcpError: only for buyer-fixable REQUEST rejection (
INVALID_REQUEST,BUDGET_TOO_LOW). For GRANT rejection return the :class:AcquireRightsRejectedResponsearm — that's the structured business outcome path. - :class:
def get_brand_identity(self, req: GetBrandIdentityRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetBrandIdentitySuccessResponse]-
Expand source code
def get_brand_identity( self, req: GetBrandIdentityRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetBrandIdentitySuccessResponse]: """Read brand identity record — ``brand_id``, ``house``, localized ``names``, optional logos / industries / ``keller_type``. Sync; no async ceremony. :raises adcp.decisioning.AdcpError: ``code='REFERENCE_NOT_FOUND'`` when the brand reference doesn't resolve to an identity the platform tracks. """ ...Read brand identity record —
brand_id,house, localizednames, optional logos / industries /keller_type. Sync; no async ceremony.:raises adcp.decisioning.AdcpError:
code='REFERENCE_NOT_FOUND'when the brand reference doesn't resolve to an identity the platform tracks. def get_rights(self, req: GetRightsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetRightsSuccessResponse]-
Expand source code
def get_rights( self, req: GetRightsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetRightsSuccessResponse]: """List rights matching a brand + use query. Sync read; framework wraps the response in the wire envelope. Returning an empty ``rights`` array is valid (= "no rights available for the requested terms"); throw ``AdcpError`` only for buyer-fixable rejection (e.g., unsupported jurisdiction). Note: the wire field is ``rights``, NOT ``offerings``. Adopters who named their internal model ``offerings`` translate at this seam. """ ...List rights matching a brand + use query.
Sync read; framework wraps the response in the wire envelope. Returning an empty
rightsarray is valid (= "no rights available for the requested terms"); throwAdcpErroronly for buyer-fixable rejection (e.g., unsupported jurisdiction).Note: the wire field is
rights, NOTofferings. Adopters who named their internal modelofferingstranslate at this seam. def update_rights(self, req: UpdateRightsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[UpdateRightsResponse]-
Expand source code
def update_rights( self, req: UpdateRightsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdateRightsResponse]: """Update an existing rights grant. Return the update result inline. The beta.5 response schema has no Submitted arm, so task/workflow handoffs are rejected before launch. A buyer-supplied push configuration is reserved for later rights notifications, not a pollable task lifecycle for this response. """ ...Update an existing rights grant.
Return the update result inline. The beta.5 response schema has no Submitted arm, so task/workflow handoffs are rejected before launch. A buyer-supplied push configuration is reserved for later rights notifications, not a pollable task lifecycle for this response.
def verify_brand_claim(self, req: VerifyBrandClaimRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[VerifyBrandClaimResponse]-
Expand source code
def verify_brand_claim( self, req: VerifyBrandClaimRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[VerifyBrandClaimResponse]: """Verify one brand claim. Optional beta 3 surface. Implement when the brand agent can authoritatively confirm or reject parent, subsidiary, property, or trademark claims. """ ...Verify one brand claim.
Optional beta 3 surface. Implement when the brand agent can authoritatively confirm or reject parent, subsidiary, property, or trademark claims.
def verify_brand_claims(self, req: VerifyBrandClaimsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[VerifyBrandClaimsResponseBulk]-
Expand source code
def verify_brand_claims( self, req: VerifyBrandClaimsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[VerifyBrandClaimsResponseBulk]: """Verify many brand claims in one call. Optional bulk companion to :meth:`verify_brand_claim`. """ ...Verify many brand claims in one call.
Optional bulk companion to :meth:
verify_brand_claim.
class CampaignGovernancePlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class CampaignGovernancePlatform(Protocol, Generic[TMeta]): """Runtime governance decisioning for advertiser campaigns. A decision API: the agent inspects a proposed action (or running delivery) and returns ``approved``, ``denied``, or ``conditions`` (approved-if). Status changes (plan moving from ``pending_approval`` → ``active`` → ``closed``) flow via ``ctx.publish_status_change(resource_type='plan', ...)``. 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 (``PLAN_NOT_FOUND``, ``INVALID_REQUEST``, etc.). Use the :meth:`check_governance` response ``status: 'denied'`` for governance decisions that ARE the answer (the plan exists and the agent is rejecting the action) — that's a legitimate business outcome, not an error. """ def check_governance( self, req: CheckGovernanceRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CheckGovernanceResponse]: """Runtime governance decision. A buyer sends an intent-shaped request (``target_agent`` plus the exact downstream ``tool`` and ``payload``); an executing service sends an execution-shaped request (opaque ``governance_context`` plus seller-authoritative ``planned_delivery``). Modern responses identify the shape with ``check_type``. Conditions are valid only for ``check_type='intent'``. Execution is a binary approved/denied boundary. Its ``phase`` is ``purchase``, ``modification``, or ``delivery`` and never exposes the buyer's plan or original payload to the service. :raises adcp.decisioning.AdcpError: for buyer-fixable rejection (``PLAN_NOT_FOUND``, ``INVALID_REQUEST``). ``status: 'denied'`` on the response is the governance-decision-as-answer path — not an error. """ ... def sync_plans( self, req: SyncPlansRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[SyncPlansResponse]: """Plan CRUD with delta upsert semantics. Buyers sync their campaign plans into the governance agent so the agent can maintain spend authority + delivery context. The agent tracks plan state across the campaign lifecycle (pending_approval → active → closed); transitions are emitted via ``ctx.publish_status_change(resource_type='plan', ...)``. """ ... def report_plan_outcome( self, req: ReportPlanOutcomeRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ReportPlanOutcomeResponse]: """Buyer/orchestrator outcome reporting after a seller response. Completed and failed reports settle the exact approved action; buyer-attributed delivery observations reconcile a prior seller delivery check. Sellers themselves report canonical delivery through ``check_governance(phase='delivery')``. """ ... def get_plan_audit_logs( self, req: GetPlanAuditLogsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetPlanAuditLogsResponse]: """Audit log read. Returns the chronological history of governance decisions + outcome reports for a plan. Buyers and operators use this to reconstruct who approved what + when, what conditions were attached, and what the seller reported. """ ...Runtime governance decisioning for advertiser campaigns.
A decision API: the agent inspects a proposed action (or running delivery) and returns
approved,denied, orconditions(approved-if). Status changes (plan moving frompending_approval→active→closed) flow viactx.publish_status_change(resource_type='plan', ...).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 (PLAN_NOT_FOUND,INVALID_REQUEST, etc.). Use the :meth:check_governanceresponsestatus: 'denied'for governance decisions that ARE the answer (the plan exists and the agent is rejecting the action) — that's a legitimate business outcome, not an error.Ancestors
- typing.Protocol
- typing.Generic
Methods
def check_governance(self, req: CheckGovernanceRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[CheckGovernanceResponse]-
Expand source code
def check_governance( self, req: CheckGovernanceRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CheckGovernanceResponse]: """Runtime governance decision. A buyer sends an intent-shaped request (``target_agent`` plus the exact downstream ``tool`` and ``payload``); an executing service sends an execution-shaped request (opaque ``governance_context`` plus seller-authoritative ``planned_delivery``). Modern responses identify the shape with ``check_type``. Conditions are valid only for ``check_type='intent'``. Execution is a binary approved/denied boundary. Its ``phase`` is ``purchase``, ``modification``, or ``delivery`` and never exposes the buyer's plan or original payload to the service. :raises adcp.decisioning.AdcpError: for buyer-fixable rejection (``PLAN_NOT_FOUND``, ``INVALID_REQUEST``). ``status: 'denied'`` on the response is the governance-decision-as-answer path — not an error. """ ...Runtime governance decision.
A buyer sends an intent-shaped request (
target_agentplus the exact downstreamtoolandpayload); an executing service sends an execution-shaped request (opaquegovernance_contextplus seller-authoritativeplanned_delivery). Modern responses identify the shape withcheck_type.Conditions are valid only for
check_type='intent'. Execution is a binary approved/denied boundary. Itsphaseispurchase,modification, ordeliveryand never exposes the buyer's plan or original payload to the service.:raises adcp.decisioning.AdcpError: for buyer-fixable rejection (
PLAN_NOT_FOUND,INVALID_REQUEST).status: 'denied'on the response is the governance-decision-as-answer path — not an error. def get_plan_audit_logs(self, req: GetPlanAuditLogsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetPlanAuditLogsResponse]-
Expand source code
def get_plan_audit_logs( self, req: GetPlanAuditLogsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetPlanAuditLogsResponse]: """Audit log read. Returns the chronological history of governance decisions + outcome reports for a plan. Buyers and operators use this to reconstruct who approved what + when, what conditions were attached, and what the seller reported. """ ...Audit log read.
Returns the chronological history of governance decisions + outcome reports for a plan. Buyers and operators use this to reconstruct who approved what + when, what conditions were attached, and what the seller reported.
def report_plan_outcome(self, req: ReportPlanOutcomeRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ReportPlanOutcomeResponse]-
Expand source code
def report_plan_outcome( self, req: ReportPlanOutcomeRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ReportPlanOutcomeResponse]: """Buyer/orchestrator outcome reporting after a seller response. Completed and failed reports settle the exact approved action; buyer-attributed delivery observations reconcile a prior seller delivery check. Sellers themselves report canonical delivery through ``check_governance(phase='delivery')``. """ ...Buyer/orchestrator outcome reporting after a seller response.
Completed and failed reports settle the exact approved action; buyer-attributed delivery observations reconcile a prior seller delivery check. Sellers themselves report canonical delivery through
check_governance(phase='delivery'). def sync_plans(self, req: SyncPlansRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[SyncPlansResponse]-
Expand source code
def sync_plans( self, req: SyncPlansRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[SyncPlansResponse]: """Plan CRUD with delta upsert semantics. Buyers sync their campaign plans into the governance agent so the agent can maintain spend authority + delivery context. The agent tracks plan state across the campaign lifecycle (pending_approval → active → closed); transitions are emitted via ``ctx.publish_status_change(resource_type='plan', ...)``. """ ...Plan CRUD with delta upsert semantics.
Buyers sync their campaign plans into the governance agent so the agent can maintain spend authority + delivery context. The agent tracks plan state across the campaign lifecycle (pending_approval → active → closed); transitions are emitted via
ctx.publish_status_change(resource_type='plan', ...).
class CollectionListsPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class CollectionListsPlatform(Protocol, Generic[TMeta]): """Collection-list CRUD with fetch-token issuance semantics. Parallel shape to :class:`PropertyListsPlatform`; covers program-level brand-safety lists (shows, series, podcasts) keyed by IMDb / Gracenote / EIDR ids. Same security model: ``create_*`` issues a per-seller ``fetch_token``, ``delete_*`` revokes it; compromise-driven revocation MUST trigger delete. """ def create_collection_list( self, req: CreateCollectionListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CreateCollectionListResponse]: """Create a collection list. Returns a per-seller-scoped ``fetch_token``.""" ... def update_collection_list( self, req: UpdateCollectionListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdateCollectionListResponse]: """Patch an existing collection list.""" ... def get_collection_list( self, req: GetCollectionListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetCollectionListResponse]: """Read a collection list by id.""" ... def list_collection_lists( self, req: ListCollectionListsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListCollectionListsResponse]: """Discover collection lists the caller is authorized to read.""" ... def delete_collection_list( self, req: DeleteCollectionListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[DeleteCollectionListResponse]: """Delete a collection list. Revokes the ``fetch_token`` and signals cache invalidation.""" ...Collection-list CRUD with fetch-token issuance semantics.
Parallel shape to :class:
PropertyListsPlatform; covers program-level brand-safety lists (shows, series, podcasts) keyed by IMDb / Gracenote / EIDR ids.Same security model:
create_*issues a per-sellerfetch_token,delete_*revokes it; compromise-driven revocation MUST trigger delete.Ancestors
- typing.Protocol
- typing.Generic
Methods
def create_collection_list(self, req: CreateCollectionListRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[CreateCollectionListResponse]-
Expand source code
def create_collection_list( self, req: CreateCollectionListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CreateCollectionListResponse]: """Create a collection list. Returns a per-seller-scoped ``fetch_token``.""" ...Create a collection list. Returns a per-seller-scoped
fetch_token. def delete_collection_list(self, req: DeleteCollectionListRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[DeleteCollectionListResponse]-
Expand source code
def delete_collection_list( self, req: DeleteCollectionListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[DeleteCollectionListResponse]: """Delete a collection list. Revokes the ``fetch_token`` and signals cache invalidation.""" ...Delete a collection list. Revokes the
fetch_tokenand signals cache invalidation. def get_collection_list(self, req: GetCollectionListRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetCollectionListResponse]-
Expand source code
def get_collection_list( self, req: GetCollectionListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetCollectionListResponse]: """Read a collection list by id.""" ...Read a collection list by id.
def list_collection_lists(self, req: ListCollectionListsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ListCollectionListsResponse]-
Expand source code
def list_collection_lists( self, req: ListCollectionListsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListCollectionListsResponse]: """Discover collection lists the caller is authorized to read.""" ...Discover collection lists the caller is authorized to read.
def update_collection_list(self, req: UpdateCollectionListRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[UpdateCollectionListResponse]-
Expand source code
def update_collection_list( self, req: UpdateCollectionListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdateCollectionListResponse]: """Patch an existing collection list.""" ...Patch an existing collection list.
class CompactMediaBuyPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class CompactMediaBuyPlatform(Protocol, Generic[TMeta]): """Optional AdCP 3.2 compact lifecycle surface. Kept separate from :class:`SalesPlatform` so adding the 3.2 methods does not make legacy implementations fail runtime structural checks. Sellers declare the implemented subset through ``media_buy.lifecycle_tools``; boot validation checks each claimed method independently. """ def list_products( self, req: ListProductsRequest, ctx: RequestContext[TMeta] ) -> MaybeAsync[ListProductsResponse]: ... def request_proposals( self, req: RequestProposalsRequest, ctx: RequestContext[TMeta] ) -> SalesResult[RequestProposalsResponse]: ... def refine_proposals( self, req: RefineProposalsRequest, ctx: RequestContext[TMeta] ) -> SalesResult[RefineProposalsResponse]: ... def decline_proposals( self, req: DeclineProposalsRequest, ctx: RequestContext[TMeta] ) -> SalesResult[DeclineProposalsResponse]: ... def buy_products( self, req: BuyProductsRequest, ctx: RequestContext[TMeta] ) -> SalesResult[BuyProductsResponse]: ... def accept_proposal( self, req: AcceptProposalRequest, ctx: RequestContext[TMeta] ) -> SalesResult[AcceptProposalResponse]: ... def control_media_buy( self, req: ControlMediaBuyRequest, ctx: RequestContext[TMeta] ) -> SalesResult[ControlMediaBuyResponse]: ...Optional AdCP 3.2 compact lifecycle surface.
Kept separate from :class:
SalesPlatformso adding the 3.2 methods does not make legacy implementations fail runtime structural checks. Sellers declare the implemented subset throughmedia_buy.lifecycle_tools; boot validation checks each claimed method independently.Ancestors
- typing.Protocol
- typing.Generic
Methods
def accept_proposal(self, req: AcceptProposalRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[AcceptProposalResponse]-
Expand source code
def accept_proposal( self, req: AcceptProposalRequest, ctx: RequestContext[TMeta] ) -> SalesResult[AcceptProposalResponse]: ... def buy_products(self, req: BuyProductsRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[BuyProductsResponse]-
Expand source code
def buy_products( self, req: BuyProductsRequest, ctx: RequestContext[TMeta] ) -> SalesResult[BuyProductsResponse]: ... def control_media_buy(self, req: ControlMediaBuyRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[ControlMediaBuyResponse]-
Expand source code
def control_media_buy( self, req: ControlMediaBuyRequest, ctx: RequestContext[TMeta] ) -> SalesResult[ControlMediaBuyResponse]: ... def decline_proposals(self, req: DeclineProposalsRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[DeclineProposalsResponse]-
Expand source code
def decline_proposals( self, req: DeclineProposalsRequest, ctx: RequestContext[TMeta] ) -> SalesResult[DeclineProposalsResponse]: ... def list_products(self, req: ListProductsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ListProductsResponse]-
Expand source code
def list_products( self, req: ListProductsRequest, ctx: RequestContext[TMeta] ) -> MaybeAsync[ListProductsResponse]: ... def refine_proposals(self, req: RefineProposalsRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[RefineProposalsResponse]-
Expand source code
def refine_proposals( self, req: RefineProposalsRequest, ctx: RequestContext[TMeta] ) -> SalesResult[RefineProposalsResponse]: ... def request_proposals(self, req: RequestProposalsRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[RequestProposalsResponse]-
Expand source code
def request_proposals( self, req: RequestProposalsRequest, ctx: RequestContext[TMeta] ) -> SalesResult[RequestProposalsResponse]: ...
class ContentStandardsPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class ContentStandardsPlatform(Protocol, Generic[TMeta]): """Content standards CRUD + calibration + delivery validation. 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 (``REFERENCE_NOT_FOUND``, ``INVALID_REQUEST``, ``POLICY_VIOLATION`` for buyer rights issues, etc.). """ def list_content_standards( self, req: ListContentStandardsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListContentStandardsResponse]: """Discover content standards published by this agent.""" ... def get_content_standards( self, req: GetContentStandardsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetContentStandardsResponse]: """Read a single content standard by id.""" ... def create_content_standards( self, req: CreateContentStandardsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CreateContentStandardsResponse]: """Create a new content standard. Adopter validates the policy schema and returns the persisted record. Idempotent on the buyer's ``idempotency_key``. """ ... def update_content_standards( self, req: UpdateContentStandardsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdateContentStandardsResponse]: """Update an existing content standard.""" ... def calibrate_content( self, req: CalibrateContentRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CalibrateContentResponse]: """Calibrate content against the published standards. Returns the standard's current calibration profile + any flags raised against the submitted content. """ ... def validate_content_delivery( self, req: ValidateContentDeliveryRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ValidateContentDeliveryResponse]: """Validate that a delivered media-buy / creative meets the buyer's declared content-standards. Sellers call this post-flight to confirm adjacency and policy conformance before issuing a ``validate_content_delivery_artifact`` to a governance agent. """ ... # ---- Optional analyzer reads ---- def get_media_buy_artifacts( self, req: GetMediaBuyArtifactsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetMediaBuyArtifactsResponse]: """Read content artifacts produced during a media buy's flight. Optional — adopters who don't expose artifact archival omit. Required by governance receivers running adjacency validation. """ ... def get_creative_features( self, req: GetCreativeFeaturesRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetCreativeFeaturesResponse]: """Read per-creative analyzed features (object detection, scene classification, transcript) extracted during calibration. Optional — adopters without analyzer pipelines omit. """ ...Content standards CRUD + calibration + delivery validation.
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 (REFERENCE_NOT_FOUND,INVALID_REQUEST,POLICY_VIOLATIONfor buyer rights issues, etc.).Ancestors
- typing.Protocol
- typing.Generic
Methods
def calibrate_content(self, req: CalibrateContentRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[CalibrateContentResponse]-
Expand source code
def calibrate_content( self, req: CalibrateContentRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CalibrateContentResponse]: """Calibrate content against the published standards. Returns the standard's current calibration profile + any flags raised against the submitted content. """ ...Calibrate content against the published standards.
Returns the standard's current calibration profile + any flags raised against the submitted content.
def create_content_standards(self, req: CreateContentStandardsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[CreateContentStandardsResponse]-
Expand source code
def create_content_standards( self, req: CreateContentStandardsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CreateContentStandardsResponse]: """Create a new content standard. Adopter validates the policy schema and returns the persisted record. Idempotent on the buyer's ``idempotency_key``. """ ...Create a new content standard.
Adopter validates the policy schema and returns the persisted record. Idempotent on the buyer's
idempotency_key. def get_content_standards(self, req: GetContentStandardsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetContentStandardsResponse]-
Expand source code
def get_content_standards( self, req: GetContentStandardsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetContentStandardsResponse]: """Read a single content standard by id.""" ...Read a single content standard by id.
def get_creative_features(self, req: GetCreativeFeaturesRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetCreativeFeaturesResponse]-
Expand source code
def get_creative_features( self, req: GetCreativeFeaturesRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetCreativeFeaturesResponse]: """Read per-creative analyzed features (object detection, scene classification, transcript) extracted during calibration. Optional — adopters without analyzer pipelines omit. """ ...Read per-creative analyzed features (object detection, scene classification, transcript) extracted during calibration.
Optional — adopters without analyzer pipelines omit.
def get_media_buy_artifacts(self, req: GetMediaBuyArtifactsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetMediaBuyArtifactsResponse]-
Expand source code
def get_media_buy_artifacts( self, req: GetMediaBuyArtifactsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetMediaBuyArtifactsResponse]: """Read content artifacts produced during a media buy's flight. Optional — adopters who don't expose artifact archival omit. Required by governance receivers running adjacency validation. """ ...Read content artifacts produced during a media buy's flight.
Optional — adopters who don't expose artifact archival omit. Required by governance receivers running adjacency validation.
def list_content_standards(self, req: ListContentStandardsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ListContentStandardsResponse]-
Expand source code
def list_content_standards( self, req: ListContentStandardsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListContentStandardsResponse]: """Discover content standards published by this agent.""" ...Discover content standards published by this agent.
def update_content_standards(self, req: UpdateContentStandardsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[UpdateContentStandardsResponse]-
Expand source code
def update_content_standards( self, req: UpdateContentStandardsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdateContentStandardsResponse]: """Update an existing content standard.""" ...Update an existing content standard.
def validate_content_delivery(self, req: ValidateContentDeliveryRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ValidateContentDeliveryResponse]-
Expand source code
def validate_content_delivery( self, req: ValidateContentDeliveryRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ValidateContentDeliveryResponse]: """Validate that a delivered media-buy / creative meets the buyer's declared content-standards. Sellers call this post-flight to confirm adjacency and policy conformance before issuing a ``validate_content_delivery_artifact`` to a governance agent. """ ...Validate that a delivered media-buy / creative meets the buyer's declared content-standards.
Sellers call this post-flight to confirm adjacency and policy conformance before issuing a
validate_content_delivery_artifactto a governance agent.
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_legacy( self, req: LegacyBuildCreativeRequest, ctx: RequestContext[TMeta], ) -> SalesResult[ LegacyBuildCreativeResponse1 | LegacyBuildCreativeResponse2 | LegacyBuildCreativeResponse3 | LegacyBuildCreativeResponse4 | LegacyBuildCreativeResponse5 | 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.). Beta.5 permits a submitted task arm for slow tag-generation pipelines; return either handoff marker when work cannot finish inline. 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_legacy( self, req: LegacyPreviewCreativeRequest, ctx: RequestContext[TMeta], ) -> SalesResult[ LegacyPreviewCreativeResponse1 | LegacyPreviewCreativeResponse2 | LegacyPreviewCreativeResponse3 ]: """Preview-only variant — sandbox URL or inline HTML, expires. NOT optional for ad-server adopters (distinct from :class:`CreativeBuilderPlatform.preview_creative`, which is optional). A handoff is valid only when ``req.allow_async`` is true. """ ... 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_legacy(self, req: LegacyBuildCreativeRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[LegacyBuildCreativeResponse1 | LegacyBuildCreativeResponse2 | LegacyBuildCreativeResponse3 | LegacyBuildCreativeResponse4 | LegacyBuildCreativeResponse5 | Sequence[CreativeManifest] | CreativeManifest]-
Expand source code
def build_creative_legacy( self, req: LegacyBuildCreativeRequest, ctx: RequestContext[TMeta], ) -> SalesResult[ LegacyBuildCreativeResponse1 | LegacyBuildCreativeResponse2 | LegacyBuildCreativeResponse3 | LegacyBuildCreativeResponse4 | LegacyBuildCreativeResponse5 | 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.). Beta.5 permits a submitted task arm for slow tag-generation pipelines; return either handoff marker when work cannot finish inline. 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.).
Beta.5 permits a submitted task arm for slow tag-generation pipelines; return either handoff marker when work cannot finish inline.
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_legacy(self, req: LegacyPreviewCreativeRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[LegacyPreviewCreativeResponse1 | LegacyPreviewCreativeResponse2 | LegacyPreviewCreativeResponse3]-
Expand source code
def preview_creative_legacy( self, req: LegacyPreviewCreativeRequest, ctx: RequestContext[TMeta], ) -> SalesResult[ LegacyPreviewCreativeResponse1 | LegacyPreviewCreativeResponse2 | LegacyPreviewCreativeResponse3 ]: """Preview-only variant — sandbox URL or inline HTML, expires. NOT optional for ad-server adopters (distinct from :class:`CreativeBuilderPlatform.preview_creative`, which is optional). A handoff is valid only when ``req.allow_async`` is true. """ ...Preview-only variant — sandbox URL or inline HTML, expires.
NOT optional for ad-server adopters (distinct from :class:
CreativeBuilderPlatform.preview_creative, which is optional). A handoff is valid only whenreq.allow_asyncis true. 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).
class CreativeBuilderPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class CreativeBuilderPlatform(Protocol, Generic[TMeta]): """Produces creatives — template-driven or brief-driven (generative). 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 (``UNSUPPORTED_FEATURE`` for missing optionals, ``POLICY_VIOLATION`` for buyer rights issues, etc.); the framework projects to the wire structured-error envelope. """ def build_creative_legacy( self, req: LegacyBuildCreativeRequest, ctx: RequestContext[TMeta], ) -> SalesResult[ LegacyBuildCreativeResponse1 | LegacyBuildCreativeResponse2 | LegacyBuildCreativeResponse3 | LegacyBuildCreativeResponse4 | LegacyBuildCreativeResponse5 | Sequence[CreativeManifest] | CreativeManifest ]: """Build the creative. Single method covers template-driven transform (``req.template_id`` + asset slots), brief-to-creative generation (``req.brief``), and any hybrid the platform supports — adopters route internally on ``req`` shape. Return shape is discriminated by the wire spec's Single vs Multi response arms: * **Single manifest, no metadata**: return a :class:`CreativeManifest` directly. Framework wraps as ``{creative_manifest: <manifest>}``. Use this for single-format requests (``target_format_id``) when you don't need to set ``sandbox`` / ``expires_at`` / ``preview``. * **Multi-format manifests, no metadata**: return a ``Sequence[CreativeManifest]``. Framework wraps as ``{creative_manifests: [...]}``. Use for multi-format requests (``target_format_ids``) when you don't need rich metadata. * **Fully-shaped envelope**: return any non-Submitted beta.5 build response arm, including error, multiplicity, or estimate responses. Framework passes the envelope through unchanged. Do not construct a Submitted response directly; return a handoff marker so the framework issues and persists the task identity. Adopters route on ``req.target_format_ids`` (multi) vs ``req.target_format_id`` (single) and return the matching arm. Returning the wrong arm shape is an adopter contract violation that surfaces as schema-validation failure on the wire response. :raises adcp.decisioning.AdcpError: ``code='POLICY_VIOLATION'`` (buyer lacks rights to the requested template / brand inputs), ``code='INVALID_REQUEST'`` (missing or unrecognized template_id). """ ... def preview_creative_legacy( self, req: LegacyPreviewCreativeRequest, ctx: RequestContext[TMeta], ) -> SalesResult[ LegacyPreviewCreativeResponse1 | LegacyPreviewCreativeResponse2 | LegacyPreviewCreativeResponse3 ]: """Preview-only variant — sandbox URL or inline HTML, expires. Optional — generative-only adopters that don't render preview ahead of generation can omit it. Return a handoff only when ``req.allow_async`` is true; otherwise return inline or reject. """ ... def sync_creatives( self, req: SyncCreativesRequest, ctx: RequestContext[TMeta], ) -> SalesResult[SyncCreativesSuccessResponse]: """Sync review surface — present-or-absent. Stateless platforms typically auto-approve; adopters needing mandatory pre-persist review return ``ctx.handoff_to_task(fn)`` to defer to a background task. Unified hybrid shape — return the typed :class:`SyncCreativesSuccessResponse` for the sync fast path OR ``ctx.handoff_to_task(fn)`` for HITL. Same wire request type as the sales-* archetypes use (``SyncCreativesRequest`` — shared spec shape); the per-archetype handler shim narrows the discriminated payload when adopters care about archetype-specific fields. """ ... def validate_input( self, req: ValidateInputRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ValidateInputResponse]: """Validate buyer-provided creative inputs before build. Optional beta 3 preflight surface. Platforms that can predict format compatibility without rendering should implement it; otherwise the framework returns ``UNSUPPORTED_FEATURE`` when called. """ ...Produces creatives — template-driven or brief-driven (generative).
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 (UNSUPPORTED_FEATUREfor missing optionals,POLICY_VIOLATIONfor buyer rights issues, etc.); the framework projects to the wire structured-error envelope.Ancestors
- typing.Protocol
- typing.Generic
Methods
def build_creative_legacy(self, req: LegacyBuildCreativeRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[LegacyBuildCreativeResponse1 | LegacyBuildCreativeResponse2 | LegacyBuildCreativeResponse3 | LegacyBuildCreativeResponse4 | LegacyBuildCreativeResponse5 | Sequence[CreativeManifest] | CreativeManifest]-
Expand source code
def build_creative_legacy( self, req: LegacyBuildCreativeRequest, ctx: RequestContext[TMeta], ) -> SalesResult[ LegacyBuildCreativeResponse1 | LegacyBuildCreativeResponse2 | LegacyBuildCreativeResponse3 | LegacyBuildCreativeResponse4 | LegacyBuildCreativeResponse5 | Sequence[CreativeManifest] | CreativeManifest ]: """Build the creative. Single method covers template-driven transform (``req.template_id`` + asset slots), brief-to-creative generation (``req.brief``), and any hybrid the platform supports — adopters route internally on ``req`` shape. Return shape is discriminated by the wire spec's Single vs Multi response arms: * **Single manifest, no metadata**: return a :class:`CreativeManifest` directly. Framework wraps as ``{creative_manifest: <manifest>}``. Use this for single-format requests (``target_format_id``) when you don't need to set ``sandbox`` / ``expires_at`` / ``preview``. * **Multi-format manifests, no metadata**: return a ``Sequence[CreativeManifest]``. Framework wraps as ``{creative_manifests: [...]}``. Use for multi-format requests (``target_format_ids``) when you don't need rich metadata. * **Fully-shaped envelope**: return any non-Submitted beta.5 build response arm, including error, multiplicity, or estimate responses. Framework passes the envelope through unchanged. Do not construct a Submitted response directly; return a handoff marker so the framework issues and persists the task identity. Adopters route on ``req.target_format_ids`` (multi) vs ``req.target_format_id`` (single) and return the matching arm. Returning the wrong arm shape is an adopter contract violation that surfaces as schema-validation failure on the wire response. :raises adcp.decisioning.AdcpError: ``code='POLICY_VIOLATION'`` (buyer lacks rights to the requested template / brand inputs), ``code='INVALID_REQUEST'`` (missing or unrecognized template_id). """ ...Build the creative.
Single method covers template-driven transform (
req.template_id+ asset slots), brief-to-creative generation (req.brief), and any hybrid the platform supports — adopters route internally onreqshape.Return shape is discriminated by the wire spec's Single vs Multi response arms:
- Single manifest, no metadata: return a :class:
CreativeManifestdirectly. Framework wraps as{creative_manifest: <manifest>}. Use this for single-format requests (target_format_id) when you don't need to setsandbox/expires_at/preview. - Multi-format manifests, no metadata: return a
Sequence[CreativeManifest]. Framework wraps as{creative_manifests: [...]}. Use for multi-format requests (target_format_ids) when you don't need rich metadata. - Fully-shaped envelope: return any non-Submitted beta.5 build response arm, including error, multiplicity, or estimate responses. Framework passes the envelope through unchanged. Do not construct a Submitted response directly; return a handoff marker so the framework issues and persists the task identity.
Adopters route on
req.target_format_ids(multi) vsreq.target_format_id(single) and return the matching arm. Returning the wrong arm shape is an adopter contract violation that surfaces as schema-validation failure on the wire response.:raises adcp.decisioning.AdcpError:
code='POLICY_VIOLATION'(buyer lacks rights to the requested template / brand inputs),code='INVALID_REQUEST'(missing or unrecognized template_id). - Single manifest, no metadata: return a :class:
def preview_creative_legacy(self, req: LegacyPreviewCreativeRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[LegacyPreviewCreativeResponse1 | LegacyPreviewCreativeResponse2 | LegacyPreviewCreativeResponse3]-
Expand source code
def preview_creative_legacy( self, req: LegacyPreviewCreativeRequest, ctx: RequestContext[TMeta], ) -> SalesResult[ LegacyPreviewCreativeResponse1 | LegacyPreviewCreativeResponse2 | LegacyPreviewCreativeResponse3 ]: """Preview-only variant — sandbox URL or inline HTML, expires. Optional — generative-only adopters that don't render preview ahead of generation can omit it. Return a handoff only when ``req.allow_async`` is true; otherwise return inline or reject. """ ...Preview-only variant — sandbox URL or inline HTML, expires.
Optional — generative-only adopters that don't render preview ahead of generation can omit it. Return a handoff only when
req.allow_asyncis true; otherwise return inline or reject. 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]: """Sync review surface — present-or-absent. Stateless platforms typically auto-approve; adopters needing mandatory pre-persist review return ``ctx.handoff_to_task(fn)`` to defer to a background task. Unified hybrid shape — return the typed :class:`SyncCreativesSuccessResponse` for the sync fast path OR ``ctx.handoff_to_task(fn)`` for HITL. Same wire request type as the sales-* archetypes use (``SyncCreativesRequest`` — shared spec shape); the per-archetype handler shim narrows the discriminated payload when adopters care about archetype-specific fields. """ ...Sync review surface — present-or-absent.
Stateless platforms typically auto-approve; adopters needing mandatory pre-persist review return
ctx.handoff_to_task(fn)to defer to a background task. Unified hybrid shape — return the typed :class:SyncCreativesSuccessResponsefor the sync fast path ORctx.handoff_to_task(fn)for HITL.Same wire request type as the sales-* archetypes use (
SyncCreativesRequest— shared spec shape); the per-archetype handler shim narrows the discriminated payload when adopters care about archetype-specific fields. def validate_input(self, req: ValidateInputRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ValidateInputResponse]-
Expand source code
def validate_input( self, req: ValidateInputRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ValidateInputResponse]: """Validate buyer-provided creative inputs before build. Optional beta 3 preflight surface. Platforms that can predict format compatibility without rendering should implement it; otherwise the framework returns ``UNSUPPORTED_FEATURE`` when called. """ ...Validate buyer-provided creative inputs before build.
Optional beta 3 preflight surface. Platforms that can predict format compatibility without rendering should implement it; otherwise the framework returns
UNSUPPORTED_FEATUREwhen called.
class OwnedSignalsPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class OwnedSignalsPlatform(Protocol, Generic[TMeta]): """Catalog discovery for seller-owned first-party signals. 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 so a blocking sync handler doesn't serialize the event loop. Throw :class:`adcp.decisioning.AdcpError` for buyer-fixable rejection (``SIGNAL_NOT_FOUND``, ``POLICY_VIOLATION``, ``INVALID_REQUEST``, etc.); the framework projects to the wire structured-error envelope. """ def get_signals( self, req: GetSignalsRequest, ctx: RequestContext[TMeta], ) -> DiscoveryResult[GetSignalsResponse]: """Catalog discovery — query your signal index, return signals matching the buyer's filters (industry, intent type, audience size, etc.). Return :class:`GetSignalsResponse` directly for the sync fast path. Brief-driven discovery MAY hand off via ``ctx.handoff_to_task(fn)`` when provider discovery needs background work (cross-provider fan-out, identity-graph lookups); the framework projects the handoff to the wire ``submitted`` envelope. The buyer reaches the terminal :class:`GetSignalsResponse` via ``tasks/get`` polling, and — when the request carried ``push_notification_config`` — the framework also delivers the terminal completion / failure webhook to the buyer's URL from the background completion path (exactly once). Wholesale (``discovery_mode='wholesale'``) MUST return synchronously — a raw catalog read with no seller-side composition to background. Returning a handoff from a wholesale call is rejected at the framework layer with ``AdcpError(INVALID_REQUEST, field='discovery_mode')``. .. note:: ``get_signals`` ships ONLY the ``submitted`` and ``working`` async arms — it has **no** ``input_required`` arm (unlike ``get_products`` and ``create_media_buy``). Signal discovery cannot pause mid-task to solicit buyer clarification; the seller either completes the discovery or fails the task. :raises adcp.decisioning.AdcpError: ``code='POLICY_VIOLATION'`` when the buyer doesn't have rights to the requested data category. """ ...Catalog discovery for seller-owned first-party signals.
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 so a blocking sync handler doesn't serialize the event loop.Throw :class:
AdcpErrorfor buyer-fixable rejection (SIGNAL_NOT_FOUND,POLICY_VIOLATION,INVALID_REQUEST, etc.); the framework projects to the wire structured-error envelope.Ancestors
- typing.Protocol
- typing.Generic
Subclasses
Methods
def get_signals(self, req: GetSignalsRequest, ctx: RequestContext[TMeta]) ‑> DiscoveryResult[GetSignalsResponse]-
Expand source code
def get_signals( self, req: GetSignalsRequest, ctx: RequestContext[TMeta], ) -> DiscoveryResult[GetSignalsResponse]: """Catalog discovery — query your signal index, return signals matching the buyer's filters (industry, intent type, audience size, etc.). Return :class:`GetSignalsResponse` directly for the sync fast path. Brief-driven discovery MAY hand off via ``ctx.handoff_to_task(fn)`` when provider discovery needs background work (cross-provider fan-out, identity-graph lookups); the framework projects the handoff to the wire ``submitted`` envelope. The buyer reaches the terminal :class:`GetSignalsResponse` via ``tasks/get`` polling, and — when the request carried ``push_notification_config`` — the framework also delivers the terminal completion / failure webhook to the buyer's URL from the background completion path (exactly once). Wholesale (``discovery_mode='wholesale'``) MUST return synchronously — a raw catalog read with no seller-side composition to background. Returning a handoff from a wholesale call is rejected at the framework layer with ``AdcpError(INVALID_REQUEST, field='discovery_mode')``. .. note:: ``get_signals`` ships ONLY the ``submitted`` and ``working`` async arms — it has **no** ``input_required`` arm (unlike ``get_products`` and ``create_media_buy``). Signal discovery cannot pause mid-task to solicit buyer clarification; the seller either completes the discovery or fails the task. :raises adcp.decisioning.AdcpError: ``code='POLICY_VIOLATION'`` when the buyer doesn't have rights to the requested data category. """ ...Catalog discovery — query your signal index, return signals matching the buyer's filters (industry, intent type, audience size, etc.).
Return :class:
GetSignalsResponsedirectly for the sync fast path. Brief-driven discovery MAY hand off viactx.handoff_to_task(fn)when provider discovery needs background work (cross-provider fan-out, identity-graph lookups); the framework projects the handoff to the wiresubmittedenvelope. The buyer reaches the terminal :class:GetSignalsResponseviatasks/getpolling, and — when the request carriedpush_notification_config— the framework also delivers the terminal completion / failure webhook to the buyer's URL from the background completion path (exactly once).Wholesale (
discovery_mode='wholesale') MUST return synchronously — a raw catalog read with no seller-side composition to background. Returning a handoff from a wholesale call is rejected at the framework layer withAdcpError(INVALID_REQUEST, field='discovery_mode').Note
get_signalsships ONLY thesubmittedandworkingasync arms — it has noinput_requiredarm (unlikeget_productsandcreate_media_buy). Signal discovery cannot pause mid-task to solicit buyer clarification; the seller either completes the discovery or fails the task.:raises adcp.decisioning.AdcpError:
code='POLICY_VIOLATION'when the buyer doesn't have rights to the requested data category.
class PropertyListsPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class PropertyListsPlatform(Protocol, Generic[TMeta]): """Property-list CRUD with fetch-token issuance semantics. 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 (``REFERENCE_NOT_FOUND``, ``POLICY_VIOLATION``). """ def create_property_list( self, req: CreatePropertyListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CreatePropertyListResponse]: """Create a property list. Returns a ``fetch_token`` the buyer stores in their secret manager. Token is scoped to this ``list_id``; MUST NOT be reused across lists. """ ... def update_property_list( self, req: UpdatePropertyListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdatePropertyListResponse]: """Patch an existing property list.""" ... def get_property_list( self, req: GetPropertyListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetPropertyListResponse]: """Read a property list by id. Sellers call this with the ``fetch_token`` from :meth:`create_property_list`. """ ... def list_property_lists( self, req: ListPropertyListsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListPropertyListsResponse]: """Discover property lists the caller is authorized to read.""" ... def delete_property_list( self, req: DeletePropertyListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[DeletePropertyListResponse]: """Delete a property list. MUST revoke the ``fetch_token`` immediately and signal cache invalidation to sellers (reduced ``cache_valid_until`` or a list-changed webhook). Compromise-driven revocation MUST also trigger this path. """ ...Property-list CRUD with fetch-token issuance semantics.
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 (REFERENCE_NOT_FOUND,POLICY_VIOLATION).Ancestors
- typing.Protocol
- typing.Generic
Methods
def create_property_list(self, req: CreatePropertyListRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[CreatePropertyListResponse]-
Expand source code
def create_property_list( self, req: CreatePropertyListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[CreatePropertyListResponse]: """Create a property list. Returns a ``fetch_token`` the buyer stores in their secret manager. Token is scoped to this ``list_id``; MUST NOT be reused across lists. """ ...Create a property list.
Returns a
fetch_tokenthe buyer stores in their secret manager. Token is scoped to thislist_id; MUST NOT be reused across lists. def delete_property_list(self, req: DeletePropertyListRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[DeletePropertyListResponse]-
Expand source code
def delete_property_list( self, req: DeletePropertyListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[DeletePropertyListResponse]: """Delete a property list. MUST revoke the ``fetch_token`` immediately and signal cache invalidation to sellers (reduced ``cache_valid_until`` or a list-changed webhook). Compromise-driven revocation MUST also trigger this path. """ ...Delete a property list.
MUST revoke the
fetch_tokenimmediately and signal cache invalidation to sellers (reducedcache_valid_untilor a list-changed webhook). Compromise-driven revocation MUST also trigger this path. def get_property_list(self, req: GetPropertyListRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetPropertyListResponse]-
Expand source code
def get_property_list( self, req: GetPropertyListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetPropertyListResponse]: """Read a property list by id. Sellers call this with the ``fetch_token`` from :meth:`create_property_list`. """ ...Read a property list by id.
Sellers call this with the
fetch_tokenfrom :meth:create_property_list. def list_property_lists(self, req: ListPropertyListsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ListPropertyListsResponse]-
Expand source code
def list_property_lists( self, req: ListPropertyListsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListPropertyListsResponse]: """Discover property lists the caller is authorized to read.""" ...Discover property lists the caller is authorized to read.
def update_property_list(self, req: UpdatePropertyListRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[UpdatePropertyListResponse]-
Expand source code
def update_property_list( self, req: UpdatePropertyListRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdatePropertyListResponse]: """Patch an existing property list.""" ...Patch an existing property list.
class SalesPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class SalesPlatform(Protocol, Generic[TMeta]): """Unified hybrid interface for every ``sales-*`` specialism. Methods may be sync (return ``T`` directly) or async (return ``Awaitable[T]``); the dispatch adapter detects via :func:`inspect.iscoroutinefunction` and runs sync methods on a thread pool via :func:`asyncio.to_thread` so a blocking sync handler doesn't serialize the event loop. Hybrid sellers (programmatic remnant + guaranteed inventory in one tenant) branch per call: return the Success directly for the sync fast path, return ``ctx.handoff_to_task(fn)`` for the HITL slow path. The framework dispatcher detects the :class:`TaskHandoff` via type-identity and projects to the wire ``Submitted`` envelope. Throw :class:`adcp.decisioning.AdcpError` for buyer-fixable rejection (``BUDGET_TOO_LOW``, ``POLICY_VIOLATION``, etc.); the framework projects to the wire structured-error envelope with code, recovery, field, suggestion, retry_after, details. """ # ---- Required for every sales-* specialism ---- def get_products( self, req: GetProductsRequest, ctx: RequestContext[TMeta], ) -> DiscoveryResult[GetProductsResponse]: """Catalog discovery — synchronous by default, MAY hand off. Return :class:`GetProductsResponse` directly for the sync fast path. Brief / refine discovery MAY hand off via ``ctx.handoff_to_task(fn)`` when composing the catalog needs background work (custom curation queue, slow proposal generation); the framework projects the handoff to the wire ``submitted`` envelope. The buyer reaches the terminal :class:`GetProductsResponse` via ``tasks/get`` polling, and — when the request carried ``push_notification_config`` — the framework also delivers the terminal completion / failure webhook to the buyer's URL from the background completion path (exactly once). ``get_products`` exposes the ``submitted`` / ``working`` / ``input_required`` async arms. Wholesale (``buying_mode='wholesale'``) MUST return synchronously — it is a raw rate-card read with no seller-side composition to background. A wholesale call that cannot finish within the buyer's ``time_budget`` declares the gap via ``incomplete[]`` on a sync response rather than handing off; returning a handoff from a wholesale call is rejected at the framework layer with ``AdcpError(INVALID_REQUEST, field='buying_mode')``. Brief-based proposal generation rides on a separate verb (``request_proposal``, adcp#3407); proposal-mode adopters surface the eventual products via ``ctx.publish_status_change(resource_type='proposal', ...)`` rather than blocking ``get_products`` waiting for trafficker approval. **Buying mode dispatch:** when ``req.buying_mode == 'refine'`` and the platform implements :meth:`refine_get_products`, the framework dispatches there instead of this method. Platforms that do not implement ``refine_get_products`` reject ``buying_mode='refine'`` with ``AdcpError(INVALID_REQUEST, field='buying_mode')`` at the framework layer — the platform method is not called. """ ... # Truly optional — adopters who don't implement refine_get_products # remain structurally conformant. The framework uses # :func:`adcp.decisioning.has_refine_support` (a ``hasattr`` check) at # dispatch time and rejects ``buying_mode='refine'`` with # ``AdcpError(INVALID_REQUEST, field='buying_mode')`` when absent. # # Implementations match this signature:: # # def refine_get_products( # self, # req: GetProductsRequest, # ctx: RequestContext[TMeta], # ) -> MaybeAsync[RefineResult]: ... # # Return a :class:`adcp.decisioning.RefineResult` with ``products``, # ``proposals``, and exactly ``len(req.refine)`` outcomes in # ``per_refine_outcome``. The framework constructs the wire # ``refinement_applied[]`` by zipping outcomes with ``req.refine`` — # adopters do NOT echo ``scope`` / ``product_id`` / ``proposal_id`` # manually. def create_media_buy( self, req: CreateMediaBuyRequest, ctx: RequestContext[TMeta], ) -> SalesResult[CreateMediaBuySuccessResponse]: """Unified hybrid. Return :class:`CreateMediaBuySuccessResponse` directly for sync fast path; return :meth:`RequestContext.handoff_to_task` for HITL slow path. Pre-flight runs sync regardless of path so bad budgets reject before allocating a task id — call ``preflight()`` at the top, ``raise AdcpError(...)`` on rejection. Buyer pattern-matches on the response shape: * ``media_buy_id`` field present → sync success * ``task_id`` + ``status='submitted'`` → poll ``tasks_get`` or receive webhook **Framework injection:** when a :class:`~adcp.decisioning.ProductConfigStore` is wired via ``config_store=`` in :func:`adcp.decisioning.serve.create_adcp_server_from_platform`, the framework calls the store before invoking this method and injects the result as ``configs: dict[str, dict[str, Any]]``. Declare ``configs`` in your method signature to receive it:: def create_media_buy(self, req, ctx, configs=None): configs = configs or {} line_item_id = configs.get(pkg.product_id, {}).get("line_item_id") When the store is not wired, or when ``req.packages`` is ``None`` (proposal-id flow), ``configs`` arrives as an empty dict ``{}``. """ ... def update_media_buy( self, media_buy_id: str, patch: UpdateMediaBuyRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdateMediaBuySuccessResponse]: """Mutate an in-flight media buy. v6.0 returns sync only — the per-tool response schema doesn't carry the ``Submitted`` arm yet (adcp#3392). Re-approval flows return the success with the ``status`` field omitted (in-spec per the schema description) and drive lifecycle via ``ctx.publish_status_change``. v6.1 + adcp#3392 expand this signature to :data:`SalesResult` so re-approval flows can hand off cleanly. """ ... def sync_creatives( self, req: SyncCreativesRequest, ctx: RequestContext[TMeta], ) -> SalesResult[SyncCreativesSuccessResponse]: """Unified hybrid for creative review. Mixed approved/pending rows in a single sync response, OR hand off the whole batch to background standards-and-practices review. Adopters with pre-approved buyer pools fast-path; new buyers' creatives go to review. """ ... def get_media_buy_delivery( self, req: GetMediaBuyDeliveryRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetMediaBuyDeliveryResponse]: """Sync delivery read — pacing, spend, impressions per package.""" ... # ---- Optional (gated by specialism — present-or-absent) ---- def get_media_buys( self, req: GetMediaBuysRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetMediaBuysResponse]: """List media buys for the resolved account. Required when claiming any ``sales-*`` specialism in v6.0 rc.1+. ``validate_platform`` fails server boot if a sales-claiming platform doesn't implement this. """ ... def provide_performance_feedback( self, req: ProvidePerformanceFeedbackRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ProvidePerformanceFeedbackResponse]: """Buyer-supplied performance signal back to the seller. Required when claiming any ``sales-*`` specialism in v6.0 rc.1+. """ ... def list_creative_formats_legacy( self, req: ListCreativeFormatsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListCreativeFormatsResponse]: """Catalog of accepted creative formats. Recommended for ``sales-*`` sellers that still serve pre-3.2 versions. Deprecated in AdCP 3.2: publish purchasable canonical declarations through ``get_products`` / ``Product.format_options[]``. Sellers advertising only 3.2 or later can omit this method even with ``ADCP_DECISIONING_STRICT_VALIDATE_PLATFORM=1``. """ ... def list_creatives( self, req: ListCreativesRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListCreativesResponse]: """List the seller's view of buyer-uploaded creatives. Required when claiming any ``sales-*`` specialism in v6.0 rc.1+. """ ... # ---- Required when claiming ``sales-catalog-driven`` ---- def sync_catalogs( self, req: SyncCatalogsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[SyncCatalogsSuccessResponse]: """Sync product catalogs with the platform. **Required** when claiming ``sales-catalog-driven``. ``validate_platform`` hard-fails at server boot when this method is absent on a ``sales-catalog-driven`` platform. Discovery mode: when ``req.catalogs is None``, return the account's existing synced catalogs without modification (read-only path per AdCP spec). Check ``req.catalogs`` before applying any mutations:: def sync_catalogs(self, req, ctx): if req.catalogs is None: return self._get_existing_catalogs(ctx.account_id) return self._upsert_catalogs(req.catalogs, ctx) **Important:** ``req.delete_missing=True`` with ``req.catalogs=None`` is spec-undefined — reject it with ``AdcpError("INVALID_REQUEST", field="catalogs")`` rather than silently deleting buyer-managed catalogs. Return a list of :class:`~adcp.types.SyncCatalogResult` rows (ergonomic form) or a fully-shaped :class:`~adcp.types.SyncCatalogsSuccessResponse`. """ raise NotImplementedErrorUnified hybrid interface for every
sales-*specialism.Methods may be sync (return
Tdirectly) or async (returnAwaitable[T]); the dispatch adapter detects via :func:inspect.iscoroutinefunctionand runs sync methods on a thread pool via :func:asyncio.to_threadso a blocking sync handler doesn't serialize the event loop.Hybrid sellers (programmatic remnant + guaranteed inventory in one tenant) branch per call: return the Success directly for the sync fast path, return
ctx.handoff_to_task(fn)for the HITL slow path. The framework dispatcher detects the :class:TaskHandoffvia type-identity and projects to the wireSubmittedenvelope.Throw :class:
AdcpErrorfor buyer-fixable rejection (BUDGET_TOO_LOW,POLICY_VIOLATION, etc.); the framework projects to the wire structured-error envelope with code, recovery, field, suggestion, retry_after, details.Ancestors
- typing.Protocol
- typing.Generic
Methods
def create_media_buy(self, req: CreateMediaBuyRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[CreateMediaBuySuccessResponse]-
Expand source code
def create_media_buy( self, req: CreateMediaBuyRequest, ctx: RequestContext[TMeta], ) -> SalesResult[CreateMediaBuySuccessResponse]: """Unified hybrid. Return :class:`CreateMediaBuySuccessResponse` directly for sync fast path; return :meth:`RequestContext.handoff_to_task` for HITL slow path. Pre-flight runs sync regardless of path so bad budgets reject before allocating a task id — call ``preflight()`` at the top, ``raise AdcpError(...)`` on rejection. Buyer pattern-matches on the response shape: * ``media_buy_id`` field present → sync success * ``task_id`` + ``status='submitted'`` → poll ``tasks_get`` or receive webhook **Framework injection:** when a :class:`~adcp.decisioning.ProductConfigStore` is wired via ``config_store=`` in :func:`adcp.decisioning.serve.create_adcp_server_from_platform`, the framework calls the store before invoking this method and injects the result as ``configs: dict[str, dict[str, Any]]``. Declare ``configs`` in your method signature to receive it:: def create_media_buy(self, req, ctx, configs=None): configs = configs or {} line_item_id = configs.get(pkg.product_id, {}).get("line_item_id") When the store is not wired, or when ``req.packages`` is ``None`` (proposal-id flow), ``configs`` arrives as an empty dict ``{}``. """ ...Unified hybrid. Return :class:
CreateMediaBuySuccessResponsedirectly for sync fast path; return :meth:RequestContext.handoff_to_taskfor HITL slow path.Pre-flight runs sync regardless of path so bad budgets reject before allocating a task id — call
preflight()at the top,raise AdcpError(…)on rejection.Buyer pattern-matches on the response shape:
media_buy_idfield present → sync successtask_id+status='submitted'→ polltasks_getor receive webhook
Framework injection: when a :class:
~adcp.decisioning.ProductConfigStoreis wired viaconfig_store=in :func:adcp.decisioning.serve.create_adcp_server_from_platform, the framework calls the store before invoking this method and injects the result asconfigs: dict[str, dict[str, Any]]. Declareconfigsin your method signature to receive it::def create_media_buy(self, req, ctx, configs=None): configs = configs or {} line_item_id = configs.get(pkg.product_id, {}).get("line_item_id")When the store is not wired, or when
req.packagesisNone(proposal-id flow),configsarrives as an empty dict{}. def get_media_buy_delivery(self, req: GetMediaBuyDeliveryRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetMediaBuyDeliveryResponse]-
Expand source code
def get_media_buy_delivery( self, req: GetMediaBuyDeliveryRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetMediaBuyDeliveryResponse]: """Sync delivery read — pacing, spend, impressions per package.""" ...Sync delivery read — pacing, spend, impressions per package.
def get_media_buys(self, req: GetMediaBuysRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[GetMediaBuysResponse]-
Expand source code
def get_media_buys( self, req: GetMediaBuysRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[GetMediaBuysResponse]: """List media buys for the resolved account. Required when claiming any ``sales-*`` specialism in v6.0 rc.1+. ``validate_platform`` fails server boot if a sales-claiming platform doesn't implement this. """ ...List media buys for the resolved account.
Required when claiming any
sales-*specialism in v6.0 rc.1+.validate_platformfails server boot if a sales-claiming platform doesn't implement this. def get_products(self, req: GetProductsRequest, ctx: RequestContext[TMeta]) ‑> DiscoveryResult[GetProductsResponse]-
Expand source code
def get_products( self, req: GetProductsRequest, ctx: RequestContext[TMeta], ) -> DiscoveryResult[GetProductsResponse]: """Catalog discovery — synchronous by default, MAY hand off. Return :class:`GetProductsResponse` directly for the sync fast path. Brief / refine discovery MAY hand off via ``ctx.handoff_to_task(fn)`` when composing the catalog needs background work (custom curation queue, slow proposal generation); the framework projects the handoff to the wire ``submitted`` envelope. The buyer reaches the terminal :class:`GetProductsResponse` via ``tasks/get`` polling, and — when the request carried ``push_notification_config`` — the framework also delivers the terminal completion / failure webhook to the buyer's URL from the background completion path (exactly once). ``get_products`` exposes the ``submitted`` / ``working`` / ``input_required`` async arms. Wholesale (``buying_mode='wholesale'``) MUST return synchronously — it is a raw rate-card read with no seller-side composition to background. A wholesale call that cannot finish within the buyer's ``time_budget`` declares the gap via ``incomplete[]`` on a sync response rather than handing off; returning a handoff from a wholesale call is rejected at the framework layer with ``AdcpError(INVALID_REQUEST, field='buying_mode')``. Brief-based proposal generation rides on a separate verb (``request_proposal``, adcp#3407); proposal-mode adopters surface the eventual products via ``ctx.publish_status_change(resource_type='proposal', ...)`` rather than blocking ``get_products`` waiting for trafficker approval. **Buying mode dispatch:** when ``req.buying_mode == 'refine'`` and the platform implements :meth:`refine_get_products`, the framework dispatches there instead of this method. Platforms that do not implement ``refine_get_products`` reject ``buying_mode='refine'`` with ``AdcpError(INVALID_REQUEST, field='buying_mode')`` at the framework layer — the platform method is not called. """ ...Catalog discovery — synchronous by default, MAY hand off.
Return :class:
GetProductsResponsedirectly for the sync fast path. Brief / refine discovery MAY hand off viactx.handoff_to_task(fn)when composing the catalog needs background work (custom curation queue, slow proposal generation); the framework projects the handoff to the wiresubmittedenvelope. The buyer reaches the terminal :class:GetProductsResponseviatasks/getpolling, and — when the request carriedpush_notification_config— the framework also delivers the terminal completion / failure webhook to the buyer's URL from the background completion path (exactly once).get_productsexposes thesubmitted/working/input_requiredasync arms.Wholesale (
buying_mode='wholesale') MUST return synchronously — it is a raw rate-card read with no seller-side composition to background. A wholesale call that cannot finish within the buyer'stime_budgetdeclares the gap viaincomplete[]on a sync response rather than handing off; returning a handoff from a wholesale call is rejected at the framework layer withAdcpError(INVALID_REQUEST, field='buying_mode').Brief-based proposal generation rides on a separate verb (
request_proposal, adcp#3407); proposal-mode adopters surface the eventual products viactx.publish_status_change(resource_type='proposal', ...)rather than blockingget_productswaiting for trafficker approval.Buying mode dispatch: when
req.buying_mode == 'refine'and the platform implements :meth:refine_get_products, the framework dispatches there instead of this method. Platforms that do not implementrefine_get_productsrejectbuying_mode='refine'withAdcpError(INVALID_REQUEST, field='buying_mode')at the framework layer — the platform method is not called. def list_creative_formats_legacy(self, req: ListCreativeFormatsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ListCreativeFormatsResponse]-
Expand source code
def list_creative_formats_legacy( self, req: ListCreativeFormatsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ListCreativeFormatsResponse]: """Catalog of accepted creative formats. Recommended for ``sales-*`` sellers that still serve pre-3.2 versions. Deprecated in AdCP 3.2: publish purchasable canonical declarations through ``get_products`` / ``Product.format_options[]``. Sellers advertising only 3.2 or later can omit this method even with ``ADCP_DECISIONING_STRICT_VALIDATE_PLATFORM=1``. """ ...Catalog of accepted creative formats.
Recommended for
sales-*sellers that still serve pre-3.2 versions. Deprecated in AdCP 3.2: publish purchasable canonical declarations throughget_products/Product.format_options[]. Sellers advertising only 3.2 or later can omit this method even withADCP_DECISIONING_STRICT_VALIDATE_PLATFORM=1. 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]: """List the seller's view of buyer-uploaded creatives. Required when claiming any ``sales-*`` specialism in v6.0 rc.1+. """ ...List the seller's view of buyer-uploaded creatives.
Required when claiming any
sales-*specialism in v6.0 rc.1+. def provide_performance_feedback(self, req: ProvidePerformanceFeedbackRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ProvidePerformanceFeedbackResponse]-
Expand source code
def provide_performance_feedback( self, req: ProvidePerformanceFeedbackRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ProvidePerformanceFeedbackResponse]: """Buyer-supplied performance signal back to the seller. Required when claiming any ``sales-*`` specialism in v6.0 rc.1+. """ ...Buyer-supplied performance signal back to the seller.
Required when claiming any
sales-*specialism in v6.0 rc.1+. def sync_catalogs(self, req: SyncCatalogsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[SyncCatalogsSuccessResponse]-
Expand source code
def sync_catalogs( self, req: SyncCatalogsRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[SyncCatalogsSuccessResponse]: """Sync product catalogs with the platform. **Required** when claiming ``sales-catalog-driven``. ``validate_platform`` hard-fails at server boot when this method is absent on a ``sales-catalog-driven`` platform. Discovery mode: when ``req.catalogs is None``, return the account's existing synced catalogs without modification (read-only path per AdCP spec). Check ``req.catalogs`` before applying any mutations:: def sync_catalogs(self, req, ctx): if req.catalogs is None: return self._get_existing_catalogs(ctx.account_id) return self._upsert_catalogs(req.catalogs, ctx) **Important:** ``req.delete_missing=True`` with ``req.catalogs=None`` is spec-undefined — reject it with ``AdcpError("INVALID_REQUEST", field="catalogs")`` rather than silently deleting buyer-managed catalogs. Return a list of :class:`~adcp.types.SyncCatalogResult` rows (ergonomic form) or a fully-shaped :class:`~adcp.types.SyncCatalogsSuccessResponse`. """ raise NotImplementedErrorSync product catalogs with the platform.
Required when claiming
sales-catalog-driven.validate_platformhard-fails at server boot when this method is absent on asales-catalog-drivenplatform.Discovery mode: when
req.catalogs is None, return the account's existing synced catalogs without modification (read-only path per AdCP spec). Checkreq.catalogsbefore applying any mutations::def sync_catalogs(self, req, ctx): if req.catalogs is None: return self._get_existing_catalogs(ctx.account_id) return self._upsert_catalogs(req.catalogs, ctx)Important:
req.delete_missing=Truewithreq.catalogs=Noneis spec-undefined — reject it withAdcpError("INVALID_REQUEST", field="catalogs")rather than silently deleting buyer-managed catalogs.Return a list of :class:
~adcp.types.SyncCatalogResultrows (ergonomic form) or a fully-shaped :class:~adcp.types.SyncCatalogsSuccessResponse. 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]: """Unified hybrid for creative review. Mixed approved/pending rows in a single sync response, OR hand off the whole batch to background standards-and-practices review. Adopters with pre-approved buyer pools fast-path; new buyers' creatives go to review. """ ...Unified hybrid for creative review.
Mixed approved/pending rows in a single sync response, OR hand off the whole batch to background standards-and-practices review. Adopters with pre-approved buyer pools fast-path; new buyers' creatives go to review.
def update_media_buy(self, media_buy_id: str, patch: UpdateMediaBuyRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[UpdateMediaBuySuccessResponse]-
Expand source code
def update_media_buy( self, media_buy_id: str, patch: UpdateMediaBuyRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[UpdateMediaBuySuccessResponse]: """Mutate an in-flight media buy. v6.0 returns sync only — the per-tool response schema doesn't carry the ``Submitted`` arm yet (adcp#3392). Re-approval flows return the success with the ``status`` field omitted (in-spec per the schema description) and drive lifecycle via ``ctx.publish_status_change``. v6.1 + adcp#3392 expand this signature to :data:`SalesResult` so re-approval flows can hand off cleanly. """ ...Mutate an in-flight media buy.
v6.0 returns sync only — the per-tool response schema doesn't carry the
Submittedarm yet (adcp#3392). Re-approval flows return the success with thestatusfield omitted (in-spec per the schema description) and drive lifecycle viactx.publish_status_change. v6.1 + adcp#3392 expand this signature to :data:SalesResultso re-approval flows can hand off cleanly.
class SignalsPlatform (*args, **kwargs)-
Expand source code
@runtime_checkable class SignalsPlatform(OwnedSignalsPlatform[TMeta], Protocol, Generic[TMeta]): """Catalog discovery + activation for marketplace/provisioned signals. Use this Protocol for ``signal-marketplace``. Use :class:`OwnedSignalsPlatform` for ``signal-owned`` platforms where returned signals are already usable in later media-buy targeting and there is no buyer-triggered destination provisioning step. """ def activate_signal( self, req: ActivateSignalRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ActivateSignalSuccessResponse]: """Provision a signal onto one or more destination platforms (Snap, Meta, TikTok, etc.). Returns the success-arm shape immediately with ``deployments`` rows in their current state — ``'pending'`` is a valid sync return for slow activation pipelines. Subsequent state changes (per-deployment ``activating`` / ``deployed`` / ``failed``) flow via ``ctx.publish_status_change(resource_type='signal', resource_id=signal_agent_segment_id, payload=...)`` as each destination's identity-graph match completes. Use ``req.action='deactivate'`` for GDPR/CCPA-compliant teardown when campaigns end. :raises adcp.decisioning.AdcpError: ``code='SIGNAL_NOT_FOUND'`` (unknown ``signal_agent_segment_id``), ``code='POLICY_VIOLATION'`` (buyer lacks rights to activate this data), or ``code='INVALID_REQUEST'`` (missing or unrecognized destination). """ ...Catalog discovery + activation for marketplace/provisioned signals.
Use this Protocol for
signal-marketplace. Use :class:OwnedSignalsPlatformforsignal-ownedplatforms where returned signals are already usable in later media-buy targeting and there is no buyer-triggered destination provisioning step.Ancestors
- OwnedSignalsPlatform
- typing.Protocol
- typing.Generic
Methods
def activate_signal(self, req: ActivateSignalRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ActivateSignalSuccessResponse]-
Expand source code
def activate_signal( self, req: ActivateSignalRequest, ctx: RequestContext[TMeta], ) -> MaybeAsync[ActivateSignalSuccessResponse]: """Provision a signal onto one or more destination platforms (Snap, Meta, TikTok, etc.). Returns the success-arm shape immediately with ``deployments`` rows in their current state — ``'pending'`` is a valid sync return for slow activation pipelines. Subsequent state changes (per-deployment ``activating`` / ``deployed`` / ``failed``) flow via ``ctx.publish_status_change(resource_type='signal', resource_id=signal_agent_segment_id, payload=...)`` as each destination's identity-graph match completes. Use ``req.action='deactivate'`` for GDPR/CCPA-compliant teardown when campaigns end. :raises adcp.decisioning.AdcpError: ``code='SIGNAL_NOT_FOUND'`` (unknown ``signal_agent_segment_id``), ``code='POLICY_VIOLATION'`` (buyer lacks rights to activate this data), or ``code='INVALID_REQUEST'`` (missing or unrecognized destination). """ ...Provision a signal onto one or more destination platforms (Snap, Meta, TikTok, etc.).
Returns the success-arm shape immediately with
deploymentsrows in their current state —'pending'is a valid sync return for slow activation pipelines.Subsequent state changes (per-deployment
activating/deployed/failed) flow viactx.publish_status_change(resource_type='signal', resource_id=signal_agent_segment_id, payload=...)as each destination's identity-graph match completes.Use
req.action='deactivate'for GDPR/CCPA-compliant teardown when campaigns end.:raises adcp.decisioning.AdcpError:
code='SIGNAL_NOT_FOUND'(unknownsignal_agent_segment_id),code='POLICY_VIOLATION'(buyer lacks rights to activate this data), orcode='INVALID_REQUEST'(missing or unrecognized destination).
Inherited members