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 spec sales-* slugs (non-guaranteed, guaranteed, broadcast-tv, social, proposal-mode, catalog-driven) under one unified hybrid shape.
  • :class:SignalsPlatform — covers signal-marketplace. Two methods: get_signals (catalog discovery) and activate_signal (provisioning onto destination platforms).
  • :class:OwnedSignalsPlatform — covers signal-owned. One method: get_signals (publisher-owned signal catalog discovery; signals are already usable on seller inventory).
  • :class:AudiencePlatform — covers audience-sync. Two methods: sync_audiences (push first-party CRM audiences with delta upsert) and poll_audience_statuses (batch state read).
  • :class:CreativeBuilderPlatform — covers creative-template + creative-generative. Required build_creative; optional preview_creative, sync_creatives. Unified shape per JS commit 841616d7 (F13) — wire spec doesn't distinguish template-driven transform from brief-to-creative generation. (No separate refine_creative method — refinement is invoked via build_creative with creative_id referencing the prior build, per schemas/cache/media-buy/build-creative-request.json.)
  • :class:CreativeAdServerPlatform — covers creative-ad-server. Stateful library + per-creative pricing + tag generation. Required build_creative, preview_creative, list_creatives, get_creative_delivery; optional sync_creatives.
  • :class:CampaignGovernancePlatform — covers governance-spend-authority + governance-delivery-monitor. Required check_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 — covers brand-rights. Required get_brand_identity, get_rights, acquire_rights (3-arm discriminated success union: acquired / pending / rejected).
  • :class:ContentStandardsPlatform — covers content-standards. 6 required CRUD + calibration + validation methods; 2 optional analyzer reads (get_media_buy_artifacts, get_creative_features).
  • :class:PropertyListsPlatform — covers property-lists. Standard 5-method CRUD with fetch-token issuance.
  • :class:CollectionListsPlatform — covers collection-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-sync specialism …

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-authority and governance-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 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:AdcpError for 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); 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.

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 — 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).

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[
        AcquireRightsAcquiredResponse | AcquireRightsPendingResponse | AcquireRightsRejectedResponse
    ]:
        """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 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 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: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.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

def acquire_rights(self, req: AcquireRightsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[AcquireRightsAcquiredResponse | AcquireRightsPendingResponse | AcquireRightsRejectedResponse]
Expand source code
def acquire_rights(
    self,
    req: AcquireRightsRequest,
    ctx: RequestContext[TMeta],
) -> MaybeAsync[
    AcquireRightsAcquiredResponse | AcquireRightsPendingResponse | AcquireRightsRejectedResponse
]:
    """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. 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 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, 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]
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 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 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.

        Buyer (or seller, on the seller's behalf) sends a proposed
        action; the agent inspects it against the plan and returns
        approved / denied / conditions.

        The ``phase`` field discriminates the context:

        * ``'intent'`` — pre-action; agent decides whether the
          proposed action is permitted at all.
        * ``'delivery'`` — running campaign with actuals; agent
          decides whether to allow further spend / new packages.
        * ``'reconciliation'`` — post-flight; agent confirms the
          campaign's outcome matches what was approved.

        The agent's logic varies by phase.

        :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]:
        """Outcome reporting from sellers.

        Sellers report what actually happened (impressions delivered,
        spend incurred, status transitions) so the agent can
        calibrate future decisions. Typically called at terminal
        plan states or at agreed reconciliation cadences.
        """
        ...

    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, or conditions (approved-if). Status changes (plan moving from pending_approvalactiveclosed) 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: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.

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.

    Buyer (or seller, on the seller's behalf) sends a proposed
    action; the agent inspects it against the plan and returns
    approved / denied / conditions.

    The ``phase`` field discriminates the context:

    * ``'intent'`` — pre-action; agent decides whether the
      proposed action is permitted at all.
    * ``'delivery'`` — running campaign with actuals; agent
      decides whether to allow further spend / new packages.
    * ``'reconciliation'`` — post-flight; agent confirms the
      campaign's outcome matches what was approved.

    The agent's logic varies by phase.

    :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.

Buyer (or seller, on the seller's behalf) sends a proposed action; the agent inspects it against the plan and returns approved / denied / conditions.

The phase field discriminates the context:

  • 'intent' — pre-action; agent decides whether the proposed action is permitted at all.
  • 'delivery' — running campaign with actuals; agent decides whether to allow further spend / new packages.
  • 'reconciliation' — post-flight; agent confirms the campaign's outcome matches what was approved.

The agent's logic varies by phase.

: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]:
    """Outcome reporting from sellers.

    Sellers report what actually happened (impressions delivered,
    spend incurred, status transitions) so the agent can
    calibrate future decisions. Typically called at terminal
    plan states or at agreed reconciliation cadences.
    """
    ...

Outcome reporting from sellers.

Sellers report what actually happened (impressions delivered, spend incurred, status transitions) so the agent can calibrate future decisions. Typically called at terminal plan states or at agreed reconciliation cadences.

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-seller fetch_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_token and 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 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 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:AdcpError for buyer-fixable rejection (REFERENCE_NOT_FOUND, INVALID_REQUEST, POLICY_VIOLATION for 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_artifact to 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(
        self,
        req: BuildCreativeRequest,
        ctx: RequestContext[TMeta],
    ) -> MaybeAsync[BuildCreativeSuccessResponse | Sequence[CreativeManifest] | CreativeManifest]:
        """Build / retrieve creative tags. Two invocation modes:

        * **Library lookup**: ``req.creative_id`` references an
          existing creative; return the manifest with tag fields
          populated (``vast_tag``, click trackers, etc.). When
          ``req.media_buy_id`` + ``req.package_id`` are also set,
          generate placement-specific tags with macro substitution
          baked in.
        * **Inline build**: ``req.creative_manifest`` is provided
          directly; transform / wrap it (similar to template archetype
          but with ad-server side effects: register the creative in
          the library, generate the tag, etc.).

        Sync at the wire level — the per-tool
        ``build-creative-response.json`` ``oneOf`` doesn't include a
        ``Submitted`` arm (spec inconsistency tracked as
        ``adcontextprotocol/adcp#3392``). Until the spec rolls
        Submitted into the ``oneOf``, slow tag-generation pipelines
        await in-request; status changes flow via
        ``ctx.publish_status_change``.

        Return shape: see :meth:`CreativeBuilderPlatform.build_creative`
        for the discriminated-arm rules — single
        :class:`CreativeManifest`, ``Sequence[CreativeManifest]`` for
        multi-format, or :class:`BuildCreativeSuccessResponse` envelope
        when you need ``sandbox``/``expires_at``/``preview``.
        """
        ...

    def preview_creative(
        self,
        req: PreviewCreativeRequest,
        ctx: RequestContext[TMeta],
    ) -> MaybeAsync[PreviewCreativeResponse]:
        """Preview-only variant — sandbox URL or inline HTML, expires.

        Always sync. NOT optional for ad-server adopters (distinct from
        :class:`CreativeBuilderPlatform.preview_creative`, which is
        optional) — buyers expect preview surface from any stateful
        creative library.
        """
        ...

    def list_creatives(
        self,
        req: ListCreativesRequest,
        ctx: RequestContext[TMeta],
    ) -> MaybeAsync[ListCreativesResponse]:
        """Read creatives from the library.

        Filters + pagination. When ``req.include_assignments``,
        include the buyer's package-assignment graph. When
        ``req.include_pricing``, include vendor pricing options on
        each creative.
        """
        ...

    def get_creative_delivery(
        self,
        req: GetCreativeDeliveryRequest,
        ctx: RequestContext[TMeta],
    ) -> MaybeAsync[GetCreativeDeliveryResponse]:
        """Per-creative delivery actuals (impressions, spend, pacing).

        Sync — report-running platforms with manual report cycles
        return the latest cached actuals and emit ``delivery_report``
        status changes via ``ctx.publish_status_change`` when fresh
        reports are available.
        """
        ...

    def sync_creatives(
        self,
        req: SyncCreativesRequest,
        ctx: RequestContext[TMeta],
    ) -> SalesResult[SyncCreativesSuccessResponse]:
        """Push creatives. Optional — present-or-absent.

        Return the typed :class:`SyncCreativesSuccessResponse` for the
        sync fast path OR ``ctx.handoff_to_task(fn)`` for HITL —
        brand-suitability, S&P review. ``action: 'created'`` for new
        entries, ``'updated'`` for replacements, ``'unchanged'`` when
        matching. Optional ``status: 'pending_review'`` for sync-arm
        rows awaiting manual review.

        Same wire request type as the sales-* and creative-builder
        archetypes use (``SyncCreativesRequest`` — shared spec shape).
        """
        ...

Stateful creative library + per-creative pricing + tag generation.

Methods may be sync (return 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:AdcpError for buyer-fixable rejection.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

def build_creative(self, req: BuildCreativeRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[BuildCreativeSuccessResponse | Sequence[CreativeManifest] | CreativeManifest]
Expand source code
def build_creative(
    self,
    req: BuildCreativeRequest,
    ctx: RequestContext[TMeta],
) -> MaybeAsync[BuildCreativeSuccessResponse | Sequence[CreativeManifest] | CreativeManifest]:
    """Build / retrieve creative tags. Two invocation modes:

    * **Library lookup**: ``req.creative_id`` references an
      existing creative; return the manifest with tag fields
      populated (``vast_tag``, click trackers, etc.). When
      ``req.media_buy_id`` + ``req.package_id`` are also set,
      generate placement-specific tags with macro substitution
      baked in.
    * **Inline build**: ``req.creative_manifest`` is provided
      directly; transform / wrap it (similar to template archetype
      but with ad-server side effects: register the creative in
      the library, generate the tag, etc.).

    Sync at the wire level — the per-tool
    ``build-creative-response.json`` ``oneOf`` doesn't include a
    ``Submitted`` arm (spec inconsistency tracked as
    ``adcontextprotocol/adcp#3392``). Until the spec rolls
    Submitted into the ``oneOf``, slow tag-generation pipelines
    await in-request; status changes flow via
    ``ctx.publish_status_change``.

    Return shape: see :meth:`CreativeBuilderPlatform.build_creative`
    for the discriminated-arm rules — single
    :class:`CreativeManifest`, ``Sequence[CreativeManifest]`` for
    multi-format, or :class:`BuildCreativeSuccessResponse` envelope
    when you need ``sandbox``/``expires_at``/``preview``.
    """
    ...

Build / retrieve creative tags. Two invocation modes:

  • Library lookup: req.creative_id references an existing creative; return the manifest with tag fields populated (vast_tag, click trackers, etc.). When req.media_buy_id + req.package_id are also set, generate placement-specific tags with macro substitution baked in.
  • Inline build: req.creative_manifest is provided directly; transform / wrap it (similar to template archetype but with ad-server side effects: register the creative in the library, generate the tag, etc.).

Sync at the wire level — the per-tool build-creative-response.json oneOf doesn't include a Submitted arm (spec inconsistency tracked as adcontextprotocol/adcp#3392). Until the spec rolls Submitted into the oneOf, slow tag-generation pipelines await in-request; status changes flow via ctx.publish_status_change.

Return shape: see :meth:CreativeBuilderPlatform.build_creative() for the discriminated-arm rules — single :class:CreativeManifest, Sequence[CreativeManifest] for multi-format, or :class:BuildCreativeSuccessResponse envelope when you need sandbox/expires_at/preview.

def 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_report status changes via ctx.publish_status_change when 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. When req.include_pricing, include vendor pricing options on each creative.

def preview_creative(self, req: PreviewCreativeRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[PreviewCreativeResponse]
Expand source code
def preview_creative(
    self,
    req: PreviewCreativeRequest,
    ctx: RequestContext[TMeta],
) -> MaybeAsync[PreviewCreativeResponse]:
    """Preview-only variant — sandbox URL or inline HTML, expires.

    Always sync. NOT optional for ad-server adopters (distinct from
    :class:`CreativeBuilderPlatform.preview_creative`, which is
    optional) — buyers expect preview surface from any stateful
    creative library.
    """
    ...

Preview-only variant — sandbox URL or inline HTML, expires.

Always sync. NOT optional for ad-server adopters (distinct from :class:CreativeBuilderPlatform.preview_creative(), which is optional) — buyers expect preview surface from any stateful creative library.

def sync_creatives(self, req: SyncCreativesRequest, ctx: RequestContext[TMeta]) ‑> SalesResult[SyncCreativesSuccessResponse]
Expand source code
def sync_creatives(
    self,
    req: SyncCreativesRequest,
    ctx: RequestContext[TMeta],
) -> SalesResult[SyncCreativesSuccessResponse]:
    """Push creatives. Optional — present-or-absent.

    Return the typed :class:`SyncCreativesSuccessResponse` for the
    sync fast path OR ``ctx.handoff_to_task(fn)`` for HITL —
    brand-suitability, S&P review. ``action: 'created'`` for new
    entries, ``'updated'`` for replacements, ``'unchanged'`` when
    matching. Optional ``status: 'pending_review'`` for sync-arm
    rows awaiting manual review.

    Same wire request type as the sales-* and creative-builder
    archetypes use (``SyncCreativesRequest`` — shared spec shape).
    """
    ...

Push creatives. Optional — present-or-absent.

Return the typed :class: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).

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(
        self,
        req: BuildCreativeRequest,
        ctx: RequestContext[TMeta],
    ) -> MaybeAsync[BuildCreativeSuccessResponse | 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 a
          :class:`BuildCreativeSuccessResponse` with ``sandbox`` /
          ``expires_at`` / ``preview`` populated. Framework passes
          through unchanged.

        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(
        self,
        req: PreviewCreativeRequest,
        ctx: RequestContext[TMeta],
    ) -> MaybeAsync[PreviewCreativeResponse]:
        """Preview-only variant — sandbox URL or inline HTML, expires.

        Always sync. Optional — generative-only adopters that don't
        render preview ahead of generation can omit it; the framework
        returns ``UNSUPPORTED_FEATURE`` to buyers calling
        ``preview_creative`` against a platform that didn't wire this.
        """
        ...

    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 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: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.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

def build_creative(self, req: BuildCreativeRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[BuildCreativeSuccessResponse | Sequence[CreativeManifest] | CreativeManifest]
Expand source code
def build_creative(
    self,
    req: BuildCreativeRequest,
    ctx: RequestContext[TMeta],
) -> MaybeAsync[BuildCreativeSuccessResponse | Sequence[CreativeManifest] | CreativeManifest]:
    """Build 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 a
      :class:`BuildCreativeSuccessResponse` with ``sandbox`` /
      ``expires_at`` / ``preview`` populated. Framework passes
      through unchanged.

    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 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 a :class:BuildCreativeSuccessResponse with sandbox / expires_at / preview populated. Framework passes through unchanged.

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(self, req: PreviewCreativeRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[PreviewCreativeResponse]
Expand source code
def preview_creative(
    self,
    req: PreviewCreativeRequest,
    ctx: RequestContext[TMeta],
) -> MaybeAsync[PreviewCreativeResponse]:
    """Preview-only variant — sandbox URL or inline HTML, expires.

    Always sync. Optional — generative-only adopters that don't
    render preview ahead of generation can omit it; the framework
    returns ``UNSUPPORTED_FEATURE`` to buyers calling
    ``preview_creative`` against a platform that didn't wire this.
    """
    ...

Preview-only variant — sandbox URL or inline HTML, expires.

Always sync. Optional — generative-only adopters that don't render preview ahead of generation can omit it; the framework returns UNSUPPORTED_FEATURE to buyers calling preview_creative against a platform that didn't wire this.

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: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]
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_FEATURE when 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 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:AdcpError for 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: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.

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 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:AdcpError for 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_token the buyer stores in their secret manager. Token is scoped to this list_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_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.

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_token from :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(
        self,
        req: ListCreativeFormatsRequest,
        ctx: RequestContext[TMeta],
    ) -> MaybeAsync[ListCreativeFormatsResponse]:
        """Catalog of accepted creative formats.

        Required when claiming any ``sales-*`` specialism in v6.0 rc.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 NotImplementedError

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: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.

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: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 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_platform fails 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: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.

def list_creative_formats(self, req: ListCreativeFormatsRequest, ctx: RequestContext[TMeta]) ‑> MaybeAsync[ListCreativeFormatsResponse]
Expand source code
def list_creative_formats(
    self,
    req: ListCreativeFormatsRequest,
    ctx: RequestContext[TMeta],
) -> MaybeAsync[ListCreativeFormatsResponse]:
    """Catalog of accepted creative formats.

    Required when claiming any ``sales-*`` specialism in v6.0 rc.1+.
    """
    ...

Catalog of accepted creative formats.

Required when claiming any sales-* specialism in v6.0 rc.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 NotImplementedError

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.

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 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.

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: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.

Ancestors

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 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).

Inherited members