Package adcp

Sub-modules

adcp.adagents
adcp.capabilities

Feature capability resolution for AdCP …

adcp.client
adcp.config
adcp.exceptions

Exception hierarchy for AdCP client.

adcp.property_registry

PropertyRegistry — local authorization cache backed by the AAO registry …

adcp.protocols
adcp.registry

Client for the AdCP registry API (brand, property, member, and policy lookups).

adcp.registry_sync

Registry change feed synchronization.

adcp.server

ADCP Server Framework …

adcp.signing

AdCP RFC 9421 request-signing profile …

adcp.simple

Simplified API accessor for ADCPClient …

adcp.testing

Test helpers for AdCP client library …

adcp.types

AdCP Type System …

adcp.utils
adcp.validation

Runtime validation for AdCP data structures …

adcp.webhook_receiver

One-call webhook receiver: verify signature, dedupe, parse …

adcp.webhook_sender

One-call outbound webhook delivery for AdCP senders …

adcp.webhooks

Webhook creation, signing, and reception for AdCP agents …

Functions

def create_a2a_webhook_payload(task_id: str,
status: TaskStatus,
context_id: str,
result: AdcpAsyncResponseData | dict[str, Any],
timestamp: datetime | None = None) ‑> a2a.types.Task | a2a.types.TaskStatusUpdateEvent
Expand source code
def create_a2a_webhook_payload(
    task_id: str,
    status: GeneratedTaskStatus,
    context_id: str,
    result: AdcpAsyncResponseData | dict[str, Any],
    timestamp: datetime | None = None,
) -> Task | TaskStatusUpdateEvent:
    """
    Create A2A webhook payload (Task or TaskStatusUpdateEvent).

    Per A2A specification:
    - Terminated statuses (completed, failed): Returns Task with artifacts[].parts[]
    - Intermediate statuses (working, input-required, submitted): Returns TaskStatusUpdateEvent
      with status.message.parts[]

    This function helps agent implementations construct properly formatted A2A webhook
    payloads for sending to clients.

    Args:
        task_id: Unique identifier for the task
        status: Current task status
        context_id: Session/conversation identifier (required by A2A protocol)
        timestamp: When the webhook was generated (defaults to current UTC time)
        result: Task-specific payload (AdCP response data)

    Returns:
        Task object for terminated statuses, TaskStatusUpdateEvent for intermediate statuses

    Examples:
        Create a completed Task webhook:
        >>> from adcp.webhooks import create_a2a_webhook_payload
        >>> from adcp.types import GeneratedTaskStatus
        >>>
        >>> task = create_a2a_webhook_payload(
        ...     task_id="task_123",
        ...     status=GeneratedTaskStatus.completed,
        ...     result={"products": [...]},
        ...     message="Found 5 products"
        ... )
        >>> # task is a Task object with artifacts containing the result

        Create a working status update:
        >>> event = create_a2a_webhook_payload(
        ...     task_id="task_456",
        ...     status=GeneratedTaskStatus.working,
        ...     message="Processing 3 of 10 items"
        ... )
        >>> # event is a TaskStatusUpdateEvent with status.message

        Send A2A webhook via HTTP POST:
        >>> import httpx
        >>> from a2a.types import Task
        >>>
        >>> payload = create_a2a_webhook_payload(...)
        >>> # Serialize to dict for JSON
        >>> if isinstance(payload, Task):
        ...     payload_dict = payload.model_dump(mode='json')
        ... else:
        ...     payload_dict = payload.model_dump(mode='json')
        >>>
        >>> response = await httpx.post(webhook_url, json=payload_dict)
    """
    if timestamp is None:
        timestamp = datetime.now(timezone.utc)

    # Convert datetime to ISO string for A2A protocol
    timestamp_str = timestamp.isoformat() if isinstance(timestamp, datetime) else timestamp

    # Map GeneratedTaskStatus to A2A status state string
    status_value = status.value if hasattr(status, "value") else str(status)

    # Map AdCP status to A2A status state
    # Note: A2A uses "input-required" (hyphenated) while AdCP uses "input_required" (underscore)
    status_mapping = {
        "completed": "completed",
        "failed": "failed",
        "working": "working",
        "submitted": "submitted",
        "input_required": "input-required",
    }
    a2a_status_state = status_mapping.get(status_value, status_value)

    # Build parts for the message/artifact
    parts: list[Part] = []

    # Add DataPart
    # Convert AdcpAsyncResponseData to dict if it's a Pydantic model
    if hasattr(result, "model_dump"):
        result_dict: dict[str, Any] = result.model_dump(mode="json")
    else:
        result_dict = result

    data_part = DataPart(data=result_dict)
    parts.append(Part(root=data_part))

    # Determine if this is a terminated status (Task) or intermediate (TaskStatusUpdateEvent)
    is_terminated = status in [GeneratedTaskStatus.completed, GeneratedTaskStatus.failed]

    # Convert string to TaskState enum
    task_state_enum = TaskState(a2a_status_state)

    if is_terminated:
        # Create Task object with artifacts for terminated statuses
        task_status = TaskStatus(state=task_state_enum, timestamp=timestamp_str)

        # Build artifact with parts
        # Note: Artifact requires artifact_id, use task_id as prefix
        if parts:
            artifact = Artifact(
                artifact_id=f"{task_id}_result",
                parts=parts,
            )
            artifacts = [artifact]
        else:
            artifacts = []

        return Task(
            id=task_id,
            status=task_status,
            artifacts=artifacts,
            context_id=context_id,
        )
    else:
        # Create TaskStatusUpdateEvent with status.message for intermediate statuses
        # Build message with parts
        if parts:
            message_obj = Message(
                message_id=f"{task_id}_msg",
                role=Role.agent,  # Agent is responding
                parts=parts,
            )
        else:
            message_obj = None

        task_status = TaskStatus(
            state=task_state_enum, timestamp=timestamp_str, message=message_obj
        )

        return TaskStatusUpdateEvent(
            task_id=task_id,
            status=task_status,
            context_id=context_id,
            final=False,  # Intermediate statuses are not final
        )

Create A2A webhook payload (Task or TaskStatusUpdateEvent).

Per A2A specification: - Terminated statuses (completed, failed): Returns Task with artifacts[].parts[] - Intermediate statuses (working, input-required, submitted): Returns TaskStatusUpdateEvent with status.message.parts[]

This function helps agent implementations construct properly formatted A2A webhook payloads for sending to clients.

Args

task_id
Unique identifier for the task
status
Current task status
context_id
Session/conversation identifier (required by A2A protocol)
timestamp
When the webhook was generated (defaults to current UTC time)
result
Task-specific payload (AdCP response data)

Returns

Task object for terminated statuses, TaskStatusUpdateEvent for intermediate statuses

Examples

Create a completed Task webhook:

>>> from adcp.webhooks import create_a2a_webhook_payload
>>> from adcp.types import GeneratedTaskStatus
>>>
>>> task = create_a2a_webhook_payload(
...     task_id="task_123",
...     status=GeneratedTaskStatus.completed,
...     result={"products": [...]},
...     message="Found 5 products"
... )
>>> # task is a Task object with artifacts containing the result

Create a working status update:

>>> event = create_a2a_webhook_payload(
...     task_id="task_456",
...     status=GeneratedTaskStatus.working,
...     message="Processing 3 of 10 items"
... )
>>> # event is a TaskStatusUpdateEvent with status.message

Send A2A webhook via HTTP POST:

>>> import httpx
>>> from a2a.types import Task
>>>
>>> payload = create_a2a_webhook_payload(...)
>>> # Serialize to dict for JSON
>>> if isinstance(payload, Task):
...     payload_dict = payload.model_dump(mode='json')
... else:
...     payload_dict = payload.model_dump(mode='json')
>>>
>>> response = await httpx.post(webhook_url, json=payload_dict)
def create_mcp_webhook_payload(task_id: str,
status: TaskStatus | str,
result: AdcpAsyncResponseData | dict[str, Any] | None = None,
timestamp: datetime | None = None,
task_type: str | None = None,
operation_id: str | None = None,
message: str | None = None,
context_id: str | None = None,
domain: str | None = None,
idempotency_key: str | None = None) ‑> dict[str, typing.Any]
Expand source code
def create_mcp_webhook_payload(
    task_id: str,
    status: GeneratedTaskStatus | str,
    result: AdcpAsyncResponseData | dict[str, Any] | None = None,
    timestamp: datetime | None = None,
    task_type: str | None = None,
    operation_id: str | None = None,
    message: str | None = None,
    context_id: str | None = None,
    domain: str | None = None,
    idempotency_key: str | None = None,
) -> dict[str, Any]:
    """
    Create MCP webhook payload dictionary.

    This function helps agent implementations construct properly formatted
    webhook payloads for sending to clients.

    Args:
        task_id: Unique identifier for the task
        status: Current task status
        task_type: Optionally type of AdCP operation (e.g., "get_products", "create_media_buy")
        timestamp: When the webhook was generated (defaults to current UTC time)
        result: Task-specific payload (AdCP response data)
        operation_id: Publisher-defined operation identifier (deprecated from payload,
            should be in URL routing, but included for backward compatibility)
        message: Human-readable summary of task state
        context_id: Session/conversation identifier
        domain: AdCP domain this task belongs to
        idempotency_key: Sender-generated key stable across retries of the same
            event. Defaults to a freshly-generated UUID v4 — callers retrying
            delivery of the same event MUST pass the key from their first
            attempt; passing None twice mints two keys and defeats dedup.

    Returns:
        Dictionary matching McpWebhookPayload schema, ready to be sent as JSON

    Examples:
        Create a completed webhook with results:
        >>> from adcp.webhooks import create_mcp_webhook_payload
        >>> from adcp.types import GeneratedTaskStatus
        >>>
        >>> payload = create_mcp_webhook_payload(
        ...     task_id="task_123",
        ...     task_type="get_products",
        ...     status=GeneratedTaskStatus.completed,
        ...     result={"products": [...]},
        ...     message="Found 5 products"
        ... )

        Create a failed webhook with error:
        >>> payload = create_mcp_webhook_payload(
        ...     task_id="task_456",
        ...     task_type="create_media_buy",
        ...     status=GeneratedTaskStatus.failed,
        ...     result={"errors": [{"code": "INVALID_INPUT", "message": "..."}]},
        ...     message="Validation failed"
        ... )

        Create a working status update:
        >>> payload = create_mcp_webhook_payload(
        ...     task_id="task_789",
        ...     task_type="sync_creatives",
        ...     status=GeneratedTaskStatus.working,
        ...     message="Processing 3 of 10 creatives"
        ... )
    """
    if timestamp is None:
        timestamp = datetime.now(timezone.utc)
    if idempotency_key is None:
        idempotency_key = generate_webhook_idempotency_key()

    # Convert status enum to string value
    status_value = status.value if hasattr(status, "value") else str(status)

    # Build payload matching McpWebhookPayload schema
    payload: dict[str, Any] = {
        "idempotency_key": idempotency_key,
        "task_id": task_id,
        "task_type": task_type,
        "status": status_value,
        "timestamp": timestamp.isoformat() if isinstance(timestamp, datetime) else timestamp,
    }

    # Add optional fields only if provided
    if result is not None:
        # Convert Pydantic model to dict if needed for JSON serialization
        if hasattr(result, "model_dump"):
            payload["result"] = result.model_dump(mode="json")
        else:
            payload["result"] = result

    if operation_id is not None:
        payload["operation_id"] = operation_id

    if message is not None:
        payload["message"] = message

    if context_id is not None:
        payload["context_id"] = context_id

    if domain is not None:
        payload["domain"] = domain

    return payload

Create MCP webhook payload dictionary.

This function helps agent implementations construct properly formatted webhook payloads for sending to clients.

Args

task_id
Unique identifier for the task
status
Current task status
task_type
Optionally type of AdCP operation (e.g., "get_products", "create_media_buy")
timestamp
When the webhook was generated (defaults to current UTC time)
result
Task-specific payload (AdCP response data)
operation_id
Publisher-defined operation identifier (deprecated from payload, should be in URL routing, but included for backward compatibility)
message
Human-readable summary of task state
context_id
Session/conversation identifier
domain
AdCP domain this task belongs to
idempotency_key
Sender-generated key stable across retries of the same event. Defaults to a freshly-generated UUID v4 — callers retrying delivery of the same event MUST pass the key from their first attempt; passing None twice mints two keys and defeats dedup.

Returns

Dictionary matching McpWebhookPayload schema, ready to be sent as JSON

Examples

Create a completed webhook with results:

>>> from adcp.webhooks import create_mcp_webhook_payload
>>> from adcp.types import GeneratedTaskStatus
>>>
>>> payload = create_mcp_webhook_payload(
...     task_id="task_123",
...     task_type="get_products",
...     status=GeneratedTaskStatus.completed,
...     result={"products": [...]},
...     message="Found 5 products"
... )

Create a failed webhook with error:

>>> payload = create_mcp_webhook_payload(
...     task_id="task_456",
...     task_type="create_media_buy",
...     status=GeneratedTaskStatus.failed,
...     result={"errors": [{"code": "INVALID_INPUT", "message": "..."}]},
...     message="Validation failed"
... )

Create a working status update:

>>> payload = create_mcp_webhook_payload(
...     task_id="task_789",
...     task_type="sync_creatives",
...     status=GeneratedTaskStatus.working,
...     message="Processing 3 of 10 creatives"
... )
def create_test_agent(**overrides: Any) ‑> AgentConfig
Expand source code
def create_test_agent(**overrides: Any) -> AgentConfig:
    """Create a custom test agent configuration.

    Useful when you need to modify the default test agent setup.

    Args:
        **overrides: Keyword arguments to override default config values

    Returns:
        Complete agent configuration

    Example:
        ```python
        from adcp.testing import create_test_agent
        from adcp.client import ADCPClient

        # Use default test agent with custom ID
        config = create_test_agent(id="my-test-agent")
        client = ADCPClient(config)
        ```

    Example:
        ```python
        # Use A2A protocol instead of MCP
        from adcp.types.core import Protocol

        config = create_test_agent(
            protocol=Protocol.A2A,
            agent_uri="https://test-agent.adcontextprotocol.org"
        )
        ```
    """
    base_config = TEST_AGENT_MCP_CONFIG.model_dump()
    base_config.update(overrides)
    return AgentConfig(**base_config)

Create a custom test agent configuration.

Useful when you need to modify the default test agent setup.

Args

**overrides
Keyword arguments to override default config values

Returns

Complete agent configuration

Example

from adcp.testing import create_test_agent
from adcp.client import ADCPClient

# Use default test agent with custom ID
config = create_test_agent(id="my-test-agent")
client = ADCPClient(config)

Example

# Use A2A protocol instead of MCP
from adcp.types.core import Protocol

config = create_test_agent(
    protocol=Protocol.A2A,
    agent_uri="https://test-agent.adcontextprotocol.org"
)
def domain_matches(property_domain: str, agent_domain_pattern: str) ‑> bool
Expand source code
def domain_matches(property_domain: str, agent_domain_pattern: str) -> bool:
    """Check if domains match per AdCP rules.

    Rules:
    - Exact match always succeeds
    - 'example.com' matches www.example.com, m.example.com (common subdomains)
    - 'subdomain.example.com' matches that specific subdomain only
    - '*.example.com' matches all subdomains

    Args:
        property_domain: Domain from property
        agent_domain_pattern: Domain pattern from adagents.json

    Returns:
        True if domains match per AdCP rules
    """
    # Normalize both domains for comparison
    try:
        property_domain = _normalize_domain(property_domain)
        agent_domain_pattern = _normalize_domain(agent_domain_pattern)
    except AdagentsValidationError:
        # Invalid domain format - no match
        return False

    # Exact match
    if property_domain == agent_domain_pattern:
        return True

    # Wildcard pattern (*.example.com)
    if agent_domain_pattern.startswith("*."):
        base_domain = agent_domain_pattern[2:]
        return property_domain.endswith(f".{base_domain}")

    # Bare domain matches common subdomains (www, m)
    # If agent pattern is a bare domain (no subdomain), match www/m subdomains
    if "." in agent_domain_pattern and not agent_domain_pattern.startswith("www."):
        # Check if this looks like a bare domain (e.g., example.com)
        parts = agent_domain_pattern.split(".")
        if len(parts) == 2:  # Looks like bare domain
            common_subdomains = ["www", "m"]
            for subdomain in common_subdomains:
                if property_domain == f"{subdomain}.{agent_domain_pattern}":
                    return True

    return False

Check if domains match per AdCP rules.

Rules: - Exact match always succeeds - 'example.com' matches www.example.com, m.example.com (common subdomains) - 'subdomain.example.com' matches that specific subdomain only - '*.example.com' matches all subdomains

Args

property_domain
Domain from property
agent_domain_pattern
Domain pattern from adagents.json

Returns

True if domains match per AdCP rules

def extract_webhook_result_data(webhook_payload: dict[str, Any]) ‑> AdcpAsyncResponseData | None
Expand source code
def extract_webhook_result_data(webhook_payload: dict[str, Any]) -> AdcpAsyncResponseData | None:
    """
    Extract result data from webhook payload (MCP or A2A format).

    This utility function handles webhook payloads from both MCP and A2A protocols,
    extracting the result data regardless of the webhook format. Useful for quick
    inspection, logging, or custom webhook routing logic without requiring full
    client initialization.

    Protocol Detection:
    - A2A Task: Has "artifacts" field (terminated statuses: completed, failed)
    - A2A TaskStatusUpdateEvent: Has nested "status.message" structure (intermediate statuses)
    - MCP: Has "result" field directly

    Args:
        webhook_payload: Raw webhook dictionary from HTTP request (JSON-deserialized)

    Returns:
        AdcpAsyncResponseData union type containing the extracted AdCP response, or None
        if no result present. For A2A webhooks, unwraps data from artifacts/message parts
        structure. For MCP webhooks, returns the result field directly.

    Examples:
        Extract from MCP webhook:
        >>> mcp_payload = {
        ...     "task_id": "task_123",
        ...     "task_type": "create_media_buy",
        ...     "status": "completed",
        ...     "timestamp": "2025-01-15T10:00:00Z",
        ...     "result": {"media_buy_id": "mb_123", "buyer_ref": "ref_123", "packages": []}
        ... }
        >>> result = extract_webhook_result_data(mcp_payload)
        >>> print(result["media_buy_id"])
        mb_123

        Extract from A2A Task webhook:
        >>> a2a_task_payload = {
        ...     "id": "task_456",
        ...     "context_id": "ctx_456",
        ...     "status": {"state": "completed", "timestamp": "2025-01-15T10:00:00Z"},
        ...     "artifacts": [
        ...         {
        ...             "artifact_id": "artifact_456",
        ...             "parts": [
        ...                 {
        ...                     "data": {
        ...                         "media_buy_id": "mb_456",
        ...                         "buyer_ref": "ref_456",
        ...                         "packages": []
        ...                     }
        ...                 }
        ...             ]
        ...         }
        ...     ]
        ... }
        >>> result = extract_webhook_result_data(a2a_task_payload)
        >>> print(result["media_buy_id"])
        mb_456

        Extract from A2A TaskStatusUpdateEvent webhook:
        >>> a2a_event_payload = {
        ...     "task_id": "task_789",
        ...     "context_id": "ctx_789",
        ...     "status": {
        ...         "state": "working",
        ...         "timestamp": "2025-01-15T10:00:00Z",
        ...         "message": {
        ...             "message_id": "msg_789",
        ...             "role": "agent",
        ...             "parts": [
        ...                 {"data": {"current_step": "processing", "percentage": 50}}
        ...             ]
        ...         }
        ...     },
        ...     "final": False
        ... }
        >>> result = extract_webhook_result_data(a2a_event_payload)
        >>> print(result["percentage"])
        50

        Handle webhook with no result:
        >>> empty_payload = {"task_id": "task_000", "status": "working", "timestamp": "..."}
        >>> result = extract_webhook_result_data(empty_payload)
        >>> print(result)
        None
    """
    # Detect A2A Task format (has "artifacts" field)
    if "artifacts" in webhook_payload:
        # Extract from task.artifacts[].parts[]
        artifacts = webhook_payload.get("artifacts", [])
        if not artifacts:
            return None

        # Use last artifact (most recent)
        target_artifact = artifacts[-1]
        parts = target_artifact.get("parts", [])
        if not parts:
            return None

        # Find DataPart (skip TextPart)
        for part in parts:
            # Check if this part has "data" field (DataPart)
            if "data" in part:
                data = part["data"]
                # Unwrap {"response": {...}} wrapper if present (A2A convention)
                if isinstance(data, dict) and "response" in data and len(data) == 1:
                    return cast(AdcpAsyncResponseData, data["response"])
                return cast(AdcpAsyncResponseData, data)

        return None

    # Detect A2A TaskStatusUpdateEvent format (has nested "status.message")
    status = webhook_payload.get("status")
    if isinstance(status, dict):
        message = status.get("message")
        if isinstance(message, dict):
            # Extract from status.message.parts[]
            parts = message.get("parts", [])
            if not parts:
                return None

            # Find DataPart
            for part in parts:
                if "data" in part:
                    data = part["data"]
                    # Unwrap {"response": {...}} wrapper if present
                    if isinstance(data, dict) and "response" in data and len(data) == 1:
                        return cast(AdcpAsyncResponseData, data["response"])
                    return cast(AdcpAsyncResponseData, data)

            return None

    # MCP format: result field directly
    return cast(AdcpAsyncResponseData | None, webhook_payload.get("result"))

Extract result data from webhook payload (MCP or A2A format).

This utility function handles webhook payloads from both MCP and A2A protocols, extracting the result data regardless of the webhook format. Useful for quick inspection, logging, or custom webhook routing logic without requiring full client initialization.

Protocol Detection: - A2A Task: Has "artifacts" field (terminated statuses: completed, failed) - A2A TaskStatusUpdateEvent: Has nested "status.message" structure (intermediate statuses) - MCP: Has "result" field directly

Args

webhook_payload
Raw webhook dictionary from HTTP request (JSON-deserialized)

Returns

AdcpAsyncResponseData union type containing the extracted AdCP response, or None if no result present. For A2A webhooks, unwraps data from artifacts/message parts structure. For MCP webhooks, returns the result field directly.

Examples

Extract from MCP webhook:

>>> mcp_payload = {
...     "task_id": "task_123",
...     "task_type": "create_media_buy",
...     "status": "completed",
...     "timestamp": "2025-01-15T10:00:00Z",
...     "result": {"media_buy_id": "mb_123", "buyer_ref": "ref_123", "packages": []}
... }
>>> result = extract_webhook_result_data(mcp_payload)
>>> print(result["media_buy_id"])
mb_123

Extract from A2A Task webhook:

>>> a2a_task_payload = {
...     "id": "task_456",
...     "context_id": "ctx_456",
...     "status": {"state": "completed", "timestamp": "2025-01-15T10:00:00Z"},
...     "artifacts": [
...         {
...             "artifact_id": "artifact_456",
...             "parts": [
...                 {
...                     "data": {
...                         "media_buy_id": "mb_456",
...                         "buyer_ref": "ref_456",
...                         "packages": []
...                     }
...                 }
...             ]
...         }
...     ]
... }
>>> result = extract_webhook_result_data(a2a_task_payload)
>>> print(result["media_buy_id"])
mb_456

Extract from A2A TaskStatusUpdateEvent webhook:

>>> a2a_event_payload = {
...     "task_id": "task_789",
...     "context_id": "ctx_789",
...     "status": {
...         "state": "working",
...         "timestamp": "2025-01-15T10:00:00Z",
...         "message": {
...             "message_id": "msg_789",
...             "role": "agent",
...             "parts": [
...                 {"data": {"current_step": "processing", "percentage": 50}}
...             ]
...         }
...     },
...     "final": False
... }
>>> result = extract_webhook_result_data(a2a_event_payload)
>>> print(result["percentage"])
50

Handle webhook with no result:

>>> empty_payload = {"task_id": "task_000", "status": "working", "timestamp": "..."}
>>> result = extract_webhook_result_data(empty_payload)
>>> print(result)
None
async def fetch_adagents(publisher_domain: str,
timeout: float = 10.0,
user_agent: str = 'AdCP-Client/1.0',
client: httpx.AsyncClient | None = None) ‑> dict[str, typing.Any]
Expand source code
async def fetch_adagents(
    publisher_domain: str,
    timeout: float = 10.0,
    user_agent: str = "AdCP-Client/1.0",
    client: httpx.AsyncClient | None = None,
) -> dict[str, Any]:
    """Fetch and parse adagents.json from publisher domain.

    Follows authoritative_location redirects per the AdCP specification. When a
    publisher's adagents.json contains an authoritative_location field instead of
    authorized_agents, this function fetches the referenced URL to get the actual
    authorization data.

    Args:
        publisher_domain: Domain hosting the adagents.json file
        timeout: Request timeout in seconds
        user_agent: User-Agent header for HTTP request
        client: Optional httpx.AsyncClient for connection pooling.
            If provided, caller is responsible for client lifecycle.
            If None, a new client is created for this request.

    Returns:
        Parsed adagents.json data (resolved from authoritative_location if present)

    Raises:
        AdagentsNotFoundError: If adagents.json not found (404)
        AdagentsValidationError: If JSON is invalid, malformed, or redirects
            exceed maximum depth or form a loop
        AdagentsTimeoutError: If request times out

    Notes:
        For production use with multiple requests, pass a shared httpx.AsyncClient
        to enable connection pooling and improve performance.
    """
    # Validate and normalize domain for security
    publisher_domain = _validate_publisher_domain(publisher_domain)

    # Construct initial URL
    url = f"https://{publisher_domain}/.well-known/adagents.json"

    # Track visited URLs to detect loops
    visited_urls: set[str] = set()

    is_redirect = False

    for depth in range(MAX_REDIRECT_DEPTH + 1):
        # Check for redirect loop
        if url in visited_urls:
            raise AdagentsValidationError(
                "Circular redirect detected in authoritative_location chain"
            )
        visited_urls.add(url)

        # Use the caller's client for the initial fetch only. Redirect targets
        # use a fresh client to avoid leaking credentials to third-party URLs.
        fetch_client = None if is_redirect else client

        data = await _fetch_adagents_url(url, timeout, user_agent, fetch_client)

        # Check if this is a redirect. A response with authoritative_location but no
        # authorized_agents indicates a redirect. If both are present, authorized_agents
        # takes precedence (response is treated as final).
        if "authoritative_location" in data and "authorized_agents" not in data:
            authoritative_url = data["authoritative_location"]

            # Validate HTTPS requirement
            if not isinstance(authoritative_url, str) or not authoritative_url.startswith(
                "https://"
            ):
                raise AdagentsValidationError(
                    f"authoritative_location must be an HTTPS URL, got: {authoritative_url!r}"
                )

            # Validate the redirect target is not a private/reserved address
            _validate_redirect_url(authoritative_url)

            # Check if we've exceeded max depth
            if depth >= MAX_REDIRECT_DEPTH:
                raise AdagentsValidationError(
                    f"Maximum redirect depth ({MAX_REDIRECT_DEPTH}) exceeded"
                )

            # Follow the redirect
            url = authoritative_url
            is_redirect = True
            continue

        # We have the final data with authorized_agents (or both fields present,
        # in which case authorized_agents takes precedence)
        return data

    # Unreachable: loop always exits via return or raise above
    raise AssertionError("Unreachable")  # pragma: no cover

Fetch and parse adagents.json from publisher domain.

Follows authoritative_location redirects per the AdCP specification. When a publisher's adagents.json contains an authoritative_location field instead of authorized_agents, this function fetches the referenced URL to get the actual authorization data.

Args

publisher_domain
Domain hosting the adagents.json file
timeout
Request timeout in seconds
user_agent
User-Agent header for HTTP request
client
Optional httpx.AsyncClient for connection pooling. If provided, caller is responsible for client lifecycle. If None, a new client is created for this request.

Returns

Parsed adagents.json data (resolved from authoritative_location if present)

Raises

AdagentsNotFoundError
If adagents.json not found (404)
AdagentsValidationError
If JSON is invalid, malformed, or redirects exceed maximum depth or form a loop
AdagentsTimeoutError
If request times out

Notes

For production use with multiple requests, pass a shared httpx.AsyncClient to enable connection pooling and improve performance.

async def fetch_agent_authorizations(agent_url: str,
publisher_domains: list[str],
timeout: float = 10.0,
client: httpx.AsyncClient | None = None) ‑> dict[str, AuthorizationContext]
Expand source code
async def fetch_agent_authorizations(
    agent_url: str,
    publisher_domains: list[str],
    timeout: float = 10.0,
    client: httpx.AsyncClient | None = None,
) -> dict[str, AuthorizationContext]:
    """Fetch authorization contexts by checking publisher adagents.json files.

    This function discovers what publishers have authorized your agent by fetching
    their adagents.json files from the .well-known directory and extracting the
    properties your agent can access.

    This is the "pull" approach - you query publishers to see if they've authorized you.

    Args:
        agent_url: URL of your sales agent
        publisher_domains: List of publisher domains to check (e.g., ["nytimes.com", "wsj.com"])
        timeout: Request timeout in seconds for each fetch
        client: Optional httpx.AsyncClient for connection pooling

    Returns:
        Dictionary mapping publisher domain to AuthorizationContext.
        Only includes domains where the agent is authorized.

    Example:
        >>> # "Pull" approach - check what publishers have authorized you
        >>> contexts = await fetch_agent_authorizations(
        ...     "https://our-sales-agent.com",
        ...     ["nytimes.com", "wsj.com", "cnn.com"]
        ... )
        >>> for domain, ctx in contexts.items():
        ...     print(f"{domain}:")
        ...     print(f"  Property IDs: {ctx.property_ids}")
        ...     print(f"  Tags: {ctx.property_tags}")

    Notes:
        - Silently skips domains where adagents.json is not found or invalid
        - Only returns domains where the agent is explicitly authorized
        - For production use with many domains, pass a shared httpx.AsyncClient
          to enable connection pooling
    """
    import asyncio

    # Create tasks to fetch all adagents.json files in parallel
    async def fetch_authorization_for_domain(
        domain: str,
    ) -> tuple[str, AuthorizationContext | None]:
        """Fetch authorization context for a single domain."""
        try:
            adagents_data = await fetch_adagents(domain, timeout=timeout, client=client)

            # Check if agent is authorized
            if not verify_agent_authorization(adagents_data, agent_url):
                return (domain, None)

            # Get properties for this agent
            properties = get_properties_by_agent(adagents_data, agent_url)

            # Create authorization context
            return (domain, AuthorizationContext(properties))

        except (AdagentsNotFoundError, AdagentsValidationError, AdagentsTimeoutError):
            # Silently skip domains with missing or invalid adagents.json
            return (domain, None)

    # Fetch all domains in parallel
    tasks = [fetch_authorization_for_domain(domain) for domain in publisher_domains]
    results = await asyncio.gather(*tasks)

    # Build result dictionary, filtering out None values
    return {domain: ctx for domain, ctx in results if ctx is not None}

Fetch authorization contexts by checking publisher adagents.json files.

This function discovers what publishers have authorized your agent by fetching their adagents.json files from the .well-known directory and extracting the properties your agent can access.

This is the "pull" approach - you query publishers to see if they've authorized you.

Args

agent_url
URL of your sales agent
publisher_domains
List of publisher domains to check (e.g., ["nytimes.com", "wsj.com"])
timeout
Request timeout in seconds for each fetch
client
Optional httpx.AsyncClient for connection pooling

Returns

Dictionary mapping publisher domain to AuthorizationContext. Only includes domains where the agent is authorized.

Example

>>> # "Pull" approach - check what publishers have authorized you
>>> contexts = await fetch_agent_authorizations(
...     "https://our-sales-agent.com",
...     ["nytimes.com", "wsj.com", "cnn.com"]
... )
>>> for domain, ctx in contexts.items():
...     print(f"{domain}:")
...     print(f"  Property IDs: {ctx.property_ids}")
...     print(f"  Tags: {ctx.property_tags}")

Notes

  • Silently skips domains where adagents.json is not found or invalid
  • Only returns domains where the agent is explicitly authorized
  • For production use with many domains, pass a shared httpx.AsyncClient to enable connection pooling
def generate_webhook_idempotency_key() ‑> str
Expand source code
def generate_webhook_idempotency_key() -> str:
    """Generate a cryptographically random idempotency_key for a webhook event.

    Returns a UUID v4 prefixed with ``whk_`` — matches the example format in
    ``webhooks.mdx`` and stays within the spec's length + charset bounds
    (``^[A-Za-z0-9_.:-]{16,255}$``).

    Publishers MUST generate this once per distinct event and reuse the same
    value when retrying delivery. Do NOT call this function again on retry —
    it would mint a fresh UUID and defeat the dedup contract.
    """
    return f"whk_{uuid.uuid4()}"

Generate a cryptographically random idempotency_key for a webhook event.

Returns a UUID v4 prefixed with whk_ — matches the example format in webhooks.mdx and stays within the spec's length + charset bounds (^[A-Za-z0-9_.:-]{16,255}$).

Publishers MUST generate this once per distinct event and reuse the same value when retrying delivery. Do NOT call this function again on retry — it would mint a fresh UUID and defeat the dedup contract.

def get_adcp_signed_headers_for_webhook(headers: dict[str, Any],
secret: str,
timestamp: str | int | None,
payload: dict[str, Any] | AdCPBaseModel) ‑> dict[str, typing.Any]
Expand source code
def get_adcp_signed_headers_for_webhook(
    headers: dict[str, Any],
    secret: str,
    timestamp: str | int | None,
    payload: dict[str, Any] | AdCPBaseModel,
) -> dict[str, Any]:
    """
    Generate AdCP-compliant signed headers for webhook delivery.

    This function creates a cryptographic signature that proves the webhook
    came from an authorized agent and protects against replay attacks by
    including a timestamp in the signed message.

    The function adds two headers to the provided headers dict:
    - X-AdCP-Signature: HMAC-SHA256 signature in format "sha256=<hex_digest>"
    - X-AdCP-Timestamp: Unix timestamp in seconds

    The signing algorithm:
    1. Constructs message as "{timestamp}.{json_payload}"
    2. JSON-serializes payload with default separators (matches wire format from json= kwarg)
    3. UTF-8 encodes the message
    4. HMAC-SHA256 signs with the shared secret
    5. Hex-encodes and prefixes with "sha256="

    Args:
        headers: Existing headers dictionary to add signature headers to
        secret: Shared secret key for HMAC signing
        timestamp: Unix timestamp in seconds (str or int). If None, uses current time.
        payload: Webhook payload (dict or Pydantic model - will be JSON-serialized)

    Returns:
        The modified headers dictionary with signature headers added

    Examples:
        Sign and send an MCP webhook:
        >>> import time
        >>> from adcp.webhooks import create_mcp_webhook_payload
        >>> from adcp.webhooks import get_adcp_signed_headers_for_webhook
        >>>
        >>> payload = create_mcp_webhook_payload(
        ...     task_id="task_123",
        ...     task_type="get_products",
        ...     status="completed",
        ...     result={"products": [...]}
        ... )
        >>> headers = {"Content-Type": "application/json"}
        >>> signed_headers = get_adcp_signed_headers_for_webhook(
        ...     headers, secret="my-webhook-secret", timestamp=str(int(time.time())),
        ...     payload=payload,
        ... )
        >>>
        >>> # Send webhook with signed headers
        >>> import httpx
        >>> response = await httpx.post(
        ...     webhook_url,
        ...     json=payload,
        ...     headers=signed_headers
        ... )

        Headers will contain:
        >>> print(signed_headers)
        {
            "Content-Type": "application/json",
            "X-AdCP-Signature": "sha256=a1b2c3...",
            "X-AdCP-Timestamp": "1773185740"
        }
    """
    signature_headers, _body_bytes = _compute_legacy_signature(
        secret=secret, timestamp=timestamp, payload=payload
    )
    headers.update(signature_headers)
    return headers

Generate AdCP-compliant signed headers for webhook delivery.

This function creates a cryptographic signature that proves the webhook came from an authorized agent and protects against replay attacks by including a timestamp in the signed message.

The function adds two headers to the provided headers dict: - X-AdCP-Signature: HMAC-SHA256 signature in format "sha256=" - X-AdCP-Timestamp: Unix timestamp in seconds

The signing algorithm: 1. Constructs message as "{timestamp}.{json_payload}" 2. JSON-serializes payload with default separators (matches wire format from json= kwarg) 3. UTF-8 encodes the message 4. HMAC-SHA256 signs with the shared secret 5. Hex-encodes and prefixes with "sha256="

Args

headers
Existing headers dictionary to add signature headers to
secret
Shared secret key for HMAC signing
timestamp
Unix timestamp in seconds (str or int). If None, uses current time.
payload
Webhook payload (dict or Pydantic model - will be JSON-serialized)

Returns

The modified headers dictionary with signature headers added

Examples

Sign and send an MCP webhook:

>>> import time
>>> from adcp.webhooks import create_mcp_webhook_payload
>>> from adcp.webhooks import get_adcp_signed_headers_for_webhook
>>>
>>> payload = create_mcp_webhook_payload(
...     task_id="task_123",
...     task_type="get_products",
...     status="completed",
...     result={"products": [...]}
... )
>>> headers = {"Content-Type": "application/json"}
>>> signed_headers = get_adcp_signed_headers_for_webhook(
...     headers, secret="my-webhook-secret", timestamp=str(int(time.time())),
...     payload=payload,
... )
>>>
>>> # Send webhook with signed headers
>>> import httpx
>>> response = await httpx.post(
...     webhook_url,
...     json=payload,
...     headers=signed_headers
... )

Headers will contain:

>>> print(signed_headers)
{
    "Content-Type": "application/json",
    "X-AdCP-Signature": "sha256=a1b2c3...",
    "X-AdCP-Timestamp": "1773185740"
}
def get_adcp_version() ‑> str
Expand source code
def get_adcp_version() -> str:
    """
    Get the target AdCP specification version this SDK is built for.

    This version determines which AdCP schemas are used for type generation
    and validation. The SDK is designed to work with this specific version
    of the AdCP specification.

    Returns:
        AdCP specification version (e.g., "2.5.0")

    Raises:
        FileNotFoundError: If ADCP_VERSION file is missing from package
    """
    from importlib.resources import files

    # Read from ADCP_VERSION file in package
    version_file = files("adcp") / "ADCP_VERSION"
    return version_file.read_text().strip()

Get the target AdCP specification version this SDK is built for.

This version determines which AdCP schemas are used for type generation and validation. The SDK is designed to work with this specific version of the AdCP specification.

Returns

AdCP specification version (e.g., "2.5.0")

Raises

FileNotFoundError
If ADCP_VERSION file is missing from package
def get_all_properties(adagents_data: dict[str, Any]) ‑> list[dict[str, typing.Any]]
Expand source code
def get_all_properties(adagents_data: dict[str, Any]) -> list[dict[str, Any]]:
    """Extract all properties from adagents.json data.

    Handles all authorization types: inline_properties, property_ids,
    property_tags, and publisher_properties.

    Args:
        adagents_data: Parsed adagents.json data

    Returns:
        List of all properties across all authorized agents, with agent_url added

    Raises:
        AdagentsValidationError: If adagents_data is malformed
    """
    if not isinstance(adagents_data, dict):
        raise AdagentsValidationError("adagents_data must be a dictionary")

    authorized_agents = adagents_data.get("authorized_agents")
    if not isinstance(authorized_agents, list):
        raise AdagentsValidationError("adagents.json must have 'authorized_agents' array")

    top_level_properties = adagents_data.get("properties", [])
    if not isinstance(top_level_properties, list):
        top_level_properties = []

    properties = []
    for agent in authorized_agents:
        if not isinstance(agent, dict):
            continue

        agent_url = agent.get("url", "")
        if not agent_url:
            continue

        agent_properties = _resolve_agent_properties(agent, top_level_properties)

        for prop in agent_properties:
            prop_with_agent = {**prop, "agent_url": agent_url}
            properties.append(prop_with_agent)

    return properties

Extract all properties from adagents.json data.

Handles all authorization types: inline_properties, property_ids, property_tags, and publisher_properties.

Args

adagents_data
Parsed adagents.json data

Returns

List of all properties across all authorized agents, with agent_url added

Raises

AdagentsValidationError
If adagents_data is malformed
def get_all_tags(adagents_data: dict[str, Any]) ‑> set[str]
Expand source code
def get_all_tags(adagents_data: dict[str, Any]) -> set[str]:
    """Extract all unique tags from properties in adagents.json data.

    Args:
        adagents_data: Parsed adagents.json data

    Returns:
        Set of all unique tags across all properties

    Raises:
        AdagentsValidationError: If adagents_data is malformed
    """
    properties = get_all_properties(adagents_data)
    tags = set()

    for prop in properties:
        prop_tags = prop.get("tags", [])
        if isinstance(prop_tags, list):
            for tag in prop_tags:
                if isinstance(tag, str):
                    tags.add(tag)

    return tags

Extract all unique tags from properties in adagents.json data.

Args

adagents_data
Parsed adagents.json data

Returns

Set of all unique tags across all properties

Raises

AdagentsValidationError
If adagents_data is malformed
def get_asset_count(format: Format) ‑> int
Expand source code
def get_asset_count(format: Format) -> int:
    """Get the count of assets in a format (for display purposes).

    Args:
        format: The Format object

    Returns:
        Number of assets, or 0 if none defined
    """
    return len(get_format_assets(format))

Get the count of assets in a format (for display purposes).

Args

format
The Format object

Returns

Number of assets, or 0 if none defined

def get_format_assets(format: Format) ‑> list[FormatAsset]
Expand source code
def get_format_assets(format: Format) -> list[FormatAsset]:
    """Get assets from a Format.

    Returns the list of assets from the format's `assets` field.
    Returns empty list if no assets are defined (flexible format with no assets).

    Args:
        format: The Format object from list_creative_formats response

    Returns:
        List of assets

    Example:
        ```python
        formats = await agent.simple.list_creative_formats()
        for format in formats.formats:
            assets = get_format_assets(format)
            print(f"{format.name} has {len(assets)} assets")
        ```
    """
    if format.assets and len(format.assets) > 0:
        return list(format.assets)

    return []

Get assets from a Format.

Returns the list of assets from the format's assets field. Returns empty list if no assets are defined (flexible format with no assets).

Args

format
The Format object from list_creative_formats response

Returns

List of assets

Example

formats = await agent.simple.list_creative_formats()
for format in formats.formats:
    assets = get_format_assets(format)
    print(f"{format.name} has {len(assets)} assets")
def get_individual_assets(format: Format) ‑> list[FormatAsset]
Expand source code
def get_individual_assets(format: Format) -> list[FormatAsset]:
    """Get individual assets (not repeatable groups) from a Format.

    Args:
        format: The Format object

    Returns:
        List of individual assets (item_type='individual')
    """
    return [asset for asset in get_format_assets(format) if _get_item_type(asset) == "individual"]

Get individual assets (not repeatable groups) from a Format.

Args

format
The Format object

Returns

List of individual assets (item_type='individual')

def get_optional_assets(format: Format) ‑> list[FormatAsset]
Expand source code
def get_optional_assets(format: Format) -> list[FormatAsset]:
    """Get only optional assets from a Format.

    Note: When using deprecated `assets_required`, this will always return empty
    since assets_required only contained required assets.

    Args:
        format: The Format object

    Returns:
        List of optional assets only

    Example:
        ```python
        optional_assets = get_optional_assets(format)
        print(f"Can optionally provide {len(optional_assets)} additional assets")
        ```
    """
    return [asset for asset in get_format_assets(format) if not _is_required(asset)]

Get only optional assets from a Format.

Note: When using deprecated assets_required, this will always return empty since assets_required only contained required assets.

Args

format
The Format object

Returns

List of optional assets only

Example

optional_assets = get_optional_assets(format)
print(f"Can optionally provide {len(optional_assets)} additional assets")
def get_properties_by_agent(adagents_data: dict[str, Any], agent_url: str) ‑> list[dict[str, typing.Any]]
Expand source code
def get_properties_by_agent(adagents_data: dict[str, Any], agent_url: str) -> list[dict[str, Any]]:
    """Get all properties authorized for a specific agent.

    Handles all authorization types per the AdCP specification:
    - inline_properties: Properties defined directly in the agent's properties array
    - property_ids: Filter top-level properties by property_id
    - property_tags: Filter top-level properties by tags
    - publisher_properties: References properties from other publisher domains
      (returns the selector objects, not resolved properties)

    Args:
        adagents_data: Parsed adagents.json data
        agent_url: URL of the agent to filter by

    Returns:
        List of properties for the specified agent (empty if agent not found)

    Raises:
        AdagentsValidationError: If adagents_data is malformed
    """
    if not isinstance(adagents_data, dict):
        raise AdagentsValidationError("adagents_data must be a dictionary")

    authorized_agents = adagents_data.get("authorized_agents")
    if not isinstance(authorized_agents, list):
        raise AdagentsValidationError("adagents.json must have 'authorized_agents' array")

    top_level_properties = adagents_data.get("properties", [])
    if not isinstance(top_level_properties, list):
        top_level_properties = []

    normalized_agent_url = normalize_url(agent_url)

    for agent in authorized_agents:
        if not isinstance(agent, dict):
            continue

        agent_url_from_json = agent.get("url", "")
        if not agent_url_from_json:
            continue

        if normalize_url(agent_url_from_json) != normalized_agent_url:
            continue

        return _resolve_agent_properties(agent, top_level_properties)

    return []

Get all properties authorized for a specific agent.

Handles all authorization types per the AdCP specification: - inline_properties: Properties defined directly in the agent's properties array - property_ids: Filter top-level properties by property_id - property_tags: Filter top-level properties by tags - publisher_properties: References properties from other publisher domains (returns the selector objects, not resolved properties)

Args

adagents_data
Parsed adagents.json data
agent_url
URL of the agent to filter by

Returns

List of properties for the specified agent (empty if agent not found)

Raises

AdagentsValidationError
If adagents_data is malformed
def get_repeatable_groups(format: Format) ‑> list[FormatAsset]
Expand source code
def get_repeatable_groups(format: Format) -> list[FormatAsset]:
    """Get repeatable asset groups from a Format.

    Args:
        format: The Format object

    Returns:
        List of repeatable asset groups (item_type='repeatable_group')
    """
    return [
        asset for asset in get_format_assets(format) if _get_item_type(asset) == "repeatable_group"
    ]

Get repeatable asset groups from a Format.

Args

format
The Format object

Returns

List of repeatable asset groups (item_type='repeatable_group')

def get_required_assets(format: Format) ‑> list[FormatAsset]
Expand source code
def get_required_assets(format: Format) -> list[FormatAsset]:
    """Get only required assets from a Format.

    Args:
        format: The Format object

    Returns:
        List of required assets only

    Example:
        ```python
        required_assets = get_required_assets(format)
        print(f"Must provide {len(required_assets)} assets")
        ```
    """
    return [asset for asset in get_format_assets(format) if _is_required(asset)]

Get only required assets from a Format.

Args

format
The Format object

Returns

List of required assets only

Example

required_assets = get_required_assets(format)
print(f"Must provide {len(required_assets)} assets")
def has_assets(format: Format) ‑> bool
Expand source code
def has_assets(format: Format) -> bool:
    """Check if a format has any assets defined.

    Args:
        format: The Format object

    Returns:
        True if format has assets, False otherwise
    """
    return get_asset_count(format) > 0

Check if a format has any assets defined.

Args

format
The Format object

Returns

True if format has assets, False otherwise

def identifiers_match(property_identifiers: list[dict[str, str]],
agent_identifiers: list[dict[str, str]]) ‑> bool
Expand source code
def identifiers_match(
    property_identifiers: list[dict[str, str]],
    agent_identifiers: list[dict[str, str]],
) -> bool:
    """Check if any property identifier matches agent's authorized identifiers.

    Args:
        property_identifiers: Identifiers from property
            (e.g., [{"type": "domain", "value": "cnn.com"}])
        agent_identifiers: Identifiers from adagents.json

    Returns:
        True if any identifier matches

    Notes:
        - Domain identifiers use AdCP domain matching rules
        - Other identifiers (bundle_id, roku_store_id, etc.) require exact match
    """
    for prop_id in property_identifiers:
        prop_type = prop_id.get("type", "")
        prop_value = prop_id.get("value", "")

        for agent_id in agent_identifiers:
            agent_type = agent_id.get("type", "")
            agent_value = agent_id.get("value", "")

            # Type must match
            if prop_type != agent_type:
                continue

            # Domain identifiers use special matching rules
            if prop_type == "domain":
                if domain_matches(prop_value, agent_value):
                    return True
            else:
                # Other identifier types require exact match
                if prop_value == agent_value:
                    return True

    return False

Check if any property identifier matches agent's authorized identifiers.

Args

property_identifiers
Identifiers from property (e.g., [{"type": "domain", "value": "cnn.com"}])
agent_identifiers
Identifiers from adagents.json

Returns

True if any identifier matches

Notes

  • Domain identifiers use AdCP domain matching rules
  • Other identifiers (bundle_id, roku_store_id, etc.) require exact match
def normalize_assets_required(assets_required: list[Any]) ‑> list[typing.Any]
Expand source code
def normalize_assets_required(assets_required: list[Any]) -> list[FormatAsset]:
    """Convert deprecated assets_required to new assets format.

    .. deprecated:: 3.2.0
        The ``assets_required`` field was removed in ADCP 3.0.0-beta.2.
        This function will be removed in a future version.

    All assets in assets_required are required by definition (that's why they were in
    that array). The new `assets` field has an explicit `required: boolean` to allow
    both required AND optional assets.

    Args:
        assets_required: The deprecated assets_required array

    Returns:
        Normalized assets as Pydantic models with explicit required=True
    """
    warnings.warn(
        "normalize_assets_required() is deprecated. "
        "The assets_required field was removed in ADCP 3.0.0-beta.2. "
        "This function will be removed in a future version.",
        DeprecationWarning,
        stacklevel=2,
    )
    normalized: list[FormatAsset] = []
    for asset in assets_required:
        # Get asset data as dict
        if isinstance(asset, dict):
            asset_dict = asset
        else:
            asset_dict = asset.model_dump() if hasattr(asset, "model_dump") else dict(asset)

        # Map old fields to new schema format
        mapped = {**asset_dict, "required": True}
        # Ensure asset_id is present (map from asset_group_id if needed)
        if "asset_group_id" in mapped and "asset_id" not in mapped:
            mapped["asset_id"] = mapped.pop("asset_group_id")
        # Remove fields that don't exist in the new schema
        for old_field in ("min_count", "max_count", "assets"):
            mapped.pop(old_field, None)
        # Use AssetsModel (individual asset type)
        normalized.append(AssetsModel(**mapped))

    return normalized

Convert deprecated assets_required to new assets format.

Deprecated since version: 3.2.0

The assets_required field was removed in ADCP 3.0.0-beta.2. This function will be removed in a future version.

All assets in assets_required are required by definition (that's why they were in that array). The new assets field has an explicit required: boolean to allow both required AND optional assets.

Args

assets_required
The deprecated assets_required array

Returns

Normalized assets as Pydantic models with explicit required=True

def sign_legacy_webhook(secret: str,
payload: dict[str, Any] | AdCPBaseModel,
*,
timestamp: str | int | None = None,
headers: dict[str, Any] | None = None) ‑> tuple[dict[str, str], bytes]
Expand source code
def sign_legacy_webhook(
    secret: str,
    payload: dict[str, Any] | AdCPBaseModel,
    *,
    timestamp: str | int | None = None,
    headers: dict[str, Any] | None = None,
) -> tuple[dict[str, str], bytes]:
    """Return ``(signed_headers, body_bytes)`` for a legacy HMAC webhook.

    Byte-equality between signature input and HTTP body is guaranteed —
    callers POST ``content=body_bytes`` instead of ``json=payload``, so the
    separator-drift trap that caused silent 401s in every spaced-vs-compact
    interop is structurally impossible here.

    This is a lower-level companion to :func:`deliver` for callers who need
    to own the HTTP transport themselves (custom auth, pre-configured
    ``httpx.AsyncClient``, non-httpx clients). For the one-shot "send a
    webhook" path, prefer :func:`deliver`.

    The returned ``body_bytes`` use compact separators (``","``/``":"``)
    matching the canonical on-wire form pinned by adcontextprotocol/adcp#2478.

    Example:
        >>> signed, body = sign_legacy_webhook("shared-secret", payload)
        >>> headers = {**signed, "Content-Type": "application/json"}
        >>> await client.post(url, content=body, headers=headers)
    """
    signature_headers, body_bytes = _compute_legacy_signature(
        secret=secret, timestamp=timestamp, payload=payload
    )
    if headers is not None:
        merged = {str(k): str(v) for k, v in headers.items()}
        merged.update(signature_headers)
        return merged, body_bytes
    return signature_headers, body_bytes

Return (signed_headers, body_bytes) for a legacy HMAC webhook.

Byte-equality between signature input and HTTP body is guaranteed — callers POST content=body_bytes instead of json=payload, so the separator-drift trap that caused silent 401s in every spaced-vs-compact interop is structurally impossible here.

This is a lower-level companion to :func:deliver for callers who need to own the HTTP transport themselves (custom auth, pre-configured httpx.AsyncClient, non-httpx clients). For the one-shot "send a webhook" path, prefer :func:deliver.

The returned body_bytes use compact separators (","/":") matching the canonical on-wire form pinned by adcontextprotocol/adcp#2478.

Example

>>> signed, body = sign_legacy_webhook("shared-secret", payload)
>>> headers = {**signed, "Content-Type": "application/json"}
>>> await client.post(url, content=body, headers=headers)
def sign_webhook(*,
method: str,
url: str,
headers: Mapping[str, str],
body: bytes,
private_key: PrivateKey,
key_id: str,
alg: str,
created: int | None = None,
expires_in_seconds: int = 300,
nonce: str | None = None,
label: str = 'sig1') ‑> SignedHeaders
Expand source code
def sign_webhook(
    *,
    method: str,
    url: str,
    headers: Mapping[str, str],
    body: bytes,
    private_key: PrivateKey,
    key_id: str,
    alg: str,
    created: int | None = None,
    expires_in_seconds: int = DEFAULT_EXPIRES_IN_SECONDS,
    nonce: str | None = None,
    label: str = SIG_LABEL_DEFAULT,
) -> SignedHeaders:
    """Sign an outgoing webhook POST per adcp/webhook-signing/v1.

    ``cover_content_digest=True`` and ``tag=WEBHOOK_TAG`` are pinned. The
    caller attaches ``SignedHeaders.as_dict()`` to the outgoing HTTP request.

    The ``method`` is normally ``"POST"`` for webhook delivery; passed through
    unchanged so callers signing a retried ``PUT`` or variant delivery verb
    are not forced into an extra translation.

    See also:
        :class:`adcp.webhooks.WebhookSender` — higher-level one-call helper
        that builds the payload, signs, and POSTs in a single call. Prefer it
        unless you need to own the HTTP transport yourself.
    """
    return sign_request(
        method=method,
        url=url,
        headers=headers,
        body=body,
        private_key=private_key,
        key_id=key_id,
        alg=alg,
        cover_content_digest=True,
        created=created,
        expires_in_seconds=expires_in_seconds,
        nonce=nonce,
        tag=WEBHOOK_TAG,
        label=label,
    )

Sign an outgoing webhook POST per adcp/webhook-signing/v1.

cover_content_digest=True and tag=WEBHOOK_TAG are pinned. The caller attaches SignedHeaders.as_dict() to the outgoing HTTP request.

The method is normally "POST" for webhook delivery; passed through unchanged so callers signing a retried PUT or variant delivery verb are not forced into an extra translation.

See also: :class:WebhookSender — higher-level one-call helper that builds the payload, signs, and POSTs in a single call. Prefer it unless you need to own the HTTP transport yourself.

def uses_deprecated_assets_field(format: Format) ‑> bool
Expand source code
def uses_deprecated_assets_field(format: Format) -> bool:
    """Check if format uses deprecated assets_required field.

    .. deprecated:: 3.2.0
        The ``assets_required`` field was removed in ADCP 3.0.0-beta.2.
        This function always returns False and will be removed in a future version.

    Args:
        format: The Format object

    Returns:
        Always False (deprecated field no longer exists)
    """
    warnings.warn(
        "uses_deprecated_assets_field() is deprecated and always returns False. "
        "The assets_required field was removed in ADCP 3.0.0-beta.2. "
        "This function will be removed in a future version.",
        DeprecationWarning,
        stacklevel=2,
    )
    return False

Check if format uses deprecated assets_required field.

Deprecated since version: 3.2.0

The assets_required field was removed in ADCP 3.0.0-beta.2. This function always returns False and will be removed in a future version.

Args

format
The Format object

Returns

Always False (deprecated field no longer exists)

def validate_adagents(adagents: dict[str, Any]) ‑> None
Expand source code
def validate_adagents(adagents: dict[str, Any]) -> None:
    """Validate an adagents.json structure.

    Args:
        adagents: The adagents.json dict

    Raises:
        ValidationError: If validation fails
    """
    if "agents" in adagents:
        for agent in adagents["agents"]:
            validate_agent_authorization(agent)

Validate an adagents.json structure.

Args

adagents
The adagents.json dict

Raises

ValidationError
If validation fails
def validate_agent_authorization(agent: dict[str, Any]) ‑> None
Expand source code
def validate_agent_authorization(agent: dict[str, Any]) -> None:
    """Validate agent authorization discriminated union.

    AdCP v2.4.0+ uses discriminated unions with authorization_type discriminator:
    - authorization_type: "property_ids" requires property_ids
    - authorization_type: "property_tags" requires property_tags
    - authorization_type: "inline_properties" requires properties
    - authorization_type: "publisher_properties" requires publisher_properties

    For backward compatibility, also validates the old mutual exclusivity constraint.

    Args:
        agent: An agent dict from adagents.json

    Raises:
        ValidationError: If discriminator or field constraints are violated
    """
    authorization_type = agent.get("authorization_type")
    auth_fields = ["properties", "property_ids", "property_tags", "publisher_properties"]
    present_fields = [field for field in auth_fields if field in agent and agent[field] is not None]

    # If authorization_type discriminator is present, validate discriminated union
    if authorization_type:
        if authorization_type == "property_ids" and "property_ids" not in present_fields:
            raise ValidationError(
                "Agent with authorization_type='property_ids' must have property_ids"
            )
        elif authorization_type == "property_tags" and "property_tags" not in present_fields:
            raise ValidationError(
                "Agent with authorization_type='property_tags' must have property_tags"
            )
        elif authorization_type == "inline_properties" and "properties" not in present_fields:
            raise ValidationError(
                "Agent with authorization_type='inline_properties' must have properties"
            )
        elif (
            authorization_type == "publisher_properties"
            and "publisher_properties" not in present_fields
        ):
            raise ValidationError(
                "Agent with authorization_type='publisher_properties' "
                "must have publisher_properties"
            )
        elif authorization_type not in (
            "property_ids",
            "property_tags",
            "inline_properties",
            "publisher_properties",
        ):
            raise ValidationError(f"Agent has invalid authorization_type: {authorization_type}")

    # Validate mutual exclusivity (for both old and new formats)
    if len(present_fields) > 1:
        raise ValidationError(
            f"Agent authorization cannot have multiple fields: {', '.join(present_fields)}. "
            f"Only one of {', '.join(auth_fields)} is allowed."
        )

    if len(present_fields) == 0:
        raise ValidationError(
            f"Agent authorization must have exactly one of: {', '.join(auth_fields)}."
        )

    # If using publisher_properties, validate each item
    if "publisher_properties" in present_fields:
        for pub_prop in agent["publisher_properties"]:
            validate_publisher_properties_item(pub_prop)

Validate agent authorization discriminated union.

AdCP v2.4.0+ uses discriminated unions with authorization_type discriminator: - authorization_type: "property_ids" requires property_ids - authorization_type: "property_tags" requires property_tags - authorization_type: "inline_properties" requires properties - authorization_type: "publisher_properties" requires publisher_properties

For backward compatibility, also validates the old mutual exclusivity constraint.

Args

agent
An agent dict from adagents.json

Raises

ValidationError
If discriminator or field constraints are violated
def validate_capabilities(handler: Any, capabilities: GetAdcpCapabilitiesResponse) ‑> list[str]
Expand source code
def validate_capabilities(
    handler: Any,
    capabilities: GetAdcpCapabilitiesResponse,
) -> list[str]:
    """Check that a handler implements the methods required by its declared features.

    Compares the features declared in a capabilities response against the handler's
    method implementations. Returns warnings for features that are declared but
    whose corresponding handler methods are not overridden from the base class.

    This is a development-time check — call it at startup to catch misconfigurations.

    Args:
        handler: An ADCPHandler instance (or any object with handler methods).
        capabilities: The capabilities response the handler will serve.

    Returns:
        List of warning strings. Empty if everything is consistent.
    """
    # Late import to avoid circular dependency: server.base imports from adcp.types
    # which may transitively import from this module.
    from adcp.server.base import ADCPHandler

    resolver = FeatureResolver(capabilities)
    warnings: list[str] = []

    for feature, handler_methods in FEATURE_HANDLER_MAP.items():
        if not resolver.supports(feature):
            continue

        for method_name in handler_methods:
            if not hasattr(handler, method_name):
                warnings.append(
                    f"Feature '{feature}' is declared but handler has no "
                    f"'{method_name}' method"
                )
                continue

            # Walk MRO to check if any class between the leaf and ADCPHandler
            # overrides the method (handles mixin / intermediate-class patterns).
            if isinstance(handler, ADCPHandler):
                overridden = any(
                    method_name in cls.__dict__
                    for cls in type(handler).__mro__
                    if cls is not ADCPHandler and not issubclass(ADCPHandler, cls)
                )
                if not overridden:
                    warnings.append(
                        f"Feature '{feature}' is declared but '{method_name}' "
                        f"is not overridden from ADCPHandler"
                    )

    return warnings

Check that a handler implements the methods required by its declared features.

Compares the features declared in a capabilities response against the handler's method implementations. Returns warnings for features that are declared but whose corresponding handler methods are not overridden from the base class.

This is a development-time check — call it at startup to catch misconfigurations.

Args

handler
An ADCPHandler instance (or any object with handler methods).
capabilities
The capabilities response the handler will serve.

Returns

List of warning strings. Empty if everything is consistent.

def validate_product(product: dict[str, Any]) ‑> None
Expand source code
def validate_product(product: dict[str, Any]) -> None:
    """Validate a Product object.

    Args:
        product: Product dict

    Raises:
        ValidationError: If validation fails
    """
    if "publisher_properties" in product and product["publisher_properties"]:
        for item in product["publisher_properties"]:
            validate_publisher_properties_item(item)

Validate a Product object.

Args

product
Product dict

Raises

ValidationError
If validation fails
def validate_publisher_properties_item(item: dict[str, Any]) ‑> None
Expand source code
def validate_publisher_properties_item(item: dict[str, Any]) -> None:
    """Validate publisher_properties item discriminated union.

    AdCP v2.4.0+ uses discriminated unions with selection_type discriminator:
    - selection_type: "by_id" requires property_ids
    - selection_type: "by_tag" requires property_tags

    For backward compatibility, also validates the old mutual exclusivity constraint.

    Args:
        item: A single item from publisher_properties array

    Raises:
        ValidationError: If discriminator or field constraints are violated
    """
    selection_type = item.get("selection_type")
    has_property_ids = "property_ids" in item and item["property_ids"] is not None
    has_property_tags = "property_tags" in item and item["property_tags"] is not None

    # If selection_type discriminator is present, validate discriminated union
    if selection_type:
        if selection_type == "by_id" and not has_property_ids:
            raise ValidationError(
                "publisher_properties item with selection_type='by_id' must have property_ids"
            )
        elif selection_type == "by_tag" and not has_property_tags:
            raise ValidationError(
                "publisher_properties item with selection_type='by_tag' must have property_tags"
            )
        elif selection_type not in ("by_id", "by_tag"):
            raise ValidationError(
                f"publisher_properties item has invalid selection_type: {selection_type}"
            )

    # Validate mutual exclusivity (for both old and new formats)
    if has_property_ids and has_property_tags:
        raise ValidationError(
            "publisher_properties item cannot have both property_ids and property_tags. "
            "These fields are mutually exclusive."
        )

    if not has_property_ids and not has_property_tags:
        raise ValidationError(
            "publisher_properties item must have either property_ids or property_tags. "
            "At least one is required."
        )

Validate publisher_properties item discriminated union.

AdCP v2.4.0+ uses discriminated unions with selection_type discriminator: - selection_type: "by_id" requires property_ids - selection_type: "by_tag" requires property_tags

For backward compatibility, also validates the old mutual exclusivity constraint.

Args

item
A single item from publisher_properties array

Raises

ValidationError
If discriminator or field constraints are violated
def verify_agent_authorization(adagents_data: dict[str, Any],
agent_url: str,
property_type: str | None = None,
property_identifiers: list[dict[str, str]] | None = None) ‑> bool
Expand source code
def verify_agent_authorization(
    adagents_data: dict[str, Any],
    agent_url: str,
    property_type: str | None = None,
    property_identifiers: list[dict[str, str]] | None = None,
) -> bool:
    """Check if agent is authorized for a property.

    Args:
        adagents_data: Parsed adagents.json data
        agent_url: URL of the sales agent to verify
        property_type: Type of property (website, app, etc.) - optional
        property_identifiers: List of identifiers to match - optional

    Returns:
        True if agent is authorized, False otherwise

    Raises:
        AdagentsValidationError: If adagents_data is malformed

    Notes:
        - If property_type/identifiers are None, checks if agent is authorized
          for ANY property on this domain
        - Implements AdCP domain matching rules
        - Agent URLs are matched ignoring protocol and trailing slash
    """
    # Validate structure
    if not isinstance(adagents_data, dict):
        raise AdagentsValidationError("adagents_data must be a dictionary")

    authorized_agents = adagents_data.get("authorized_agents")
    if not isinstance(authorized_agents, list):
        raise AdagentsValidationError("adagents.json must have 'authorized_agents' array")

    # Normalize the agent URL for comparison
    normalized_agent_url = normalize_url(agent_url)

    # Check each authorized agent
    for agent in authorized_agents:
        if not isinstance(agent, dict):
            continue

        agent_url_from_json = agent.get("url", "")
        if not agent_url_from_json:
            continue

        # Match agent URL (protocol-agnostic)
        if normalize_url(agent_url_from_json) != normalized_agent_url:
            continue

        # Found matching agent - now check properties
        properties = agent.get("properties")

        # If properties field is missing or empty, agent is authorized for all properties
        if properties is None or (isinstance(properties, list) and len(properties) == 0):
            return True

        # If no property filters specified, we found the agent - authorized
        if property_type is None and property_identifiers is None:
            return True

        # Check specific property authorization
        if isinstance(properties, list):
            for prop in properties:
                if not isinstance(prop, dict):
                    continue

                # Check property type if specified
                if property_type is not None:
                    prop_type = prop.get("property_type", "")
                    if prop_type != property_type:
                        continue

                # Check identifiers if specified
                if property_identifiers is not None:
                    prop_identifiers = prop.get("identifiers", [])
                    if not isinstance(prop_identifiers, list):
                        continue

                    if identifiers_match(property_identifiers, prop_identifiers):
                        return True
                else:
                    # Property type matched and no identifier check needed
                    return True

    return False

Check if agent is authorized for a property.

Args

adagents_data
Parsed adagents.json data
agent_url
URL of the sales agent to verify
property_type
Type of property (website, app, etc.) - optional
property_identifiers
List of identifiers to match - optional

Returns

True if agent is authorized, False otherwise

Raises

AdagentsValidationError
If adagents_data is malformed

Notes

  • If property_type/identifiers are None, checks if agent is authorized for ANY property on this domain
  • Implements AdCP domain matching rules
  • Agent URLs are matched ignoring protocol and trailing slash
async def verify_agent_for_property(publisher_domain: str,
agent_url: str,
property_identifiers: list[dict[str, str]],
property_type: str | None = None,
timeout: float = 10.0,
client: httpx.AsyncClient | None = None) ‑> bool
Expand source code
async def verify_agent_for_property(
    publisher_domain: str,
    agent_url: str,
    property_identifiers: list[dict[str, str]],
    property_type: str | None = None,
    timeout: float = 10.0,
    client: httpx.AsyncClient | None = None,
) -> bool:
    """Convenience wrapper to fetch adagents.json and verify authorization in one call.

    Args:
        publisher_domain: Domain hosting the adagents.json file
        agent_url: URL of the sales agent to verify
        property_identifiers: List of identifiers to match
        property_type: Type of property (website, app, etc.) - optional
        timeout: Request timeout in seconds
        client: Optional httpx.AsyncClient for connection pooling

    Returns:
        True if agent is authorized, False otherwise

    Raises:
        AdagentsNotFoundError: If adagents.json not found (404)
        AdagentsValidationError: If JSON is invalid or malformed
        AdagentsTimeoutError: If request times out
    """
    adagents_data = await fetch_adagents(publisher_domain, timeout=timeout, client=client)
    return verify_agent_authorization(
        adagents_data=adagents_data,
        agent_url=agent_url,
        property_type=property_type,
        property_identifiers=property_identifiers,
    )

Convenience wrapper to fetch adagents.json and verify authorization in one call.

Args

publisher_domain
Domain hosting the adagents.json file
agent_url
URL of the sales agent to verify
property_identifiers
List of identifiers to match
property_type
Type of property (website, app, etc.) - optional
timeout
Request timeout in seconds
client
Optional httpx.AsyncClient for connection pooling

Returns

True if agent is authorized, False otherwise

Raises

AdagentsNotFoundError
If adagents.json not found (404)
AdagentsValidationError
If JSON is invalid or malformed
AdagentsTimeoutError
If request times out

Classes

class ADCPAuthenticationError (message: str, agent_id: str | None = None, agent_uri: str | None = None)
Expand source code
class ADCPAuthenticationError(ADCPError):
    """Authentication failed (401, 403)."""

    def __init__(self, message: str, agent_id: str | None = None, agent_uri: str | None = None):
        """Initialize authentication error."""
        suggestion = (
            "Check that your auth_token is valid and not expired.\n"
            "     Verify auth_type ('bearer' vs 'token') and auth_header are correct.\n"
            "     Some agents (like Optable) require auth_type='bearer' and "
            "auth_header='Authorization'"
        )
        super().__init__(message, agent_id, agent_uri, suggestion)

Authentication failed (401, 403).

Initialize authentication error.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ADCPClient (agent_config: AgentConfig,
webhook_url_template: str | None = None,
webhook_secret: str | None = None,
on_activity: Callable[[Activity], None] | None = None,
webhook_timestamp_tolerance: int = 300,
capabilities_ttl: float = 3600.0,
validate_features: bool = False,
strict_idempotency: bool = False,
signing: SigningConfig | None = None)
Expand source code
class ADCPClient:
    """Client for interacting with a single AdCP agent."""

    def __init__(
        self,
        agent_config: AgentConfig,
        webhook_url_template: str | None = None,
        webhook_secret: str | None = None,
        on_activity: Callable[[Activity], None] | None = None,
        webhook_timestamp_tolerance: int = 300,
        capabilities_ttl: float = 3600.0,
        validate_features: bool = False,
        strict_idempotency: bool = False,
        signing: SigningConfig | None = None,
    ):
        """
        Initialize ADCP client for a single agent.

        Args:
            agent_config: Agent configuration
            webhook_url_template: Template for webhook URLs with {agent_id},
                {task_type}, {operation_id}
            webhook_secret: Secret for webhook signature verification
            on_activity: Callback for activity events
            webhook_timestamp_tolerance: Maximum age (in seconds) for webhook
                timestamps. Webhooks with timestamps older than this or more than
                this far in the future are rejected. Defaults to 300 (5 minutes).
            capabilities_ttl: Time-to-live in seconds for cached capabilities (default: 1 hour)
            validate_features: When True, automatically check that the seller supports
                required features before making task calls (e.g., sync_audiences requires
                audience_targeting). Requires capabilities to have been fetched first.
            strict_idempotency: When True, verify the seller declared
                ``adcp.idempotency.replay_ttl_seconds`` in capabilities before any
                mutating call. Fetches capabilities lazily on first use. Raises
                ``IdempotencyUnsupportedError`` if the declaration is missing —
                sellers that don't declare it provide no retry-safety guarantee
                per AdCP #2315. Defaults to False for backward compatibility.
            signing: Optional RFC 9421 request-signing config. When provided,
                the client automatically attaches ``Signature`` /
                ``Signature-Input`` / ``Content-Digest`` headers to operations
                the seller's ``request_signing`` capability lists in
                ``required_for``, ``warn_for``, or ``supported_for``. The
                seller's ``covers_content_digest`` policy determines whether
                the body is bound to the signature. Generate a key with
                ``adcp-keygen`` and publish the public JWK at your
                ``jwks_uri``. Supported on both A2A and MCP
                (``mcp_transport="streamable_http"``); SSE-transport MCP
                logs a warning and falls through unsigned.
        """
        self.agent_config = agent_config
        self.webhook_url_template = webhook_url_template
        self.webhook_secret = webhook_secret
        self.on_activity = on_activity
        self.webhook_timestamp_tolerance = webhook_timestamp_tolerance
        self.capabilities_ttl = capabilities_ttl
        self.validate_features = validate_features
        self.strict_idempotency = strict_idempotency
        self.signing = signing

        # Capabilities cache
        self._capabilities: GetAdcpCapabilitiesResponse | None = None
        self._feature_resolver: FeatureResolver | None = None
        self._capabilities_fetched_at: float | None = None
        self._idempotency_capability_verified: bool = False
        # Unique per-instance token so use_idempotency_key scopes to this
        # client and does not bleed to siblings (AdCP #2315 cross-seller risk).
        from uuid import uuid4 as _uuid4

        self._idempotency_client_token: str = _uuid4().hex

        # Initialize protocol adapter
        self.adapter: ProtocolAdapter
        if agent_config.protocol == Protocol.A2A:
            self.adapter = A2AAdapter(agent_config)
        elif agent_config.protocol == Protocol.MCP:
            self.adapter = MCPAdapter(agent_config)
        else:
            raise ValueError(f"Unsupported protocol: {agent_config.protocol}")

        self.adapter.idempotency_client_token = self._idempotency_client_token
        if strict_idempotency:
            self.adapter.idempotency_capability_check = self._ensure_idempotency_capability
        if signing is not None:
            self.adapter.signing_request_hook = self._sign_outgoing_request

        # Initialize simple API accessor (lazy import to avoid circular dependency)
        from adcp.simple import SimpleAPI

        self.simple = SimpleAPI(self)

    async def _ensure_idempotency_capability(self) -> None:
        """Verify the seller positively declares idempotency support in capabilities.

        Called before every mutating request when ``strict_idempotency=True``.
        Fetches capabilities on first invocation; subsequent calls are no-ops
        once the declaration has been observed. Raises
        ``IdempotencyUnsupportedError`` when ``adcp.idempotency`` is missing,
        declares ``supported=False`` (seller does not dedupe — naive retry
        would double-process), or declares ``supported=True`` without a
        ``replay_ttl_seconds`` window.

        Sets ``_idempotency_capability_verified = True`` BEFORE calling
        ``fetch_capabilities`` so any recursive dispatch through the adapter
        terminates (``get_adcp_capabilities`` is non-mutating, so it would
        short-circuit anyway — but this guard protects against future refactors
        that might add it to the mutating set).
        """
        from adcp.exceptions import IdempotencyUnsupportedError

        if self._idempotency_capability_verified:
            return

        self._idempotency_capability_verified = True
        try:
            caps = await self.fetch_capabilities()
            adcp_info = getattr(caps, "adcp", None)
            idempotency_info = getattr(adcp_info, "idempotency", None) if adcp_info else None

            if idempotency_info is None:
                raise IdempotencyUnsupportedError(
                    agent_id=self.agent_config.id,
                    agent_uri=self.agent_config.agent_uri,
                    reason="seller did not declare adcp.idempotency",
                )

            supported = getattr(idempotency_info, "supported", None)
            if supported is False:
                raise IdempotencyUnsupportedError(
                    agent_id=self.agent_config.id,
                    agent_uri=self.agent_config.agent_uri,
                    reason="seller declared adcp.idempotency.supported=false",
                )

            ttl = getattr(idempotency_info, "replay_ttl_seconds", None)
            if ttl is None:
                raise IdempotencyUnsupportedError(
                    agent_id=self.agent_config.id,
                    agent_uri=self.agent_config.agent_uri,
                    reason=(
                        "seller declared adcp.idempotency.supported=true but omitted "
                        "replay_ttl_seconds"
                    ),
                )
        except Exception:
            self._idempotency_capability_verified = False
            raise

    async def _sign_outgoing_request(self, request: httpx.Request) -> None:
        """httpx request event hook that attaches RFC 9421 signature headers.

        Installed on the protocol adapter's httpx client when a
        ``SigningConfig`` was passed to ``ADCPClient``. Consults the
        seller's advertised ``request_signing`` capability and signs only
        the operations the seller listed in ``required_for``, ``warn_for``,
        or ``supported_for`` — other requests (including the agent-card
        fetch and ``get_adcp_capabilities`` itself) pass through unsigned.
        The ``covers_content_digest`` tri-state determines whether the
        body is bound to the signature.
        """
        if self.signing is None:
            return
        operation = _signing_current_operation.get()
        # Unset ContextVar → out-of-band call (agent-card fetch, session
        # initialize, etc). Skip without fetching capabilities.
        #
        # get_adcp_capabilities → bootstrap carve-out: signing it would
        # require capabilities we don't have yet, and if a pathological
        # seller listed this op in its own required_for we'd recurse.
        # Keep this check narrow — only operations strictly required to
        # *obtain* capabilities belong here. Today that's just
        # get_adcp_capabilities. A future adapter that adds another
        # capabilities-precondition op MUST extend this guard.
        if operation is None or operation == "get_adcp_capabilities":
            return

        caps = await self.fetch_capabilities()
        req_signing = getattr(caps, "request_signing", None)

        # Detect and surface a malformed seller config: supported=False is
        # "signatures are ignored", but populating required_for alongside
        # it is contradictory. The classifier correctly skips (matches
        # verifier behavior) but the silent downgrade hides a config bug
        # that will bite pilots.
        if (
            req_signing is not None
            and not req_signing.supported
            and (req_signing.required_for or req_signing.warn_for)
        ):
            logger.warning(
                "Seller %s advertises request_signing.supported=false but "
                "populates required_for/warn_for — treating as unsupported "
                "per spec. Verify the seller's capability advertisement.",
                self.agent_config.id,
            )

        decision = operation_needs_signing(req_signing, operation)
        if decision == "skip":
            return

        covers_policy: str | None = None
        if req_signing is not None and req_signing.covers_content_digest is not None:
            covers_policy = req_signing.covers_content_digest.value
        if covers_policy == "forbidden":
            cover_digest = False
        elif covers_policy == "required":
            cover_digest = True
        else:
            # "either" or absent — signer's choice; default stricter.
            cover_digest = True

        body = request.content
        signed = sign_request(
            method=request.method,
            url=str(request.url),
            headers=dict(request.headers),
            body=body,
            private_key=self.signing.private_key,
            key_id=self.signing.key_id,
            alg=self.signing.alg,
            cover_content_digest=cover_digest,
            tag=self.signing.tag,
        )
        # pop-then-set ensures our signed values are authoritative even if
        # another hook or earlier layer added a same-named header. httpx
        # headers are a case-insensitive MultiDict, so a naive assignment
        # could leave a duplicate value in a different case.
        for header_name, header_value in signed.as_dict().items():
            request.headers.pop(header_name, None)
            request.headers[header_name] = header_value

    def get_webhook_url(self, task_type: str, operation_id: str) -> str:
        """Generate webhook URL for a task."""
        if not self.webhook_url_template:
            raise ValueError("webhook_url_template not configured")

        return self.webhook_url_template.format(
            agent_id=self.agent_config.id,
            task_type=task_type,
            operation_id=operation_id,
        )

    def _emit_activity(self, activity: Activity) -> None:
        """Emit activity event."""
        if self.on_activity:
            self.on_activity(activity)

    @contextlib.contextmanager
    def use_idempotency_key(self, key: str) -> Iterator[str]:
        """Pin an ``idempotency_key`` for the next mutating call on THIS client.

        Use when you've persisted a key (e.g., in a buyer-side database) and
        want the SDK to send that exact key on resume or retry across process
        restarts. The key is validated against ``^[A-Za-z0-9_.:-]{16,255}$`` on
        entry; a ``ValueError`` is raised for malformed keys.

        Scope rules:

        * **Single-use within scope.** The first mutating call inside the
          ``with`` block consumes the pinned key; a second mutating call falls
          through to a fresh UUID. This protects against ``asyncio.gather``
          siblings accidentally sharing the key (which would trigger
          ``IDEMPOTENCY_CONFLICT`` or silently duplicate work). If you need to
          retry, wrap each attempt in its own ``with`` block.
        * **Client-scoped.** The pinned key applies only to calls on THIS
          client. A mutating call on a sibling ``ADCPClient`` inside the same
          ``with`` block generates a fresh key and emits a ``UserWarning`` —
          keys must be unique per (seller, request) pair (AdCP #2315).
        * **No nesting.** Nested ``use_idempotency_key`` on the same client
          raises ``RuntimeError``.

        Example::

            with client.use_idempotency_key(campaign.stored_key):
                result = await client.create_media_buy(request)
        """
        from adcp import _idempotency

        _idempotency.validate_key(key)
        token = self._idempotency_client_token
        if token in _idempotency._scoped_keys:
            raise RuntimeError(
                "use_idempotency_key is already active on this client; "
                "nested usage is not supported."
            )
        _idempotency._scoped_keys[token] = key
        try:
            yield key
        finally:
            _idempotency._scoped_keys.pop(token, None)

    # ========================================================================
    # Capability Validation
    # ========================================================================

    @property
    def capabilities(self) -> GetAdcpCapabilitiesResponse | None:
        """Return cached capabilities, or None if not yet fetched."""
        return self._capabilities

    @property
    def feature_resolver(self) -> FeatureResolver | None:
        """Return the FeatureResolver for cached capabilities, or None."""
        return self._feature_resolver

    async def fetch_capabilities(self) -> GetAdcpCapabilitiesResponse:
        """Fetch capabilities, using cache if still valid.

        Returns:
            The seller's capabilities response.
        """
        if self._capabilities is not None and self._capabilities_fetched_at is not None:
            elapsed = time.monotonic() - self._capabilities_fetched_at
            if elapsed < self.capabilities_ttl:
                return self._capabilities

        return await self.refresh_capabilities()

    async def refresh_capabilities(self) -> GetAdcpCapabilitiesResponse:
        """Fetch capabilities from the seller, bypassing cache.

        Returns:
            The seller's capabilities response.
        """
        result = await self.get_adcp_capabilities(GetAdcpCapabilitiesRequest())
        if result.success and result.data is not None:
            self._capabilities = result.data
            self._feature_resolver = FeatureResolver(result.data)
            self._capabilities_fetched_at = time.monotonic()
            return self._capabilities
        raise ADCPError(
            f"Failed to fetch capabilities: {result.error or result.message}",
            agent_id=self.agent_config.id,
            agent_uri=self.agent_config.agent_uri,
        )

    def _ensure_resolver(self) -> FeatureResolver:
        """Return the FeatureResolver, raising if capabilities haven't been fetched."""
        if self._feature_resolver is None:
            raise ADCPError(
                "Cannot check feature support: capabilities have not been fetched. "
                "Call fetch_capabilities() first.",
                agent_id=self.agent_config.id,
                agent_uri=self.agent_config.agent_uri,
            )
        return self._feature_resolver

    def supports(self, feature: str) -> bool:
        """Check if the seller supports a feature.

        Supports multiple feature namespaces:
        - Protocol support: ``supports("media_buy")`` checks ``supported_protocols``
        - Extension support: ``supports("ext:scope3")`` checks ``extensions_supported``
        - Targeting: ``supports("targeting.geo_countries")`` checks
          ``media_buy.execution.targeting``
        - Media buy features: ``supports("audience_targeting")`` checks
          ``media_buy.features``
        - Signals features: ``supports("catalog_signals")`` checks
          ``signals.features``

        Args:
            feature: Feature identifier to check.

        Returns:
            True if the seller declares the feature as supported.

        Raises:
            ADCPError: If capabilities have not been fetched yet.
        """
        return self._ensure_resolver().supports(feature)

    def require(self, *features: str) -> None:
        """Assert that the seller supports all listed features.

        Args:
            *features: Feature identifiers to require.

        Raises:
            ADCPFeatureUnsupportedError: If any features are not supported.
            ADCPError: If capabilities have not been fetched yet.
        """
        self._ensure_resolver().require(
            *features,
            agent_id=self.agent_config.id,
            agent_uri=self.agent_config.agent_uri,
        )

    def _validate_task_features(self, task_name: str) -> None:
        """Check feature requirements for a task if validate_features is enabled.

        Returns without checking if validate_features is False or capabilities
        haven't been fetched yet (logs a warning in the latter case).
        """
        if not self.validate_features:
            return
        if self._feature_resolver is None:
            logger.warning(
                "validate_features is enabled but capabilities have not been fetched. "
                "Call fetch_capabilities() to enable feature validation."
            )
            return
        required_feature = TASK_FEATURE_MAP.get(task_name)
        if required_feature is None:
            return
        self.require(required_feature)

    async def get_products(
        self,
        request: GetProductsRequest,
        fetch_previews: bool = False,
        preview_output_format: str = "url",
        creative_agent_client: ADCPClient | None = None,
    ) -> TaskResult[GetProductsResponse]:
        """
        Get advertising products.

        Args:
            request: Request parameters
            fetch_previews: If True, generate preview URLs for each product's formats
                (uses batch API for 5-10x performance improvement)
            preview_output_format: "url" for iframe URLs (default), "html" for direct
                embedding (2-3x faster, no iframe overhead)
            creative_agent_client: Client for creative agent (required if
                fetch_previews=True)

        Returns:
            TaskResult containing GetProductsResponse with optional preview URLs in metadata

        Raises:
            ValueError: If fetch_previews=True but creative_agent_client is not provided
        """
        if fetch_previews and not creative_agent_client:
            raise ValueError("creative_agent_client is required when fetch_previews=True")

        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_products",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_products(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_products",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        result: TaskResult[GetProductsResponse] = self.adapter._parse_response(
            raw_result, GetProductsResponse
        )

        if fetch_previews and result.success and result.data and creative_agent_client:
            from adcp.utils.preview_cache import add_preview_urls_to_products

            products_with_previews = await add_preview_urls_to_products(
                result.data.products,
                creative_agent_client,
                use_batch=True,
                output_format=preview_output_format,
            )
            result.metadata = result.metadata or {}
            result.metadata["products_with_previews"] = products_with_previews

        return result

    async def list_creative_formats(
        self,
        request: ListCreativeFormatsRequest,
        fetch_previews: bool = False,
        preview_output_format: str = "url",
    ) -> TaskResult[ListCreativeFormatsResponse]:
        """
        List supported creative formats.

        Args:
            request: Request parameters
            fetch_previews: If True, generate preview URLs for each format using
                sample manifests (uses batch API for 5-10x performance improvement)
            preview_output_format: "url" for iframe URLs (default), "html" for direct
                embedding (2-3x faster, no iframe overhead)

        Returns:
            TaskResult containing ListCreativeFormatsResponse with optional preview URLs in metadata
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_creative_formats",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.list_creative_formats(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_creative_formats",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        result: TaskResult[ListCreativeFormatsResponse] = self.adapter._parse_response(
            raw_result, ListCreativeFormatsResponse
        )

        if fetch_previews and result.success and result.data:
            from adcp.utils.preview_cache import add_preview_urls_to_formats

            formats_with_previews = await add_preview_urls_to_formats(
                result.data.formats,
                self,
                use_batch=True,
                output_format=preview_output_format,
            )
            result.metadata = result.metadata or {}
            result.metadata["formats_with_previews"] = formats_with_previews

        return result

    async def preview_creative(
        self,
        request: PreviewCreativeRequest,
    ) -> TaskResult[PreviewCreativeResponse]:
        """
        Generate preview of a creative manifest.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing PreviewCreativeResponse with preview URLs
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="preview_creative",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.preview_creative(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="preview_creative",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, PreviewCreativeResponse)

    async def sync_creatives(
        self,
        request: SyncCreativesRequest,
    ) -> TaskResult[SyncCreativesResponse]:
        """
        Sync Creatives.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing SyncCreativesResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_creatives",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.sync_creatives(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_creatives",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SyncCreativesResponse)

    async def list_creatives(
        self,
        request: ListCreativesRequest,
    ) -> TaskResult[ListCreativesResponse]:
        """
        List Creatives.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing ListCreativesResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_creatives",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.list_creatives(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_creatives",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ListCreativesResponse)

    async def get_media_buy_delivery(
        self,
        request: GetMediaBuyDeliveryRequest,
    ) -> TaskResult[GetMediaBuyDeliveryResponse]:
        """
        Get Media Buy Delivery.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing GetMediaBuyDeliveryResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_media_buy_delivery",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_media_buy_delivery(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_media_buy_delivery",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetMediaBuyDeliveryResponse)

    async def get_media_buys(
        self,
        request: GetMediaBuysRequest,
    ) -> TaskResult[GetMediaBuysResponse]:
        """
        Get Media Buys.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing GetMediaBuysResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_media_buys",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_media_buys(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_media_buys",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetMediaBuysResponse)

    async def get_signals(
        self,
        request: GetSignalsRequest,
    ) -> TaskResult[GetSignalsResponse]:
        """
        Get Signals.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing GetSignalsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_signals",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_signals(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_signals",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetSignalsResponse)

    async def activate_signal(
        self,
        request: ActivateSignalRequest,
    ) -> TaskResult[ActivateSignalResponse]:
        """
        Activate Signal.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing ActivateSignalResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="activate_signal",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.activate_signal(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="activate_signal",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ActivateSignalResponse)

    async def provide_performance_feedback(
        self,
        request: ProvidePerformanceFeedbackRequest,
    ) -> TaskResult[ProvidePerformanceFeedbackResponse]:
        """
        Provide Performance Feedback.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing ProvidePerformanceFeedbackResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="provide_performance_feedback",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.provide_performance_feedback(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="provide_performance_feedback",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ProvidePerformanceFeedbackResponse)

    async def create_media_buy(
        self,
        request: CreateMediaBuyRequest,
    ) -> TaskResult[CreateMediaBuyResponse]:
        """
        Create a new media buy reservation.

        Requests the agent to reserve inventory for a campaign. The agent returns a
        media_buy_id that tracks this reservation and can be used for updates.

        Args:
            request: Media buy creation parameters including:
                - brand: Brand reference; resolved from brand.json or the registry at execution
                - packages: List of package requests specifying desired inventory
                - publisher_properties: Target properties for ad placement
                - budget: Optional budget constraints
                - start_date/end_date: Campaign flight dates

        Returns:
            TaskResult containing CreateMediaBuyResponse with:
                - media_buy_id: Unique identifier for this reservation
                - status: Current state of the media buy
                - packages: Confirmed package details
                - Additional platform-specific metadata

        Example:
            >>> from adcp import ADCPClient, CreateMediaBuyRequest, BrandReference
            >>> client = ADCPClient(agent_config)
            >>> request = CreateMediaBuyRequest(
            ...     brand=BrandReference(domain="acme.com"),
            ...     packages=[package_request],
            ...     publisher_properties=properties,
            ... )
            >>> result = await client.create_media_buy(request)
            >>> if result.success:
            ...     media_buy_id = result.data.media_buy_id
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="create_media_buy",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.create_media_buy(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="create_media_buy",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, CreateMediaBuyResponse)

    async def update_media_buy(
        self,
        request: UpdateMediaBuyRequest,
    ) -> TaskResult[UpdateMediaBuyResponse]:
        """
        Update an existing media buy reservation.

        Modifies a previously created media buy by updating packages or publisher
        properties. The update operation uses discriminated unions to specify what
        to change - either package details or targeting properties.

        Args:
            request: Media buy update parameters including:
                - media_buy_id: Identifier from create_media_buy response
                - updates: Discriminated union specifying update type:
                    * UpdateMediaBuyPackagesRequest: Modify package selections
                    * UpdateMediaBuyPropertiesRequest: Change targeting properties

        Returns:
            TaskResult containing UpdateMediaBuyResponse with:
                - media_buy_id: The updated media buy identifier
                - status: Updated state of the media buy
                - packages: Updated package configurations
                - Additional platform-specific metadata

        Example:
            >>> from adcp import ADCPClient, UpdateMediaBuyPackagesRequest
            >>> client = ADCPClient(agent_config)
            >>> request = UpdateMediaBuyPackagesRequest(
            ...     media_buy_id="mb_123",
            ...     packages=[updated_package]
            ... )
            >>> result = await client.update_media_buy(request)
            >>> if result.success:
            ...     updated_packages = result.data.packages
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_media_buy",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.update_media_buy(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_media_buy",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, UpdateMediaBuyResponse)

    async def build_creative(
        self,
        request: BuildCreativeRequest,
    ) -> TaskResult[BuildCreativeResponse]:
        """
        Generate production-ready creative assets.

        Requests the creative agent to build final deliverable assets in the target
        format (e.g., VAST, DAAST, HTML5). This is typically called after previewing
        and approving a creative manifest.

        Args:
            request: Creative build parameters including:
                - manifest: Creative manifest with brand info and content
                - target_format_id: Desired output format identifier
                - inputs: Optional user-provided inputs for template variables
                - deployment: Platform or agent deployment configuration

        Returns:
            TaskResult containing BuildCreativeResponse with:
                - assets: Production-ready creative files (URLs or inline content)
                - format_id: The generated format identifier
                - manifest: The creative manifest used for generation
                - metadata: Additional platform-specific details

        Example:
            >>> from adcp import ADCPClient, BuildCreativeRequest
            >>> client = ADCPClient(agent_config)
            >>> request = BuildCreativeRequest(
            ...     manifest=creative_manifest,
            ...     target_format_id="vast_2.0",
            ...     inputs={"duration": 30}
            ... )
            >>> result = await client.build_creative(request)
            >>> if result.success:
            ...     vast_url = result.data.assets[0].url
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="build_creative",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.build_creative(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="build_creative",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, BuildCreativeResponse)

    async def list_accounts(
        self,
        request: ListAccountsRequest,
    ) -> TaskResult[ListAccountsResponse]:
        """
        List Accounts.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing ListAccountsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_accounts",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.list_accounts(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_accounts",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ListAccountsResponse)

    async def sync_accounts(
        self,
        request: SyncAccountsRequest,
    ) -> TaskResult[SyncAccountsResponse]:
        """
        Sync Accounts.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing SyncAccountsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_accounts",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.sync_accounts(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_accounts",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SyncAccountsResponse)

    async def get_account_financials(
        self,
        request: GetAccountFinancialsRequest,
    ) -> TaskResult[GetAccountFinancialsResponse]:
        """
        Get Account Financials.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing GetAccountFinancialsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_account_financials",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_account_financials(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_account_financials",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetAccountFinancialsResponse)

    async def report_usage(
        self,
        request: ReportUsageRequest,
    ) -> TaskResult[ReportUsageResponse]:
        """
        Report Usage.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing ReportUsageResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="report_usage",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.report_usage(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="report_usage",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ReportUsageResponse)

    async def log_event(
        self,
        request: LogEventRequest,
    ) -> TaskResult[LogEventResponse]:
        """
        Log Event.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing LogEventResponse
        """
        self._validate_task_features("log_event")
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="log_event",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.log_event(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="log_event",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, LogEventResponse)

    async def sync_event_sources(
        self,
        request: SyncEventSourcesRequest,
    ) -> TaskResult[SyncEventSourcesResponse]:
        """
        Sync Event Sources.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing SyncEventSourcesResponse
        """
        self._validate_task_features("sync_event_sources")
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_event_sources",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.sync_event_sources(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_event_sources",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SyncEventSourcesResponse)

    async def sync_audiences(
        self,
        request: SyncAudiencesRequest,
    ) -> TaskResult[SyncAudiencesResponse]:
        """
        Sync Audiences.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing SyncAudiencesResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_audiences",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.sync_audiences(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_audiences",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SyncAudiencesResponse)

    async def sync_catalogs(
        self,
        request: SyncCatalogsRequest,
    ) -> TaskResult[SyncCatalogsResponse]:
        """
        Sync Catalogs.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing SyncCatalogsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_catalogs",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.sync_catalogs(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_catalogs",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SyncCatalogsResponse)

    async def get_creative_delivery(
        self,
        request: GetCreativeDeliveryRequest,
    ) -> TaskResult[GetCreativeDeliveryResponse]:
        """
        Get Creative Delivery.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing GetCreativeDeliveryResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_creative_delivery",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_creative_delivery(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_creative_delivery",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetCreativeDeliveryResponse)

    # ========================================================================
    # V3 Protocol Methods - Protocol Discovery
    # ========================================================================

    async def get_adcp_capabilities(
        self,
        request: GetAdcpCapabilitiesRequest,
    ) -> TaskResult[GetAdcpCapabilitiesResponse]:
        """
        Get AdCP capabilities from the agent.

        Queries the agent's supported AdCP features, protocol versions, and
        domain-specific capabilities (media_buy, signals, sponsored_intelligence).

        Args:
            request: Request parameters including optional protocol filters

        Returns:
            TaskResult containing GetAdcpCapabilitiesResponse with:
                - adcp: Core protocol version information
                - supported_protocols: List of supported domain protocols
                - media_buy: Media buy capabilities (if supported)
                - sponsored_intelligence: SI capabilities (if supported)
                - signals: Signals capabilities (if supported)
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_adcp_capabilities",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_adcp_capabilities(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_adcp_capabilities",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetAdcpCapabilitiesResponse)

    # ========================================================================
    # V3 Protocol Methods - Content Standards
    # ========================================================================

    async def create_content_standards(
        self,
        request: CreateContentStandardsRequest,
    ) -> TaskResult[CreateContentStandardsResponse]:
        """
        Create a new content standards configuration.

        Defines acceptable content contexts for ad placement using natural
        language policy and optional calibration exemplars.

        Args:
            request: Request parameters including policy and scope

        Returns:
            TaskResult containing CreateContentStandardsResponse with standards_id
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="create_content_standards",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.create_content_standards(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="create_content_standards",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, CreateContentStandardsResponse)

    async def get_content_standards(
        self,
        request: GetContentStandardsRequest,
    ) -> TaskResult[GetContentStandardsResponse]:
        """
        Get a content standards configuration by ID.

        Args:
            request: Request parameters including standards_id

        Returns:
            TaskResult containing GetContentStandardsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_content_standards",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_content_standards(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_content_standards",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetContentStandardsResponse)

    async def list_content_standards(
        self,
        request: ListContentStandardsRequest,
    ) -> TaskResult[ListContentStandardsResponse]:
        """
        List content standards configurations.

        Args:
            request: Request parameters including optional filters

        Returns:
            TaskResult containing ListContentStandardsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_content_standards",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.list_content_standards(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_content_standards",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ListContentStandardsResponse)

    async def update_content_standards(
        self,
        request: UpdateContentStandardsRequest,
    ) -> TaskResult[UpdateContentStandardsResponse]:
        """
        Update a content standards configuration.

        Args:
            request: Request parameters including standards_id and updates

        Returns:
            TaskResult containing UpdateContentStandardsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_content_standards",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.update_content_standards(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_content_standards",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, UpdateContentStandardsResponse)

    async def calibrate_content(
        self,
        request: CalibrateContentRequest,
    ) -> TaskResult[CalibrateContentResponse]:
        """
        Calibrate content against standards.

        Evaluates content (artifact or URL) against configured standards to
        determine suitability for ad placement.

        Args:
            request: Request parameters including content to evaluate

        Returns:
            TaskResult containing CalibrateContentResponse with verdict
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="calibrate_content",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.calibrate_content(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="calibrate_content",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, CalibrateContentResponse)

    async def validate_content_delivery(
        self,
        request: ValidateContentDeliveryRequest,
    ) -> TaskResult[ValidateContentDeliveryResponse]:
        """
        Validate content delivery against standards.

        Validates that ad delivery records comply with content standards.

        Args:
            request: Request parameters including delivery records

        Returns:
            TaskResult containing ValidateContentDeliveryResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="validate_content_delivery",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.validate_content_delivery(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="validate_content_delivery",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ValidateContentDeliveryResponse)

    async def get_media_buy_artifacts(
        self,
        request: GetMediaBuyArtifactsRequest,
    ) -> TaskResult[GetMediaBuyArtifactsResponse]:
        """
        Get artifacts associated with a media buy.

        Retrieves content artifacts where ads were delivered for a media buy.

        Args:
            request: Request parameters including media_buy_id

        Returns:
            TaskResult containing GetMediaBuyArtifactsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_media_buy_artifacts",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_media_buy_artifacts(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_media_buy_artifacts",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetMediaBuyArtifactsResponse)

    # ========================================================================
    # V3 Protocol Methods - Sponsored Intelligence
    # ========================================================================

    async def si_get_offering(
        self,
        request: SiGetOfferingRequest,
    ) -> TaskResult[SiGetOfferingResponse]:
        """
        Get sponsored intelligence offering.

        Retrieves product/service offerings that can be presented in a
        sponsored intelligence session.

        Args:
            request: Request parameters including brand context

        Returns:
            TaskResult containing SiGetOfferingResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="si_get_offering",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.si_get_offering(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="si_get_offering",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SiGetOfferingResponse)

    async def si_initiate_session(
        self,
        request: SiInitiateSessionRequest,
    ) -> TaskResult[SiInitiateSessionResponse]:
        """
        Initiate a sponsored intelligence session.

        Starts a conversational brand experience session with a user.

        Args:
            request: Request parameters including identity and context

        Returns:
            TaskResult containing SiInitiateSessionResponse with session_id
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="si_initiate_session",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.si_initiate_session(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="si_initiate_session",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SiInitiateSessionResponse)

    async def si_send_message(
        self,
        request: SiSendMessageRequest,
    ) -> TaskResult[SiSendMessageResponse]:
        """
        Send a message in a sponsored intelligence session.

        Continues the conversation in an active SI session.

        Args:
            request: Request parameters including session_id and message

        Returns:
            TaskResult containing SiSendMessageResponse with brand response
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="si_send_message",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.si_send_message(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="si_send_message",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SiSendMessageResponse)

    async def si_terminate_session(
        self,
        request: SiTerminateSessionRequest,
    ) -> TaskResult[SiTerminateSessionResponse]:
        """
        Terminate a sponsored intelligence session.

        Ends an active SI session, optionally with follow-up actions.

        Args:
            request: Request parameters including session_id and termination context

        Returns:
            TaskResult containing SiTerminateSessionResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="si_terminate_session",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.si_terminate_session(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="si_terminate_session",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SiTerminateSessionResponse)

    # ========================================================================
    # V3 Governance Methods
    # ========================================================================

    async def get_creative_features(
        self,
        request: GetCreativeFeaturesRequest,
    ) -> TaskResult[GetCreativeFeaturesResponse]:
        """Evaluate governance features for a creative manifest."""
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_creative_features",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_creative_features(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_creative_features",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetCreativeFeaturesResponse)

    async def sync_plans(
        self,
        request: SyncPlansRequest,
    ) -> TaskResult[SyncPlansResponse]:
        """Sync campaign governance plans to the governance agent."""
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_plans",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.sync_plans(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_plans",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SyncPlansResponse)

    async def check_governance(
        self,
        request: CheckGovernanceRequest,
    ) -> TaskResult[CheckGovernanceResponse]:
        """Check a proposed or committed action against campaign governance."""
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="check_governance",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.check_governance(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="check_governance",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, CheckGovernanceResponse)

    async def report_plan_outcome(
        self,
        request: ReportPlanOutcomeRequest,
    ) -> TaskResult[ReportPlanOutcomeResponse]:
        """Report the outcome of a governed action to the governance agent."""
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="report_plan_outcome",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.report_plan_outcome(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="report_plan_outcome",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ReportPlanOutcomeResponse)

    async def get_plan_audit_logs(
        self,
        request: GetPlanAuditLogsRequest,
    ) -> TaskResult[GetPlanAuditLogsResponse]:
        """Retrieve governance state and audit logs for one or more plans."""
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_plan_audit_logs",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_plan_audit_logs(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_plan_audit_logs",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetPlanAuditLogsResponse)

    async def create_property_list(
        self,
        request: CreatePropertyListRequest,
    ) -> TaskResult[CreatePropertyListResponse]:
        """
        Create a property list for governance filtering.

        Property lists define dynamic sets of properties based on filters,
        brand manifests, and feature requirements.

        Args:
            request: Request parameters for creating the property list

        Returns:
            TaskResult containing CreatePropertyListResponse with list_id
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="create_property_list",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.create_property_list(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="create_property_list",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, CreatePropertyListResponse)

    async def get_property_list(
        self,
        request: GetPropertyListRequest,
    ) -> TaskResult[GetPropertyListResponse]:
        """
        Get a property list with optional resolution.

        When resolve=true, returns the list of resolved property identifiers.
        Use this to get the actual properties that match the list's filters.

        Args:
            request: Request parameters including list_id and resolve flag

        Returns:
            TaskResult containing GetPropertyListResponse with identifiers
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_property_list",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_property_list(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_property_list",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetPropertyListResponse)

    async def list_property_lists(
        self,
        request: ListPropertyListsRequest,
    ) -> TaskResult[ListPropertyListsResponse]:
        """
        List property lists owned by a principal.

        Retrieves metadata for all property lists, optionally filtered
        by principal or pagination parameters.

        Args:
            request: Request parameters with optional filtering

        Returns:
            TaskResult containing ListPropertyListsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_property_lists",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.list_property_lists(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_property_lists",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ListPropertyListsResponse)

    async def update_property_list(
        self,
        request: UpdatePropertyListRequest,
    ) -> TaskResult[UpdatePropertyListResponse]:
        """
        Update a property list.

        Modifies the filters, brand manifest, or other parameters
        of an existing property list.

        Args:
            request: Request parameters with list_id and updates

        Returns:
            TaskResult containing UpdatePropertyListResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_property_list",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.update_property_list(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_property_list",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, UpdatePropertyListResponse)

    async def delete_property_list(
        self,
        request: DeletePropertyListRequest,
    ) -> TaskResult[DeletePropertyListResponse]:
        """
        Delete a property list.

        Removes a property list. Any active subscriptions to this list
        will be terminated.

        Args:
            request: Request parameters with list_id

        Returns:
            TaskResult containing DeletePropertyListResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="delete_property_list",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.delete_property_list(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="delete_property_list",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, DeletePropertyListResponse)

    # ========================================================================
    # V3 Protocol Methods - Governance (Collection Lists)
    # ========================================================================

    async def create_collection_list(
        self,
        request: CreateCollectionListRequest,
    ) -> TaskResult[CreateCollectionListResponse]:
        """Create a collection list for governance filtering.

        Collection lists define dynamic sets of collections (properties, segments, etc.)
        that can be referenced by authorization rules and audience scoping.

        Args:
            request: Request parameters for creating the collection list

        Returns:
            TaskResult containing CreateCollectionListResponse with list_id
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="create_collection_list",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.create_collection_list(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="create_collection_list",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, CreateCollectionListResponse)

    async def get_collection_list(
        self,
        request: GetCollectionListRequest,
    ) -> TaskResult[GetCollectionListResponse]:
        """Get a collection list with optional resolution.

        When resolve=true, returns the resolved members of the collection list.

        Args:
            request: Request parameters including list_id and resolve flag

        Returns:
            TaskResult containing GetCollectionListResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_collection_list",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_collection_list(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_collection_list",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetCollectionListResponse)

    async def list_collection_lists(
        self,
        request: ListCollectionListsRequest,
    ) -> TaskResult[ListCollectionListsResponse]:
        """List collection lists owned by a principal.

        Args:
            request: Request parameters with optional filtering

        Returns:
            TaskResult containing ListCollectionListsResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_collection_lists",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.list_collection_lists(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="list_collection_lists",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ListCollectionListsResponse)

    async def update_collection_list(
        self,
        request: UpdateCollectionListRequest,
    ) -> TaskResult[UpdateCollectionListResponse]:
        """Update a collection list.

        Args:
            request: Request parameters with list_id and updates

        Returns:
            TaskResult containing UpdateCollectionListResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_collection_list",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.update_collection_list(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_collection_list",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, UpdateCollectionListResponse)

    async def delete_collection_list(
        self,
        request: DeleteCollectionListRequest,
    ) -> TaskResult[DeleteCollectionListResponse]:
        """Delete a collection list.

        Args:
            request: Request parameters with list_id

        Returns:
            TaskResult containing DeleteCollectionListResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="delete_collection_list",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.delete_collection_list(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="delete_collection_list",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, DeleteCollectionListResponse)

    # ========================================================================
    # V3 Protocol Methods - Governance (Sync Governance)
    # ========================================================================

    async def sync_governance(
        self,
        request: SyncGovernanceRequest,
    ) -> TaskResult[SyncGovernanceResponse]:
        """Sync governance agents attached to an account.

        Attach, detach, or replace the set of governance agents that must be
        consulted for plan approval on an account.

        Args:
            request: Request parameters with account and governance agents

        Returns:
            TaskResult containing SyncGovernanceResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_governance",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.sync_governance(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="sync_governance",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, SyncGovernanceResponse)

    # ========================================================================
    # V3 Protocol Methods - Temporal Matching Protocol (TMP)
    # ========================================================================

    async def context_match(
        self,
        request: ContextMatchRequest,
    ) -> TaskResult[ContextMatchResponse]:
        """Match ad context to buyer packages.

        Evaluates contextual signals for a publisher placement against the
        buyer's active packages and returns matching offers.

        Args:
            request: Context match request with placement, property, and
                optional artifact refs, context signals, and geo data.

        Returns:
            TaskResult containing ContextMatchResponse with offers.
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True, by_alias=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="context_match",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.context_match(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="context_match",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ContextMatchResponse)

    async def identity_match(
        self,
        request: IdentityMatchRequest,
    ) -> TaskResult[IdentityMatchResponse]:
        """Match user identity for package eligibility.

        Evaluates a user identity token against all active packages for
        frequency capping and personalization.

        Args:
            request: Identity match request with user_token, uid_type,
                and package_ids.

        Returns:
            TaskResult containing IdentityMatchResponse with eligible_package_ids.
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True, by_alias=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="identity_match",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.identity_match(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="identity_match",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, IdentityMatchResponse)

    # ========================================================================
    # V3 Protocol Methods - Brand Rights
    # ========================================================================

    async def get_brand_identity(
        self,
        request: GetBrandIdentityRequest,
    ) -> TaskResult[GetBrandIdentityResponse]:
        """Get brand identity information.

        Retrieves brand identity data including logos, colors, fonts,
        voice synthesis config, and rights availability.

        Args:
            request: Request with brand_id and optional fields filter.

        Returns:
            TaskResult containing GetBrandIdentityResponse.
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_brand_identity",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_brand_identity(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_brand_identity",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetBrandIdentityResponse)

    async def get_rights(
        self,
        request: GetRightsRequest,
    ) -> TaskResult[GetRightsResponse]:
        """Get available rights for licensing.

        Searches for rights offerings using natural language query and
        filters by type, uses, countries, and buyer compatibility.

        Args:
            request: Request with query, uses, and optional filters.

        Returns:
            TaskResult containing GetRightsResponse with matched rights.
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_rights",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.get_rights(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="get_rights",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, GetRightsResponse)

    async def acquire_rights(
        self,
        request: AcquireRightsRequest,
    ) -> TaskResult[AcquireRightsResponse]:
        """Acquire rights for brand content usage.

        Binding contractual request to license rights for a campaign.
        Returns credentials for generating rights-cleared content.

        Args:
            request: Request with rights_id, pricing_option_id, buyer,
                campaign, and revocation_webhook.

        Returns:
            TaskResult containing AcquireRightsResponse (acquired,
            pending_approval, rejected, or error).
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="acquire_rights",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.acquire_rights(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="acquire_rights",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, AcquireRightsResponse)

    async def update_rights(
        self,
        request: UpdateRightsRequest,
    ) -> TaskResult[UpdateRightsResponse]:
        """Update terms of an existing rights acquisition.

        Modifies a previously acquired rights record — typically to extend
        the ``end_date``, raise the ``impression_cap``, pause/unpause via
        ``paused``, or swap to a compatible ``pricing_option_id``. Partial
        update: pass only the fields you want to change.

        Failure modes (surface as ``TaskResult`` with ``success=False``):

        * Acquisition is expired or revoked — the seller rejects the update
          outright; mint a fresh ``acquire_rights`` instead.
        * ``pricing_option_id`` swap to an incompatible option — rejected;
          the new option's terms must be a strict superset / compatible
          with the original acquisition.
        * No partial-state mutations on rejection: the acquisition remains
          at its prior state when any field fails validation.

        Args:
            request: Request with ``rights_id`` and at least one mutable
                field (``end_date``, ``impression_cap``, ``paused``, or
                ``pricing_option_id``).

        Returns:
            TaskResult containing UpdateRightsResponse (updated or error).
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_rights",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.update_rights(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="update_rights",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, UpdateRightsResponse)

    # ========================================================================
    # V3 Protocol Methods - Compliance
    # ========================================================================

    async def comply_test_controller(
        self,
        request: ComplyTestControllerRequest,
    ) -> TaskResult[ComplyTestControllerResponse]:
        """Compliance test controller for sandbox testing.

        Enables sellers to simulate state transitions and delivery data
        in a sandbox environment for compliance testing.

        Args:
            request: Request specifying scenario and parameters.

        Returns:
            TaskResult containing ComplyTestControllerResponse.
        """
        operation_id = create_operation_id()
        params = request.model_dump(mode="json", exclude_none=True)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_REQUEST,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="comply_test_controller",
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        raw_result = await self.adapter.comply_test_controller(params)

        self._emit_activity(
            Activity(
                type=ActivityType.PROTOCOL_RESPONSE,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type="comply_test_controller",
                status=raw_result.status,
                timestamp=datetime.now(timezone.utc).isoformat(),
            )
        )

        return self.adapter._parse_response(raw_result, ComplyTestControllerResponse)

    async def list_tools(self) -> list[str]:
        """
        List available tools from the agent.

        Returns:
            List of tool names
        """
        return await self.adapter.list_tools()

    async def get_info(self) -> dict[str, Any]:
        """
        Get agent information including AdCP extension metadata.

        Returns agent card information including:
        - Agent name, description, version
        - Protocol type (mcp or a2a)
        - AdCP version (from extensions.adcp.adcp_version)
        - Supported protocols (from extensions.adcp.protocols_supported)
        - Available tools/skills

        Returns:
            Dictionary with agent metadata
        """
        return await self.adapter.get_agent_info()

    async def close(self) -> None:
        """Close the adapter and clean up resources."""
        if hasattr(self.adapter, "close"):
            logger.debug(f"Closing adapter for agent {self.agent_config.id}")
            await self.adapter.close()

    async def __aenter__(self) -> ADCPClient:
        """Async context manager entry."""
        return self

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        """Async context manager exit."""
        await self.close()

    def _verify_webhook_signature(
        self,
        payload: dict[str, Any],
        signature: str,
        timestamp: str,
        raw_body: bytes | str | None = None,
    ) -> bool:
        """
        Verify HMAC-SHA256 signature of webhook payload.

        The verification algorithm matches get_adcp_signed_headers_for_webhook:
        1. Constructs message as "{timestamp}.{raw_http_body_bytes}"
        2. HMAC-SHA256 signs with the shared secret
        3. Compares against the provided signature (with "sha256=" prefix stripped)
           using constant-time comparison.

        Per AdCP spec (adcontextprotocol/adcp#2478): verifiers MUST use the raw
        HTTP body bytes captured before any JSON parse; they SHOULD NOT
        re-serialize a parsed payload to reconstruct the signed bytes, because
        re-serialization silently fails against signers whose output differs in
        separator choice, key order, unicode escapes, or number formatting —
        masking signer bugs the verifier should surface. Callers that genuinely
        cannot capture raw bytes MUST fail closed.

        This implementation therefore rejects verification attempts that don't
        supply ``raw_body``. Capture it from your framework's pre-parse hook
        (FastAPI ``Request.body()``, Flask ``request.get_data(cache=True)``,
        aiohttp ``Request.read()``, Express ``express.raw()``).

        Args:
            payload: Parsed webhook payload dict (not used for signing; kept
                for signature parity with callers, but verification derives
                solely from ``raw_body``).
            signature: Signature to verify (with or without "sha256=" prefix)
            timestamp: Unix timestamp in seconds from X-AdCP-Timestamp header
            raw_body: Raw HTTP request body bytes as received on the wire,
                captured before any JSON parse. Required.

        Returns:
            True if signature is valid, False otherwise (including when
            ``raw_body`` is missing — fails closed per spec).
        """
        if not self.webhook_secret:
            logger.warning("Webhook signature verification skipped: no webhook_secret configured")
            return True

        # Fail closed per adcontextprotocol/adcp#2478: verifiers that cannot
        # capture raw bytes MUST reject, surfacing the infrastructure gap
        # rather than silently reconstructing a signed body that may diverge
        # from the bytes the signer actually hashed.
        if raw_body is None:
            logger.error(
                "Webhook signature verification failed: raw_body is required. "
                "Capture the raw HTTP body pre-parse and pass it to "
                "handle_webhook(raw_body=...). See "
                "https://adcontextprotocol.org/docs/building/implementation/security"
                "#legacy-hmac-sha256-fallback-deprecated-removed-in-40"
            )
            return False

        # Reject stale or future timestamps to prevent replay attacks
        try:
            ts = int(timestamp)
        except (ValueError, TypeError):
            return False
        now = int(time.time())
        if abs(now - ts) > self.webhook_timestamp_tolerance:
            return False

        # Strip "sha256=" prefix if present
        if signature.startswith("sha256="):
            signature = signature[7:]

        payload_str = raw_body.decode("utf-8") if isinstance(raw_body, bytes) else raw_body

        # Construct signed message: timestamp.payload
        signed_message = f"{timestamp}.{payload_str}"

        # Generate expected signature
        expected_signature = hmac.new(
            self.webhook_secret.encode("utf-8"), signed_message.encode("utf-8"), hashlib.sha256
        ).hexdigest()

        return hmac.compare_digest(signature, expected_signature)

    def _parse_webhook_result(
        self,
        task_id: str,
        task_type: str,
        operation_id: str,
        status: GeneratedTaskStatus,
        result: Any,
        timestamp: datetime | str,
        message: str | None,
        context_id: str | None,
    ) -> TaskResult[AdcpAsyncResponseData]:
        """
        Parse webhook data into typed TaskResult based on task_type.

        Args:
            task_id: Unique identifier for this task
            task_type: Task type from application routing (e.g., "get_products")
            operation_id: Operation identifier from application routing
            status: Current task status
            result: Task-specific payload (AdCP response data)
            timestamp: ISO 8601 timestamp when webhook was generated
            message: Human-readable summary of task state
            context_id: Session/conversation identifier

        Returns:
            TaskResult with task-specific typed response data

        Note:
            This method works with both MCP and A2A protocols by accepting
            protocol-agnostic parameters rather than protocol-specific objects.
        """
        from adcp.utils.response_parser import parse_json_or_text

        # Map task types to their response types (using string literals, not enum)
        # Note: Some response types are Union types (e.g., ActivateSignalResponse = Success | Error)
        response_type_map: dict[str, type[BaseModel] | Any] = {
            # Core operations
            "get_products": GetProductsResponse,
            "list_creative_formats": ListCreativeFormatsResponse,
            "sync_creatives": SyncCreativesResponse,
            "list_creatives": ListCreativesResponse,
            "build_creative": BuildCreativeResponse,
            "preview_creative": PreviewCreativeResponse,
            "create_media_buy": CreateMediaBuyResponse,
            "update_media_buy": UpdateMediaBuyResponse,
            "get_media_buy_delivery": GetMediaBuyDeliveryResponse,
            "get_media_buys": GetMediaBuysResponse,
            "get_signals": GetSignalsResponse,
            "activate_signal": ActivateSignalResponse,
            "provide_performance_feedback": ProvidePerformanceFeedbackResponse,
            "report_usage": ReportUsageResponse,
            "get_account_financials": GetAccountFinancialsResponse,
            "list_accounts": ListAccountsResponse,
            "sync_accounts": SyncAccountsResponse,
            "log_event": LogEventResponse,
            "sync_event_sources": SyncEventSourcesResponse,
            "sync_audiences": SyncAudiencesResponse,
            "sync_catalogs": SyncCatalogsResponse,
            "get_creative_delivery": GetCreativeDeliveryResponse,
            # V3 Protocol Discovery
            "get_adcp_capabilities": GetAdcpCapabilitiesResponse,
            # V3 Content Standards
            "create_content_standards": CreateContentStandardsResponse,
            "get_content_standards": GetContentStandardsResponse,
            "list_content_standards": ListContentStandardsResponse,
            "update_content_standards": UpdateContentStandardsResponse,
            "calibrate_content": CalibrateContentResponse,
            "validate_content_delivery": ValidateContentDeliveryResponse,
            "get_media_buy_artifacts": GetMediaBuyArtifactsResponse,
            # V3 Sponsored Intelligence
            "si_get_offering": SiGetOfferingResponse,
            "si_initiate_session": SiInitiateSessionResponse,
            "si_send_message": SiSendMessageResponse,
            "si_terminate_session": SiTerminateSessionResponse,
            # V3 Governance
            "get_creative_features": GetCreativeFeaturesResponse,
            "sync_plans": SyncPlansResponse,
            "check_governance": CheckGovernanceResponse,
            "report_plan_outcome": ReportPlanOutcomeResponse,
            "get_plan_audit_logs": GetPlanAuditLogsResponse,
            "create_property_list": CreatePropertyListResponse,
            "get_property_list": GetPropertyListResponse,
            "list_property_lists": ListPropertyListsResponse,
            "update_property_list": UpdatePropertyListResponse,
            "delete_property_list": DeletePropertyListResponse,
            # TMP
            "context_match": ContextMatchResponse,
            "identity_match": IdentityMatchResponse,
            # Brand Rights
            "get_brand_identity": GetBrandIdentityResponse,
            "get_rights": GetRightsResponse,
            "acquire_rights": AcquireRightsResponse,
            "update_rights": UpdateRightsResponse,
            # Compliance
            "comply_test_controller": ComplyTestControllerResponse,
        }

        # Handle completed tasks with result parsing
        if status == GeneratedTaskStatus.completed and result is not None:
            response_type = response_type_map.get(task_type)
            if response_type:
                try:
                    parsed_result: Any = parse_json_or_text(result, response_type)
                    return TaskResult[AdcpAsyncResponseData](
                        status=TaskStatus.COMPLETED,
                        data=parsed_result,
                        success=True,
                        metadata={
                            "task_id": task_id,
                            "operation_id": operation_id,
                            "timestamp": timestamp,
                            "message": message,
                        },
                    )
                except ValueError as e:
                    logger.warning(f"Failed to parse webhook result: {e}")
                    # Fall through to untyped result

        # Handle failed, input-required, or unparseable results
        # Convert status to core TaskStatus enum
        status_map = {
            GeneratedTaskStatus.completed: TaskStatus.COMPLETED,
            GeneratedTaskStatus.submitted: TaskStatus.SUBMITTED,
            GeneratedTaskStatus.working: TaskStatus.WORKING,
            GeneratedTaskStatus.failed: TaskStatus.FAILED,
            GeneratedTaskStatus.input_required: TaskStatus.NEEDS_INPUT,
        }
        task_status = status_map.get(status, TaskStatus.FAILED)

        # Extract error message from result.errors if present
        error_message: str | None = None
        if result is not None and hasattr(result, "errors"):
            errors = getattr(result, "errors", None)
            if errors and len(errors) > 0:
                first_error = errors[0]
                if hasattr(first_error, "message"):
                    error_message = first_error.message

        return TaskResult[AdcpAsyncResponseData](
            status=task_status,
            data=result,
            success=status == GeneratedTaskStatus.completed,
            error=error_message,
            metadata={
                "task_id": task_id,
                "operation_id": operation_id,
                "timestamp": timestamp,
                "message": message,
                "context_id": context_id,
            },
        )

    async def _handle_mcp_webhook(
        self,
        payload: dict[str, Any],
        task_type: str,
        operation_id: str,
        signature: str | None,
        timestamp: str | None = None,
        raw_body: bytes | str | None = None,
    ) -> TaskResult[AdcpAsyncResponseData]:
        """
        Handle MCP webhook delivered via HTTP POST.

        Args:
            payload: Webhook payload dict
            task_type: Task type from application routing
            operation_id: Operation identifier from application routing
            signature: Optional HMAC-SHA256 signature for verification (X-AdCP-Signature header)
            timestamp: Optional Unix timestamp for signature verification (X-AdCP-Timestamp header)
            raw_body: Optional raw HTTP request body for signature verification

        Returns:
            TaskResult with parsed task-specific response data

        Raises:
            ADCPWebhookSignatureError: If signature verification fails
            ValidationError: If payload doesn't match McpWebhookPayload schema
        """
        from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload

        # When a webhook_secret is configured, require signed webhooks
        if self.webhook_secret:
            if not signature or not timestamp:
                raise ADCPWebhookSignatureError(
                    "Webhook signature and timestamp headers are required"
                )
            if not self._verify_webhook_signature(payload, signature, timestamp, raw_body):
                logger.warning(
                    f"Webhook signature verification failed for agent {self.agent_config.id}"
                )
                raise ADCPWebhookSignatureError("Invalid webhook signature")

        # Validate and parse MCP webhook payload
        webhook = McpWebhookPayload.model_validate(payload)

        # Emit activity for monitoring
        self._emit_activity(
            Activity(
                type=ActivityType.WEBHOOK_RECEIVED,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type=task_type,
                timestamp=datetime.now(timezone.utc).isoformat(),
                metadata={"payload": payload, "protocol": "mcp"},
            )
        )

        # Extract fields and parse result
        return self._parse_webhook_result(
            task_id=webhook.task_id,
            task_type=task_type,
            operation_id=operation_id,
            status=webhook.status,
            result=webhook.result,
            timestamp=webhook.timestamp,
            message=webhook.message,
            context_id=webhook.context_id,
        )

    async def _handle_a2a_webhook(
        self, payload: Task | TaskStatusUpdateEvent, task_type: str, operation_id: str
    ) -> TaskResult[AdcpAsyncResponseData]:
        """
        Handle A2A webhook delivered through Task or TaskStatusUpdateEvent.

        Per A2A specification:
        - Terminated statuses (completed, failed): Payload is Task with artifacts[].parts[]
        - Intermediate statuses (working, input-required, submitted):
        Payload is TaskStatusUpdateEvent with status.message.parts[]

        Args:
            payload: A2A Task or TaskStatusUpdateEvent object
            task_type: Task type from application routing
            operation_id: Operation identifier from application routing

        Returns:
            TaskResult with parsed task-specific response data

        Note:
            Signature verification is NOT applicable for A2A webhooks
            as they arrive through authenticated A2A connections, not HTTP.
        """
        from a2a.types import DataPart, TextPart

        adcp_data: Any = None
        text_message: str | None = None
        task_id: str
        context_id: str | None
        status_state: str
        timestamp: datetime | str

        # Type detection and extraction based on payload type
        if isinstance(payload, TaskStatusUpdateEvent):
            # Intermediate status: Extract from status.message.parts[]
            task_id = payload.task_id
            context_id = payload.context_id
            status_state = payload.status.state if payload.status else "failed"
            timestamp = (
                payload.status.timestamp
                if payload.status and payload.status.timestamp
                else datetime.now(timezone.utc)
            )

            # Extract from status.message.parts[]
            if payload.status and payload.status.message and payload.status.message.parts:
                # Extract DataPart for structured AdCP payload
                data_parts = [
                    p.root for p in payload.status.message.parts if isinstance(p.root, DataPart)
                ]
                if data_parts:
                    # Use last DataPart as authoritative
                    last_data_part = data_parts[-1]
                    adcp_data = last_data_part.data

                    # Unwrap {"response": {...}} wrapper if present (ADK pattern)
                    if isinstance(adcp_data, dict) and "response" in adcp_data:
                        adcp_data = adcp_data["response"]

                # Extract TextPart for human-readable message
                for part in payload.status.message.parts:
                    if isinstance(part.root, TextPart):
                        text_message = part.root.text
                        break

        else:
            # Terminated status (Task): Extract from artifacts[].parts[]
            task_id = payload.id
            context_id = payload.context_id
            status_state = payload.status.state if payload.status else "failed"
            timestamp = (
                payload.status.timestamp
                if payload.status and payload.status.timestamp
                else datetime.now(timezone.utc)
            )

            # Extract from task.artifacts[].parts[]
            # Following A2A spec: use last artifact, last DataPart is authoritative
            if payload.artifacts:
                # Use last artifact (most recent in streaming scenarios)
                target_artifact = payload.artifacts[-1]

                if target_artifact.parts:
                    # Extract DataPart for structured AdCP payload
                    data_parts = [
                        p.root for p in target_artifact.parts if isinstance(p.root, DataPart)
                    ]
                    if data_parts:
                        # Use last DataPart as authoritative
                        last_data_part = data_parts[-1]
                        adcp_data = last_data_part.data

                        # Unwrap {"response": {...}} wrapper if present (ADK pattern)
                        if isinstance(adcp_data, dict) and "response" in adcp_data:
                            adcp_data = adcp_data["response"]

                    # Extract TextPart for human-readable message
                    for part in target_artifact.parts:
                        if isinstance(part.root, TextPart):
                            text_message = part.root.text
                            break

        # Map A2A status.state to GeneratedTaskStatus enum
        status_map = {
            "completed": GeneratedTaskStatus.completed,
            "submitted": GeneratedTaskStatus.submitted,
            "working": GeneratedTaskStatus.working,
            "failed": GeneratedTaskStatus.failed,
            "input-required": GeneratedTaskStatus.input_required,
            "input_required": GeneratedTaskStatus.input_required,  # Handle both formats
        }
        mapped_status = status_map.get(status_state, GeneratedTaskStatus.failed)

        # Emit activity for monitoring
        self._emit_activity(
            Activity(
                type=ActivityType.WEBHOOK_RECEIVED,
                operation_id=operation_id,
                agent_id=self.agent_config.id,
                task_type=task_type,
                timestamp=datetime.now(timezone.utc).isoformat(),
                metadata={
                    "task_id": task_id,
                    "protocol": "a2a",
                    "payload_type": (
                        "TaskStatusUpdateEvent"
                        if isinstance(payload, TaskStatusUpdateEvent)
                        else "Task"
                    ),
                },
            )
        )

        # Parse and return typed result by passing extracted fields directly
        return self._parse_webhook_result(
            task_id=task_id,
            task_type=task_type,
            operation_id=operation_id,
            status=mapped_status,
            result=adcp_data,
            timestamp=timestamp,
            message=text_message,
            context_id=context_id,
        )

    async def handle_webhook(
        self,
        payload: dict[str, Any] | Task | TaskStatusUpdateEvent,
        task_type: str,
        operation_id: str,
        signature: str | None = None,
        timestamp: str | None = None,
        raw_body: bytes | str | None = None,
    ) -> TaskResult[AdcpAsyncResponseData]:
        """
        Handle incoming webhook and return typed result.

        This method provides a unified interface for handling webhooks from both
        MCP and A2A protocols:

        - MCP Webhooks: HTTP POST with dict payload, optional HMAC signature
        - A2A Webhooks: Task or TaskStatusUpdateEvent objects based on status

        The method automatically detects the protocol type and routes to the
        appropriate handler. Both protocols return a consistent TaskResult
        structure with typed AdCP response data.

        Args:
            payload: Webhook payload - one of:
                - dict[str, Any]: MCP webhook payload from HTTP POST
                - Task: A2A webhook for terminated statuses (completed, failed)
                - TaskStatusUpdateEvent: A2A webhook for intermediate statuses
                  (working, input-required, submitted)
            task_type: Task type from application routing (e.g., "get_products").
                Applications should extract this from URL routing pattern:
                /webhook/{task_type}/{agent_id}/{operation_id}
            operation_id: Operation identifier from application routing.
                Used to correlate webhook notifications with original task submission.
            signature: Optional HMAC-SHA256 signature for MCP webhook verification
                (X-AdCP-Signature header). Ignored for A2A webhooks.
            timestamp: Optional Unix timestamp (seconds) for MCP webhook signature
                verification (X-AdCP-Timestamp header). Required when signature is provided.
            raw_body: Optional raw HTTP request body bytes for signature verification.
                When provided, used directly instead of re-serializing the payload,
                avoiding cross-language JSON serialization mismatches. Strongly
                recommended for production use.

        Returns:
            TaskResult with parsed task-specific response data. The structure
            is identical regardless of protocol.

        Raises:
            ADCPWebhookSignatureError: If MCP signature verification fails
            ValidationError: If MCP payload doesn't match WebhookPayload schema

        Note:
            task_type and operation_id were deprecated from the webhook payload
            per AdCP specification. Applications must extract these from URL
            routing and pass them explicitly.

        Examples:
            MCP webhook (HTTP endpoint):
            >>> @app.post("/webhook/{task_type}/{agent_id}/{operation_id}")
            >>> async def webhook_handler(task_type: str, operation_id: str, request: Request):
            >>>     raw_body = await request.body()
            >>>     payload = json.loads(raw_body)
            >>>     signature = request.headers.get("X-AdCP-Signature")
            >>>     timestamp = request.headers.get("X-AdCP-Timestamp")
            >>>     result = await client.handle_webhook(
            >>>         payload, task_type, operation_id, signature, timestamp,
            >>>         raw_body=raw_body,
            >>>     )
            >>>     if result.success:
            >>>         print(f"Task completed: {result.data}")

            A2A webhook with Task (terminated status):
            >>> async def on_task_completed(task: Task):
            >>>     # Extract task_type and operation_id from your app's task tracking
            >>>     task_type = your_task_registry.get_type(task.id)
            >>>     operation_id = your_task_registry.get_operation_id(task.id)
            >>>     result = await client.handle_webhook(
            >>>         task, task_type, operation_id
            >>>     )
            >>>     if result.success:
            >>>         print(f"Task completed: {result.data}")

            A2A webhook with TaskStatusUpdateEvent (intermediate status):
            >>> async def on_task_update(event: TaskStatusUpdateEvent):
            >>>     # Extract task_type and operation_id from your app's task tracking
            >>>     task_type = your_task_registry.get_type(event.task_id)
            >>>     operation_id = your_task_registry.get_operation_id(event.task_id)
            >>>     result = await client.handle_webhook(
            >>>         event, task_type, operation_id
            >>>     )
            >>>     if result.status == GeneratedTaskStatus.working:
            >>>         print(f"Task still working: {result.metadata.get('message')}")
        """
        # Detect protocol type and route to appropriate handler
        if isinstance(payload, (Task, TaskStatusUpdateEvent)):
            # A2A webhook (Task or TaskStatusUpdateEvent)
            return await self._handle_a2a_webhook(payload, task_type, operation_id)
        else:
            # MCP webhook (dict payload)
            return await self._handle_mcp_webhook(
                payload, task_type, operation_id, signature, timestamp, raw_body
            )

Client for interacting with a single AdCP agent.

Initialize ADCP client for a single agent.

Args

agent_config
Agent configuration
webhook_url_template
Template for webhook URLs with {agent_id}, {task_type}, {operation_id}
webhook_secret
Secret for webhook signature verification
on_activity
Callback for activity events
webhook_timestamp_tolerance
Maximum age (in seconds) for webhook timestamps. Webhooks with timestamps older than this or more than this far in the future are rejected. Defaults to 300 (5 minutes).
capabilities_ttl
Time-to-live in seconds for cached capabilities (default: 1 hour)
validate_features
When True, automatically check that the seller supports required features before making task calls (e.g., sync_audiences requires audience_targeting). Requires capabilities to have been fetched first.
strict_idempotency
When True, verify the seller declared adcp.idempotency.replay_ttl_seconds in capabilities before any mutating call. Fetches capabilities lazily on first use. Raises IdempotencyUnsupportedError if the declaration is missing — sellers that don't declare it provide no retry-safety guarantee per AdCP #2315. Defaults to False for backward compatibility.
signing
Optional RFC 9421 request-signing config. When provided, the client automatically attaches Signature / Signature-Input / Content-Digest headers to operations the seller's request_signing capability lists in required_for, warn_for, or supported_for. The seller's covers_content_digest policy determines whether the body is bound to the signature. Generate a key with adcp-keygen and publish the public JWK at your jwks_uri. Supported on both A2A and MCP (mcp_transport="streamable_http"); SSE-transport MCP logs a warning and falls through unsigned.

Instance variables

prop capabilities : GetAdcpCapabilitiesResponse | None
Expand source code
@property
def capabilities(self) -> GetAdcpCapabilitiesResponse | None:
    """Return cached capabilities, or None if not yet fetched."""
    return self._capabilities

Return cached capabilities, or None if not yet fetched.

prop feature_resolverFeatureResolver | None
Expand source code
@property
def feature_resolver(self) -> FeatureResolver | None:
    """Return the FeatureResolver for cached capabilities, or None."""
    return self._feature_resolver

Return the FeatureResolver for cached capabilities, or None.

Methods

async def acquire_rights(self,
request: AcquireRightsRequest) ‑> TaskResult[Union[AcquireRightsResponse1AcquireRightsResponse2AcquireRightsResponse3AcquireRightsResponse4]]
Expand source code
async def acquire_rights(
    self,
    request: AcquireRightsRequest,
) -> TaskResult[AcquireRightsResponse]:
    """Acquire rights for brand content usage.

    Binding contractual request to license rights for a campaign.
    Returns credentials for generating rights-cleared content.

    Args:
        request: Request with rights_id, pricing_option_id, buyer,
            campaign, and revocation_webhook.

    Returns:
        TaskResult containing AcquireRightsResponse (acquired,
        pending_approval, rejected, or error).
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="acquire_rights",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.acquire_rights(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="acquire_rights",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, AcquireRightsResponse)

Acquire rights for brand content usage.

Binding contractual request to license rights for a campaign. Returns credentials for generating rights-cleared content.

Args

request
Request with rights_id, pricing_option_id, buyer, campaign, and revocation_webhook.

Returns

TaskResult containing AcquireRightsResponse (acquired, pending_approval, rejected, or error).

async def activate_signal(self,
request: ActivateSignalRequest) ‑> TaskResult[Union[ActivateSignalResponse1ActivateSignalResponse2]]
Expand source code
async def activate_signal(
    self,
    request: ActivateSignalRequest,
) -> TaskResult[ActivateSignalResponse]:
    """
    Activate Signal.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing ActivateSignalResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="activate_signal",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.activate_signal(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="activate_signal",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ActivateSignalResponse)

Activate Signal.

Args

request
Request parameters

Returns

TaskResult containing ActivateSignalResponse

async def build_creative(self,
request: BuildCreativeRequest) ‑> TaskResult[Union[BuildCreativeResponse1BuildCreativeResponse2, BuildCreativeResponse3]]
Expand source code
async def build_creative(
    self,
    request: BuildCreativeRequest,
) -> TaskResult[BuildCreativeResponse]:
    """
    Generate production-ready creative assets.

    Requests the creative agent to build final deliverable assets in the target
    format (e.g., VAST, DAAST, HTML5). This is typically called after previewing
    and approving a creative manifest.

    Args:
        request: Creative build parameters including:
            - manifest: Creative manifest with brand info and content
            - target_format_id: Desired output format identifier
            - inputs: Optional user-provided inputs for template variables
            - deployment: Platform or agent deployment configuration

    Returns:
        TaskResult containing BuildCreativeResponse with:
            - assets: Production-ready creative files (URLs or inline content)
            - format_id: The generated format identifier
            - manifest: The creative manifest used for generation
            - metadata: Additional platform-specific details

    Example:
        >>> from adcp import ADCPClient, BuildCreativeRequest
        >>> client = ADCPClient(agent_config)
        >>> request = BuildCreativeRequest(
        ...     manifest=creative_manifest,
        ...     target_format_id="vast_2.0",
        ...     inputs={"duration": 30}
        ... )
        >>> result = await client.build_creative(request)
        >>> if result.success:
        ...     vast_url = result.data.assets[0].url
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="build_creative",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.build_creative(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="build_creative",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, BuildCreativeResponse)

Generate production-ready creative assets.

Requests the creative agent to build final deliverable assets in the target format (e.g., VAST, DAAST, HTML5). This is typically called after previewing and approving a creative manifest.

Args

request
Creative build parameters including: - manifest: Creative manifest with brand info and content - target_format_id: Desired output format identifier - inputs: Optional user-provided inputs for template variables - deployment: Platform or agent deployment configuration

Returns

TaskResult containing BuildCreativeResponse with: - assets: Production-ready creative files (URLs or inline content) - format_id: The generated format identifier - manifest: The creative manifest used for generation - metadata: Additional platform-specific details

Example

>>> from adcp import ADCPClient, BuildCreativeRequest
>>> client = ADCPClient(agent_config)
>>> request = BuildCreativeRequest(
...     manifest=creative_manifest,
...     target_format_id="vast_2.0",
...     inputs={"duration": 30}
... )
>>> result = await client.build_creative(request)
>>> if result.success:
...     vast_url = result.data.assets[0].url
async def calibrate_content(self, request: CalibrateContentRequest) ‑> TaskResult[Union[CalibrateContentResponse1CalibrateContentResponse2]]
Expand source code
async def calibrate_content(
    self,
    request: CalibrateContentRequest,
) -> TaskResult[CalibrateContentResponse]:
    """
    Calibrate content against standards.

    Evaluates content (artifact or URL) against configured standards to
    determine suitability for ad placement.

    Args:
        request: Request parameters including content to evaluate

    Returns:
        TaskResult containing CalibrateContentResponse with verdict
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="calibrate_content",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.calibrate_content(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="calibrate_content",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, CalibrateContentResponse)

Calibrate content against standards.

Evaluates content (artifact or URL) against configured standards to determine suitability for ad placement.

Args

request
Request parameters including content to evaluate

Returns

TaskResult containing CalibrateContentResponse with verdict

async def check_governance(self,
request: CheckGovernanceRequest) ‑> TaskResult[CheckGovernanceResponse]
Expand source code
async def check_governance(
    self,
    request: CheckGovernanceRequest,
) -> TaskResult[CheckGovernanceResponse]:
    """Check a proposed or committed action against campaign governance."""
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="check_governance",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.check_governance(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="check_governance",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, CheckGovernanceResponse)

Check a proposed or committed action against campaign governance.

async def close(self) ‑> None
Expand source code
async def close(self) -> None:
    """Close the adapter and clean up resources."""
    if hasattr(self.adapter, "close"):
        logger.debug(f"Closing adapter for agent {self.agent_config.id}")
        await self.adapter.close()

Close the adapter and clean up resources.

async def comply_test_controller(self,
request: ComplyTestControllerRequest) ‑> TaskResult[Union[ComplyTestControllerResponse1, ComplyTestControllerResponse2, ComplyTestControllerResponse3, ComplyTestControllerResponse4]]
Expand source code
async def comply_test_controller(
    self,
    request: ComplyTestControllerRequest,
) -> TaskResult[ComplyTestControllerResponse]:
    """Compliance test controller for sandbox testing.

    Enables sellers to simulate state transitions and delivery data
    in a sandbox environment for compliance testing.

    Args:
        request: Request specifying scenario and parameters.

    Returns:
        TaskResult containing ComplyTestControllerResponse.
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="comply_test_controller",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.comply_test_controller(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="comply_test_controller",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ComplyTestControllerResponse)

Compliance test controller for sandbox testing.

Enables sellers to simulate state transitions and delivery data in a sandbox environment for compliance testing.

Args

request
Request specifying scenario and parameters.

Returns

TaskResult containing ComplyTestControllerResponse.

async def context_match(self,
request: ContextMatchRequest) ‑> TaskResult[ContextMatchResponse]
Expand source code
async def context_match(
    self,
    request: ContextMatchRequest,
) -> TaskResult[ContextMatchResponse]:
    """Match ad context to buyer packages.

    Evaluates contextual signals for a publisher placement against the
    buyer's active packages and returns matching offers.

    Args:
        request: Context match request with placement, property, and
            optional artifact refs, context signals, and geo data.

    Returns:
        TaskResult containing ContextMatchResponse with offers.
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True, by_alias=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="context_match",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.context_match(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="context_match",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ContextMatchResponse)

Match ad context to buyer packages.

Evaluates contextual signals for a publisher placement against the buyer's active packages and returns matching offers.

Args

request
Context match request with placement, property, and optional artifact refs, context signals, and geo data.

Returns

TaskResult containing ContextMatchResponse with offers.

async def create_collection_list(self, request: CreateCollectionListRequest) ‑> TaskResult[CreateCollectionListResponse]
Expand source code
async def create_collection_list(
    self,
    request: CreateCollectionListRequest,
) -> TaskResult[CreateCollectionListResponse]:
    """Create a collection list for governance filtering.

    Collection lists define dynamic sets of collections (properties, segments, etc.)
    that can be referenced by authorization rules and audience scoping.

    Args:
        request: Request parameters for creating the collection list

    Returns:
        TaskResult containing CreateCollectionListResponse with list_id
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="create_collection_list",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.create_collection_list(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="create_collection_list",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, CreateCollectionListResponse)

Create a collection list for governance filtering.

Collection lists define dynamic sets of collections (properties, segments, etc.) that can be referenced by authorization rules and audience scoping.

Args

request
Request parameters for creating the collection list

Returns

TaskResult containing CreateCollectionListResponse with list_id

async def create_content_standards(self, request: CreateContentStandardsRequest) ‑> TaskResult[Union[CreateContentStandardsResponse1CreateContentStandardsResponse2]]
Expand source code
async def create_content_standards(
    self,
    request: CreateContentStandardsRequest,
) -> TaskResult[CreateContentStandardsResponse]:
    """
    Create a new content standards configuration.

    Defines acceptable content contexts for ad placement using natural
    language policy and optional calibration exemplars.

    Args:
        request: Request parameters including policy and scope

    Returns:
        TaskResult containing CreateContentStandardsResponse with standards_id
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="create_content_standards",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.create_content_standards(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="create_content_standards",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, CreateContentStandardsResponse)

Create a new content standards configuration.

Defines acceptable content contexts for ad placement using natural language policy and optional calibration exemplars.

Args

request
Request parameters including policy and scope

Returns

TaskResult containing CreateContentStandardsResponse with standards_id

async def create_media_buy(self,
request: CreateMediaBuyRequest) ‑> TaskResult[Union[CreateMediaBuyResponse1CreateMediaBuyResponse2, CreateMediaBuyResponse3]]
Expand source code
async def create_media_buy(
    self,
    request: CreateMediaBuyRequest,
) -> TaskResult[CreateMediaBuyResponse]:
    """
    Create a new media buy reservation.

    Requests the agent to reserve inventory for a campaign. The agent returns a
    media_buy_id that tracks this reservation and can be used for updates.

    Args:
        request: Media buy creation parameters including:
            - brand: Brand reference; resolved from brand.json or the registry at execution
            - packages: List of package requests specifying desired inventory
            - publisher_properties: Target properties for ad placement
            - budget: Optional budget constraints
            - start_date/end_date: Campaign flight dates

    Returns:
        TaskResult containing CreateMediaBuyResponse with:
            - media_buy_id: Unique identifier for this reservation
            - status: Current state of the media buy
            - packages: Confirmed package details
            - Additional platform-specific metadata

    Example:
        >>> from adcp import ADCPClient, CreateMediaBuyRequest, BrandReference
        >>> client = ADCPClient(agent_config)
        >>> request = CreateMediaBuyRequest(
        ...     brand=BrandReference(domain="acme.com"),
        ...     packages=[package_request],
        ...     publisher_properties=properties,
        ... )
        >>> result = await client.create_media_buy(request)
        >>> if result.success:
        ...     media_buy_id = result.data.media_buy_id
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="create_media_buy",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.create_media_buy(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="create_media_buy",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, CreateMediaBuyResponse)

Create a new media buy reservation.

Requests the agent to reserve inventory for a campaign. The agent returns a media_buy_id that tracks this reservation and can be used for updates.

Args

request
Media buy creation parameters including: - brand: Brand reference; resolved from brand.json or the registry at execution - packages: List of package requests specifying desired inventory - publisher_properties: Target properties for ad placement - budget: Optional budget constraints - start_date/end_date: Campaign flight dates

Returns

TaskResult containing CreateMediaBuyResponse with: - media_buy_id: Unique identifier for this reservation - status: Current state of the media buy - packages: Confirmed package details - Additional platform-specific metadata

Example

>>> from adcp import ADCPClient, CreateMediaBuyRequest, BrandReference
>>> client = ADCPClient(agent_config)
>>> request = CreateMediaBuyRequest(
...     brand=BrandReference(domain="acme.com"),
...     packages=[package_request],
...     publisher_properties=properties,
... )
>>> result = await client.create_media_buy(request)
>>> if result.success:
...     media_buy_id = result.data.media_buy_id
async def create_property_list(self, request: CreatePropertyListRequest) ‑> TaskResult[CreatePropertyListResponse]
Expand source code
async def create_property_list(
    self,
    request: CreatePropertyListRequest,
) -> TaskResult[CreatePropertyListResponse]:
    """
    Create a property list for governance filtering.

    Property lists define dynamic sets of properties based on filters,
    brand manifests, and feature requirements.

    Args:
        request: Request parameters for creating the property list

    Returns:
        TaskResult containing CreatePropertyListResponse with list_id
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="create_property_list",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.create_property_list(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="create_property_list",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, CreatePropertyListResponse)

Create a property list for governance filtering.

Property lists define dynamic sets of properties based on filters, brand manifests, and feature requirements.

Args

request
Request parameters for creating the property list

Returns

TaskResult containing CreatePropertyListResponse with list_id

async def delete_collection_list(self, request: DeleteCollectionListRequest) ‑> TaskResult[DeleteCollectionListResponse]
Expand source code
async def delete_collection_list(
    self,
    request: DeleteCollectionListRequest,
) -> TaskResult[DeleteCollectionListResponse]:
    """Delete a collection list.

    Args:
        request: Request parameters with list_id

    Returns:
        TaskResult containing DeleteCollectionListResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="delete_collection_list",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.delete_collection_list(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="delete_collection_list",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, DeleteCollectionListResponse)

Delete a collection list.

Args

request
Request parameters with list_id

Returns

TaskResult containing DeleteCollectionListResponse

async def delete_property_list(self, request: DeletePropertyListRequest) ‑> TaskResult[DeletePropertyListResponse]
Expand source code
async def delete_property_list(
    self,
    request: DeletePropertyListRequest,
) -> TaskResult[DeletePropertyListResponse]:
    """
    Delete a property list.

    Removes a property list. Any active subscriptions to this list
    will be terminated.

    Args:
        request: Request parameters with list_id

    Returns:
        TaskResult containing DeletePropertyListResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="delete_property_list",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.delete_property_list(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="delete_property_list",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, DeletePropertyListResponse)

Delete a property list.

Removes a property list. Any active subscriptions to this list will be terminated.

Args

request
Request parameters with list_id

Returns

TaskResult containing DeletePropertyListResponse

async def fetch_capabilities(self) ‑> GetAdcpCapabilitiesResponse
Expand source code
async def fetch_capabilities(self) -> GetAdcpCapabilitiesResponse:
    """Fetch capabilities, using cache if still valid.

    Returns:
        The seller's capabilities response.
    """
    if self._capabilities is not None and self._capabilities_fetched_at is not None:
        elapsed = time.monotonic() - self._capabilities_fetched_at
        if elapsed < self.capabilities_ttl:
            return self._capabilities

    return await self.refresh_capabilities()

Fetch capabilities, using cache if still valid.

Returns

The seller's capabilities response.

async def get_account_financials(self,
request: GetAccountFinancialsRequest) ‑> TaskResult[Union[GetAccountFinancialsResponse1GetAccountFinancialsResponse2]]
Expand source code
async def get_account_financials(
    self,
    request: GetAccountFinancialsRequest,
) -> TaskResult[GetAccountFinancialsResponse]:
    """
    Get Account Financials.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing GetAccountFinancialsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_account_financials",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_account_financials(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_account_financials",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetAccountFinancialsResponse)

Get Account Financials.

Args

request
Request parameters

Returns

TaskResult containing GetAccountFinancialsResponse

async def get_adcp_capabilities(self, request: GetAdcpCapabilitiesRequest) ‑> TaskResult[GetAdcpCapabilitiesResponse]
Expand source code
async def get_adcp_capabilities(
    self,
    request: GetAdcpCapabilitiesRequest,
) -> TaskResult[GetAdcpCapabilitiesResponse]:
    """
    Get AdCP capabilities from the agent.

    Queries the agent's supported AdCP features, protocol versions, and
    domain-specific capabilities (media_buy, signals, sponsored_intelligence).

    Args:
        request: Request parameters including optional protocol filters

    Returns:
        TaskResult containing GetAdcpCapabilitiesResponse with:
            - adcp: Core protocol version information
            - supported_protocols: List of supported domain protocols
            - media_buy: Media buy capabilities (if supported)
            - sponsored_intelligence: SI capabilities (if supported)
            - signals: Signals capabilities (if supported)
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_adcp_capabilities",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_adcp_capabilities(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_adcp_capabilities",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetAdcpCapabilitiesResponse)

Get AdCP capabilities from the agent.

Queries the agent's supported AdCP features, protocol versions, and domain-specific capabilities (media_buy, signals, sponsored_intelligence).

Args

request
Request parameters including optional protocol filters

Returns

TaskResult containing GetAdcpCapabilitiesResponse with: - adcp: Core protocol version information - supported_protocols: List of supported domain protocols - media_buy: Media buy capabilities (if supported) - sponsored_intelligence: SI capabilities (if supported) - signals: Signals capabilities (if supported)

async def get_brand_identity(self,
request: GetBrandIdentityRequest) ‑> TaskResult[Union[GetBrandIdentityResponse1GetBrandIdentityResponse2]]
Expand source code
async def get_brand_identity(
    self,
    request: GetBrandIdentityRequest,
) -> TaskResult[GetBrandIdentityResponse]:
    """Get brand identity information.

    Retrieves brand identity data including logos, colors, fonts,
    voice synthesis config, and rights availability.

    Args:
        request: Request with brand_id and optional fields filter.

    Returns:
        TaskResult containing GetBrandIdentityResponse.
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_brand_identity",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_brand_identity(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_brand_identity",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetBrandIdentityResponse)

Get brand identity information.

Retrieves brand identity data including logos, colors, fonts, voice synthesis config, and rights availability.

Args

request
Request with brand_id and optional fields filter.

Returns

TaskResult containing GetBrandIdentityResponse.

async def get_collection_list(self, request: GetCollectionListRequest) ‑> TaskResult[GetCollectionListResponse]
Expand source code
async def get_collection_list(
    self,
    request: GetCollectionListRequest,
) -> TaskResult[GetCollectionListResponse]:
    """Get a collection list with optional resolution.

    When resolve=true, returns the resolved members of the collection list.

    Args:
        request: Request parameters including list_id and resolve flag

    Returns:
        TaskResult containing GetCollectionListResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_collection_list",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_collection_list(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_collection_list",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetCollectionListResponse)

Get a collection list with optional resolution.

When resolve=true, returns the resolved members of the collection list.

Args

request
Request parameters including list_id and resolve flag

Returns

TaskResult containing GetCollectionListResponse

async def get_content_standards(self, request: GetContentStandardsRequest) ‑> TaskResult[Union[GetContentStandardsResponse1GetContentStandardsResponse2]]
Expand source code
async def get_content_standards(
    self,
    request: GetContentStandardsRequest,
) -> TaskResult[GetContentStandardsResponse]:
    """
    Get a content standards configuration by ID.

    Args:
        request: Request parameters including standards_id

    Returns:
        TaskResult containing GetContentStandardsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_content_standards",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_content_standards(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_content_standards",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetContentStandardsResponse)

Get a content standards configuration by ID.

Args

request
Request parameters including standards_id

Returns

TaskResult containing GetContentStandardsResponse

async def get_creative_delivery(self,
request: GetCreativeDeliveryRequest) ‑> TaskResult[GetCreativeDeliveryResponse]
Expand source code
async def get_creative_delivery(
    self,
    request: GetCreativeDeliveryRequest,
) -> TaskResult[GetCreativeDeliveryResponse]:
    """
    Get Creative Delivery.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing GetCreativeDeliveryResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_creative_delivery",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_creative_delivery(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_creative_delivery",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetCreativeDeliveryResponse)

Get Creative Delivery.

Args

request
Request parameters

Returns

TaskResult containing GetCreativeDeliveryResponse

async def get_creative_features(self,
request: GetCreativeFeaturesRequest) ‑> TaskResult[Union[GetCreativeFeaturesResponse1GetCreativeFeaturesResponse2]]
Expand source code
async def get_creative_features(
    self,
    request: GetCreativeFeaturesRequest,
) -> TaskResult[GetCreativeFeaturesResponse]:
    """Evaluate governance features for a creative manifest."""
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_creative_features",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_creative_features(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_creative_features",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetCreativeFeaturesResponse)

Evaluate governance features for a creative manifest.

async def get_info(self) ‑> dict[str, typing.Any]
Expand source code
async def get_info(self) -> dict[str, Any]:
    """
    Get agent information including AdCP extension metadata.

    Returns agent card information including:
    - Agent name, description, version
    - Protocol type (mcp or a2a)
    - AdCP version (from extensions.adcp.adcp_version)
    - Supported protocols (from extensions.adcp.protocols_supported)
    - Available tools/skills

    Returns:
        Dictionary with agent metadata
    """
    return await self.adapter.get_agent_info()

Get agent information including AdCP extension metadata.

Returns agent card information including: - Agent name, description, version - Protocol type (mcp or a2a) - AdCP version (from extensions.adcp.adcp_version) - Supported protocols (from extensions.adcp.protocols_supported) - Available tools/skills

Returns

Dictionary with agent metadata

async def get_media_buy_artifacts(self, request: GetMediaBuyArtifactsRequest) ‑> TaskResult[Union[GetMediaBuyArtifactsResponse1GetMediaBuyArtifactsResponse2]]
Expand source code
async def get_media_buy_artifacts(
    self,
    request: GetMediaBuyArtifactsRequest,
) -> TaskResult[GetMediaBuyArtifactsResponse]:
    """
    Get artifacts associated with a media buy.

    Retrieves content artifacts where ads were delivered for a media buy.

    Args:
        request: Request parameters including media_buy_id

    Returns:
        TaskResult containing GetMediaBuyArtifactsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_media_buy_artifacts",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_media_buy_artifacts(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_media_buy_artifacts",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetMediaBuyArtifactsResponse)

Get artifacts associated with a media buy.

Retrieves content artifacts where ads were delivered for a media buy.

Args

request
Request parameters including media_buy_id

Returns

TaskResult containing GetMediaBuyArtifactsResponse

async def get_media_buy_delivery(self,
request: GetMediaBuyDeliveryRequest) ‑> TaskResult[GetMediaBuyDeliveryResponse]
Expand source code
async def get_media_buy_delivery(
    self,
    request: GetMediaBuyDeliveryRequest,
) -> TaskResult[GetMediaBuyDeliveryResponse]:
    """
    Get Media Buy Delivery.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing GetMediaBuyDeliveryResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_media_buy_delivery",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_media_buy_delivery(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_media_buy_delivery",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetMediaBuyDeliveryResponse)

Get Media Buy Delivery.

Args

request
Request parameters

Returns

TaskResult containing GetMediaBuyDeliveryResponse

async def get_media_buys(self,
request: GetMediaBuysRequest) ‑> TaskResult[GetMediaBuysResponse]
Expand source code
async def get_media_buys(
    self,
    request: GetMediaBuysRequest,
) -> TaskResult[GetMediaBuysResponse]:
    """
    Get Media Buys.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing GetMediaBuysResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_media_buys",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_media_buys(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_media_buys",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetMediaBuysResponse)

Get Media Buys.

Args

request
Request parameters

Returns

TaskResult containing GetMediaBuysResponse

async def get_plan_audit_logs(self,
request: GetPlanAuditLogsRequest) ‑> TaskResult[GetPlanAuditLogsResponse]
Expand source code
async def get_plan_audit_logs(
    self,
    request: GetPlanAuditLogsRequest,
) -> TaskResult[GetPlanAuditLogsResponse]:
    """Retrieve governance state and audit logs for one or more plans."""
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_plan_audit_logs",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_plan_audit_logs(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_plan_audit_logs",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetPlanAuditLogsResponse)

Retrieve governance state and audit logs for one or more plans.

async def get_products(self,
request: GetProductsRequest,
fetch_previews: bool = False,
preview_output_format: str = 'url',
creative_agent_client: ADCPClient | None = None) ‑> TaskResult[GetProductsResponse]
Expand source code
async def get_products(
    self,
    request: GetProductsRequest,
    fetch_previews: bool = False,
    preview_output_format: str = "url",
    creative_agent_client: ADCPClient | None = None,
) -> TaskResult[GetProductsResponse]:
    """
    Get advertising products.

    Args:
        request: Request parameters
        fetch_previews: If True, generate preview URLs for each product's formats
            (uses batch API for 5-10x performance improvement)
        preview_output_format: "url" for iframe URLs (default), "html" for direct
            embedding (2-3x faster, no iframe overhead)
        creative_agent_client: Client for creative agent (required if
            fetch_previews=True)

    Returns:
        TaskResult containing GetProductsResponse with optional preview URLs in metadata

    Raises:
        ValueError: If fetch_previews=True but creative_agent_client is not provided
    """
    if fetch_previews and not creative_agent_client:
        raise ValueError("creative_agent_client is required when fetch_previews=True")

    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_products",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_products(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_products",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    result: TaskResult[GetProductsResponse] = self.adapter._parse_response(
        raw_result, GetProductsResponse
    )

    if fetch_previews and result.success and result.data and creative_agent_client:
        from adcp.utils.preview_cache import add_preview_urls_to_products

        products_with_previews = await add_preview_urls_to_products(
            result.data.products,
            creative_agent_client,
            use_batch=True,
            output_format=preview_output_format,
        )
        result.metadata = result.metadata or {}
        result.metadata["products_with_previews"] = products_with_previews

    return result

Get advertising products.

Args

request
Request parameters
fetch_previews
If True, generate preview URLs for each product's formats (uses batch API for 5-10x performance improvement)
preview_output_format
"url" for iframe URLs (default), "html" for direct embedding (2-3x faster, no iframe overhead)
creative_agent_client
Client for creative agent (required if fetch_previews=True)

Returns

TaskResult containing GetProductsResponse with optional preview URLs in metadata

Raises

ValueError
If fetch_previews=True but creative_agent_client is not provided
async def get_property_list(self, request: GetPropertyListRequest) ‑> TaskResult[GetPropertyListResponse]
Expand source code
async def get_property_list(
    self,
    request: GetPropertyListRequest,
) -> TaskResult[GetPropertyListResponse]:
    """
    Get a property list with optional resolution.

    When resolve=true, returns the list of resolved property identifiers.
    Use this to get the actual properties that match the list's filters.

    Args:
        request: Request parameters including list_id and resolve flag

    Returns:
        TaskResult containing GetPropertyListResponse with identifiers
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_property_list",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_property_list(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_property_list",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetPropertyListResponse)

Get a property list with optional resolution.

When resolve=true, returns the list of resolved property identifiers. Use this to get the actual properties that match the list's filters.

Args

request
Request parameters including list_id and resolve flag

Returns

TaskResult containing GetPropertyListResponse with identifiers

async def get_rights(self,
request: GetRightsRequest) ‑> TaskResult[Union[GetRightsResponse1GetRightsResponse2]]
Expand source code
async def get_rights(
    self,
    request: GetRightsRequest,
) -> TaskResult[GetRightsResponse]:
    """Get available rights for licensing.

    Searches for rights offerings using natural language query and
    filters by type, uses, countries, and buyer compatibility.

    Args:
        request: Request with query, uses, and optional filters.

    Returns:
        TaskResult containing GetRightsResponse with matched rights.
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_rights",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_rights(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_rights",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetRightsResponse)

Get available rights for licensing.

Searches for rights offerings using natural language query and filters by type, uses, countries, and buyer compatibility.

Args

request
Request with query, uses, and optional filters.

Returns

TaskResult containing GetRightsResponse with matched rights.

async def get_signals(self,
request: GetSignalsRequest) ‑> TaskResult[GetSignalsResponse]
Expand source code
async def get_signals(
    self,
    request: GetSignalsRequest,
) -> TaskResult[GetSignalsResponse]:
    """
    Get Signals.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing GetSignalsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_signals",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.get_signals(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="get_signals",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, GetSignalsResponse)

Get Signals.

Args

request
Request parameters

Returns

TaskResult containing GetSignalsResponse

def get_webhook_url(self, task_type: str, operation_id: str) ‑> str
Expand source code
def get_webhook_url(self, task_type: str, operation_id: str) -> str:
    """Generate webhook URL for a task."""
    if not self.webhook_url_template:
        raise ValueError("webhook_url_template not configured")

    return self.webhook_url_template.format(
        agent_id=self.agent_config.id,
        task_type=task_type,
        operation_id=operation_id,
    )

Generate webhook URL for a task.

async def handle_webhook(self,
payload: dict[str, Any] | Task | TaskStatusUpdateEvent,
task_type: str,
operation_id: str,
signature: str | None = None,
timestamp: str | None = None,
raw_body: bytes | str | None = None) ‑> TaskResult[AdcpAsyncResponseData]
Expand source code
async def handle_webhook(
    self,
    payload: dict[str, Any] | Task | TaskStatusUpdateEvent,
    task_type: str,
    operation_id: str,
    signature: str | None = None,
    timestamp: str | None = None,
    raw_body: bytes | str | None = None,
) -> TaskResult[AdcpAsyncResponseData]:
    """
    Handle incoming webhook and return typed result.

    This method provides a unified interface for handling webhooks from both
    MCP and A2A protocols:

    - MCP Webhooks: HTTP POST with dict payload, optional HMAC signature
    - A2A Webhooks: Task or TaskStatusUpdateEvent objects based on status

    The method automatically detects the protocol type and routes to the
    appropriate handler. Both protocols return a consistent TaskResult
    structure with typed AdCP response data.

    Args:
        payload: Webhook payload - one of:
            - dict[str, Any]: MCP webhook payload from HTTP POST
            - Task: A2A webhook for terminated statuses (completed, failed)
            - TaskStatusUpdateEvent: A2A webhook for intermediate statuses
              (working, input-required, submitted)
        task_type: Task type from application routing (e.g., "get_products").
            Applications should extract this from URL routing pattern:
            /webhook/{task_type}/{agent_id}/{operation_id}
        operation_id: Operation identifier from application routing.
            Used to correlate webhook notifications with original task submission.
        signature: Optional HMAC-SHA256 signature for MCP webhook verification
            (X-AdCP-Signature header). Ignored for A2A webhooks.
        timestamp: Optional Unix timestamp (seconds) for MCP webhook signature
            verification (X-AdCP-Timestamp header). Required when signature is provided.
        raw_body: Optional raw HTTP request body bytes for signature verification.
            When provided, used directly instead of re-serializing the payload,
            avoiding cross-language JSON serialization mismatches. Strongly
            recommended for production use.

    Returns:
        TaskResult with parsed task-specific response data. The structure
        is identical regardless of protocol.

    Raises:
        ADCPWebhookSignatureError: If MCP signature verification fails
        ValidationError: If MCP payload doesn't match WebhookPayload schema

    Note:
        task_type and operation_id were deprecated from the webhook payload
        per AdCP specification. Applications must extract these from URL
        routing and pass them explicitly.

    Examples:
        MCP webhook (HTTP endpoint):
        >>> @app.post("/webhook/{task_type}/{agent_id}/{operation_id}")
        >>> async def webhook_handler(task_type: str, operation_id: str, request: Request):
        >>>     raw_body = await request.body()
        >>>     payload = json.loads(raw_body)
        >>>     signature = request.headers.get("X-AdCP-Signature")
        >>>     timestamp = request.headers.get("X-AdCP-Timestamp")
        >>>     result = await client.handle_webhook(
        >>>         payload, task_type, operation_id, signature, timestamp,
        >>>         raw_body=raw_body,
        >>>     )
        >>>     if result.success:
        >>>         print(f"Task completed: {result.data}")

        A2A webhook with Task (terminated status):
        >>> async def on_task_completed(task: Task):
        >>>     # Extract task_type and operation_id from your app's task tracking
        >>>     task_type = your_task_registry.get_type(task.id)
        >>>     operation_id = your_task_registry.get_operation_id(task.id)
        >>>     result = await client.handle_webhook(
        >>>         task, task_type, operation_id
        >>>     )
        >>>     if result.success:
        >>>         print(f"Task completed: {result.data}")

        A2A webhook with TaskStatusUpdateEvent (intermediate status):
        >>> async def on_task_update(event: TaskStatusUpdateEvent):
        >>>     # Extract task_type and operation_id from your app's task tracking
        >>>     task_type = your_task_registry.get_type(event.task_id)
        >>>     operation_id = your_task_registry.get_operation_id(event.task_id)
        >>>     result = await client.handle_webhook(
        >>>         event, task_type, operation_id
        >>>     )
        >>>     if result.status == GeneratedTaskStatus.working:
        >>>         print(f"Task still working: {result.metadata.get('message')}")
    """
    # Detect protocol type and route to appropriate handler
    if isinstance(payload, (Task, TaskStatusUpdateEvent)):
        # A2A webhook (Task or TaskStatusUpdateEvent)
        return await self._handle_a2a_webhook(payload, task_type, operation_id)
    else:
        # MCP webhook (dict payload)
        return await self._handle_mcp_webhook(
            payload, task_type, operation_id, signature, timestamp, raw_body
        )

Handle incoming webhook and return typed result.

This method provides a unified interface for handling webhooks from both MCP and A2A protocols:

  • MCP Webhooks: HTTP POST with dict payload, optional HMAC signature
  • A2A Webhooks: Task or TaskStatusUpdateEvent objects based on status

The method automatically detects the protocol type and routes to the appropriate handler. Both protocols return a consistent TaskResult structure with typed AdCP response data.

Args

payload
Webhook payload - one of: - dict[str, Any]: MCP webhook payload from HTTP POST - Task: A2A webhook for terminated statuses (completed, failed) - TaskStatusUpdateEvent: A2A webhook for intermediate statuses (working, input-required, submitted)
task_type
Task type from application routing (e.g., "get_products"). Applications should extract this from URL routing pattern: /webhook/{task_type}/{agent_id}/{operation_id}
operation_id
Operation identifier from application routing. Used to correlate webhook notifications with original task submission.
signature
Optional HMAC-SHA256 signature for MCP webhook verification (X-AdCP-Signature header). Ignored for A2A webhooks.
timestamp
Optional Unix timestamp (seconds) for MCP webhook signature verification (X-AdCP-Timestamp header). Required when signature is provided.
raw_body
Optional raw HTTP request body bytes for signature verification. When provided, used directly instead of re-serializing the payload, avoiding cross-language JSON serialization mismatches. Strongly recommended for production use.

Returns

TaskResult with parsed task-specific response data. The structure is identical regardless of protocol.

Raises

ADCPWebhookSignatureError
If MCP signature verification fails
ValidationError
If MCP payload doesn't match WebhookPayload schema

Note

task_type and operation_id were deprecated from the webhook payload per AdCP specification. Applications must extract these from URL routing and pass them explicitly.

Examples

MCP webhook (HTTP endpoint):

>>> @app.post("/webhook/{task_type}/{agent_id}/{operation_id}")
>>> async def webhook_handler(task_type: str, operation_id: str, request: Request):
>>>     raw_body = await request.body()
>>>     payload = json.loads(raw_body)
>>>     signature = request.headers.get("X-AdCP-Signature")
>>>     timestamp = request.headers.get("X-AdCP-Timestamp")
>>>     result = await client.handle_webhook(
>>>         payload, task_type, operation_id, signature, timestamp,
>>>         raw_body=raw_body,
>>>     )
>>>     if result.success:
>>>         print(f"Task completed: {result.data}")

A2A webhook with Task (terminated status):

>>> async def on_task_completed(task: Task):
>>>     # Extract task_type and operation_id from your app's task tracking
>>>     task_type = your_task_registry.get_type(task.id)
>>>     operation_id = your_task_registry.get_operation_id(task.id)
>>>     result = await client.handle_webhook(
>>>         task, task_type, operation_id
>>>     )
>>>     if result.success:
>>>         print(f"Task completed: {result.data}")

A2A webhook with TaskStatusUpdateEvent (intermediate status):

>>> async def on_task_update(event: TaskStatusUpdateEvent):
>>>     # Extract task_type and operation_id from your app's task tracking
>>>     task_type = your_task_registry.get_type(event.task_id)
>>>     operation_id = your_task_registry.get_operation_id(event.task_id)
>>>     result = await client.handle_webhook(
>>>         event, task_type, operation_id
>>>     )
>>>     if result.status == GeneratedTaskStatus.working:
>>>         print(f"Task still working: {result.metadata.get('message')}")
async def identity_match(self,
request: IdentityMatchRequest) ‑> TaskResult[IdentityMatchResponse]
Expand source code
async def identity_match(
    self,
    request: IdentityMatchRequest,
) -> TaskResult[IdentityMatchResponse]:
    """Match user identity for package eligibility.

    Evaluates a user identity token against all active packages for
    frequency capping and personalization.

    Args:
        request: Identity match request with user_token, uid_type,
            and package_ids.

    Returns:
        TaskResult containing IdentityMatchResponse with eligible_package_ids.
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True, by_alias=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="identity_match",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.identity_match(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="identity_match",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, IdentityMatchResponse)

Match user identity for package eligibility.

Evaluates a user identity token against all active packages for frequency capping and personalization.

Args

request
Identity match request with user_token, uid_type, and package_ids.

Returns

TaskResult containing IdentityMatchResponse with eligible_package_ids.

async def list_accounts(self,
request: ListAccountsRequest) ‑> TaskResult[ListAccountsResponse]
Expand source code
async def list_accounts(
    self,
    request: ListAccountsRequest,
) -> TaskResult[ListAccountsResponse]:
    """
    List Accounts.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing ListAccountsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_accounts",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.list_accounts(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_accounts",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ListAccountsResponse)

List Accounts.

Args

request
Request parameters

Returns

TaskResult containing ListAccountsResponse

async def list_collection_lists(self, request: ListCollectionListsRequest) ‑> TaskResult[ListCollectionListsResponse]
Expand source code
async def list_collection_lists(
    self,
    request: ListCollectionListsRequest,
) -> TaskResult[ListCollectionListsResponse]:
    """List collection lists owned by a principal.

    Args:
        request: Request parameters with optional filtering

    Returns:
        TaskResult containing ListCollectionListsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_collection_lists",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.list_collection_lists(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_collection_lists",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ListCollectionListsResponse)

List collection lists owned by a principal.

Args

request
Request parameters with optional filtering

Returns

TaskResult containing ListCollectionListsResponse

async def list_content_standards(self, request: ListContentStandardsRequest) ‑> TaskResult[Union[ListContentStandardsResponse1ListContentStandardsResponse2]]
Expand source code
async def list_content_standards(
    self,
    request: ListContentStandardsRequest,
) -> TaskResult[ListContentStandardsResponse]:
    """
    List content standards configurations.

    Args:
        request: Request parameters including optional filters

    Returns:
        TaskResult containing ListContentStandardsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_content_standards",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.list_content_standards(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_content_standards",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ListContentStandardsResponse)

List content standards configurations.

Args

request
Request parameters including optional filters

Returns

TaskResult containing ListContentStandardsResponse

async def list_creative_formats(self,
request: ListCreativeFormatsRequest,
fetch_previews: bool = False,
preview_output_format: str = 'url') ‑> TaskResult[ListCreativeFormatsResponse]
Expand source code
async def list_creative_formats(
    self,
    request: ListCreativeFormatsRequest,
    fetch_previews: bool = False,
    preview_output_format: str = "url",
) -> TaskResult[ListCreativeFormatsResponse]:
    """
    List supported creative formats.

    Args:
        request: Request parameters
        fetch_previews: If True, generate preview URLs for each format using
            sample manifests (uses batch API for 5-10x performance improvement)
        preview_output_format: "url" for iframe URLs (default), "html" for direct
            embedding (2-3x faster, no iframe overhead)

    Returns:
        TaskResult containing ListCreativeFormatsResponse with optional preview URLs in metadata
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_creative_formats",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.list_creative_formats(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_creative_formats",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    result: TaskResult[ListCreativeFormatsResponse] = self.adapter._parse_response(
        raw_result, ListCreativeFormatsResponse
    )

    if fetch_previews and result.success and result.data:
        from adcp.utils.preview_cache import add_preview_urls_to_formats

        formats_with_previews = await add_preview_urls_to_formats(
            result.data.formats,
            self,
            use_batch=True,
            output_format=preview_output_format,
        )
        result.metadata = result.metadata or {}
        result.metadata["formats_with_previews"] = formats_with_previews

    return result

List supported creative formats.

Args

request
Request parameters
fetch_previews
If True, generate preview URLs for each format using sample manifests (uses batch API for 5-10x performance improvement)
preview_output_format
"url" for iframe URLs (default), "html" for direct embedding (2-3x faster, no iframe overhead)

Returns

TaskResult containing ListCreativeFormatsResponse with optional preview URLs in metadata

async def list_creatives(self,
request: ListCreativesRequest) ‑> TaskResult[ListCreativesResponse]
Expand source code
async def list_creatives(
    self,
    request: ListCreativesRequest,
) -> TaskResult[ListCreativesResponse]:
    """
    List Creatives.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing ListCreativesResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_creatives",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.list_creatives(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_creatives",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ListCreativesResponse)

List Creatives.

Args

request
Request parameters

Returns

TaskResult containing ListCreativesResponse

async def list_property_lists(self, request: ListPropertyListsRequest) ‑> TaskResult[ListPropertyListsResponse]
Expand source code
async def list_property_lists(
    self,
    request: ListPropertyListsRequest,
) -> TaskResult[ListPropertyListsResponse]:
    """
    List property lists owned by a principal.

    Retrieves metadata for all property lists, optionally filtered
    by principal or pagination parameters.

    Args:
        request: Request parameters with optional filtering

    Returns:
        TaskResult containing ListPropertyListsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_property_lists",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.list_property_lists(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="list_property_lists",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ListPropertyListsResponse)

List property lists owned by a principal.

Retrieves metadata for all property lists, optionally filtered by principal or pagination parameters.

Args

request
Request parameters with optional filtering

Returns

TaskResult containing ListPropertyListsResponse

async def list_tools(self) ‑> list[str]
Expand source code
async def list_tools(self) -> list[str]:
    """
    List available tools from the agent.

    Returns:
        List of tool names
    """
    return await self.adapter.list_tools()

List available tools from the agent.

Returns

List of tool names

async def log_event(self,
request: LogEventRequest) ‑> TaskResult[Union[LogEventResponse1LogEventResponse2]]
Expand source code
async def log_event(
    self,
    request: LogEventRequest,
) -> TaskResult[LogEventResponse]:
    """
    Log Event.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing LogEventResponse
    """
    self._validate_task_features("log_event")
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="log_event",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.log_event(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="log_event",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, LogEventResponse)

Log Event.

Args

request
Request parameters

Returns

TaskResult containing LogEventResponse

async def preview_creative(self,
request: PreviewCreativeRequest) ‑> TaskResult[Union[PreviewCreativeResponse1PreviewCreativeResponse2PreviewCreativeResponse3]]
Expand source code
async def preview_creative(
    self,
    request: PreviewCreativeRequest,
) -> TaskResult[PreviewCreativeResponse]:
    """
    Generate preview of a creative manifest.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing PreviewCreativeResponse with preview URLs
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="preview_creative",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.preview_creative(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="preview_creative",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, PreviewCreativeResponse)

Generate preview of a creative manifest.

Args

request
Request parameters

Returns

TaskResult containing PreviewCreativeResponse with preview URLs

async def provide_performance_feedback(self,
request: ProvidePerformanceFeedbackRequest) ‑> TaskResult[Union[ProvidePerformanceFeedbackResponse1ProvidePerformanceFeedbackResponse2]]
Expand source code
async def provide_performance_feedback(
    self,
    request: ProvidePerformanceFeedbackRequest,
) -> TaskResult[ProvidePerformanceFeedbackResponse]:
    """
    Provide Performance Feedback.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing ProvidePerformanceFeedbackResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="provide_performance_feedback",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.provide_performance_feedback(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="provide_performance_feedback",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ProvidePerformanceFeedbackResponse)

Provide Performance Feedback.

Args

request
Request parameters

Returns

TaskResult containing ProvidePerformanceFeedbackResponse

async def refresh_capabilities(self) ‑> GetAdcpCapabilitiesResponse
Expand source code
async def refresh_capabilities(self) -> GetAdcpCapabilitiesResponse:
    """Fetch capabilities from the seller, bypassing cache.

    Returns:
        The seller's capabilities response.
    """
    result = await self.get_adcp_capabilities(GetAdcpCapabilitiesRequest())
    if result.success and result.data is not None:
        self._capabilities = result.data
        self._feature_resolver = FeatureResolver(result.data)
        self._capabilities_fetched_at = time.monotonic()
        return self._capabilities
    raise ADCPError(
        f"Failed to fetch capabilities: {result.error or result.message}",
        agent_id=self.agent_config.id,
        agent_uri=self.agent_config.agent_uri,
    )

Fetch capabilities from the seller, bypassing cache.

Returns

The seller's capabilities response.

async def report_plan_outcome(self,
request: ReportPlanOutcomeRequest) ‑> TaskResult[ReportPlanOutcomeResponse]
Expand source code
async def report_plan_outcome(
    self,
    request: ReportPlanOutcomeRequest,
) -> TaskResult[ReportPlanOutcomeResponse]:
    """Report the outcome of a governed action to the governance agent."""
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="report_plan_outcome",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.report_plan_outcome(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="report_plan_outcome",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ReportPlanOutcomeResponse)

Report the outcome of a governed action to the governance agent.

async def report_usage(self,
request: ReportUsageRequest) ‑> TaskResult[ReportUsageResponse]
Expand source code
async def report_usage(
    self,
    request: ReportUsageRequest,
) -> TaskResult[ReportUsageResponse]:
    """
    Report Usage.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing ReportUsageResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="report_usage",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.report_usage(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="report_usage",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ReportUsageResponse)

Report Usage.

Args

request
Request parameters

Returns

TaskResult containing ReportUsageResponse

def require(self, *features: str) ‑> None
Expand source code
def require(self, *features: str) -> None:
    """Assert that the seller supports all listed features.

    Args:
        *features: Feature identifiers to require.

    Raises:
        ADCPFeatureUnsupportedError: If any features are not supported.
        ADCPError: If capabilities have not been fetched yet.
    """
    self._ensure_resolver().require(
        *features,
        agent_id=self.agent_config.id,
        agent_uri=self.agent_config.agent_uri,
    )

Assert that the seller supports all listed features.

Args

*features
Feature identifiers to require.

Raises

ADCPFeatureUnsupportedError
If any features are not supported.
ADCPError
If capabilities have not been fetched yet.
async def si_get_offering(self, request: SiGetOfferingRequest) ‑> TaskResult[SiGetOfferingResponse]
Expand source code
async def si_get_offering(
    self,
    request: SiGetOfferingRequest,
) -> TaskResult[SiGetOfferingResponse]:
    """
    Get sponsored intelligence offering.

    Retrieves product/service offerings that can be presented in a
    sponsored intelligence session.

    Args:
        request: Request parameters including brand context

    Returns:
        TaskResult containing SiGetOfferingResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="si_get_offering",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.si_get_offering(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="si_get_offering",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SiGetOfferingResponse)

Get sponsored intelligence offering.

Retrieves product/service offerings that can be presented in a sponsored intelligence session.

Args

request
Request parameters including brand context

Returns

TaskResult containing SiGetOfferingResponse

async def si_initiate_session(self, request: SiInitiateSessionRequest) ‑> TaskResult[SiInitiateSessionResponse]
Expand source code
async def si_initiate_session(
    self,
    request: SiInitiateSessionRequest,
) -> TaskResult[SiInitiateSessionResponse]:
    """
    Initiate a sponsored intelligence session.

    Starts a conversational brand experience session with a user.

    Args:
        request: Request parameters including identity and context

    Returns:
        TaskResult containing SiInitiateSessionResponse with session_id
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="si_initiate_session",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.si_initiate_session(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="si_initiate_session",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SiInitiateSessionResponse)

Initiate a sponsored intelligence session.

Starts a conversational brand experience session with a user.

Args

request
Request parameters including identity and context

Returns

TaskResult containing SiInitiateSessionResponse with session_id

async def si_send_message(self,
request: SiSendMessageRequest) ‑> TaskResult[SiSendMessageResponse]
Expand source code
async def si_send_message(
    self,
    request: SiSendMessageRequest,
) -> TaskResult[SiSendMessageResponse]:
    """
    Send a message in a sponsored intelligence session.

    Continues the conversation in an active SI session.

    Args:
        request: Request parameters including session_id and message

    Returns:
        TaskResult containing SiSendMessageResponse with brand response
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="si_send_message",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.si_send_message(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="si_send_message",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SiSendMessageResponse)

Send a message in a sponsored intelligence session.

Continues the conversation in an active SI session.

Args

request
Request parameters including session_id and message

Returns

TaskResult containing SiSendMessageResponse with brand response

async def si_terminate_session(self, request: SiTerminateSessionRequest) ‑> TaskResult[SiTerminateSessionResponse]
Expand source code
async def si_terminate_session(
    self,
    request: SiTerminateSessionRequest,
) -> TaskResult[SiTerminateSessionResponse]:
    """
    Terminate a sponsored intelligence session.

    Ends an active SI session, optionally with follow-up actions.

    Args:
        request: Request parameters including session_id and termination context

    Returns:
        TaskResult containing SiTerminateSessionResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="si_terminate_session",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.si_terminate_session(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="si_terminate_session",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SiTerminateSessionResponse)

Terminate a sponsored intelligence session.

Ends an active SI session, optionally with follow-up actions.

Args

request
Request parameters including session_id and termination context

Returns

TaskResult containing SiTerminateSessionResponse

def supports(self, feature: str) ‑> bool
Expand source code
def supports(self, feature: str) -> bool:
    """Check if the seller supports a feature.

    Supports multiple feature namespaces:
    - Protocol support: ``supports("media_buy")`` checks ``supported_protocols``
    - Extension support: ``supports("ext:scope3")`` checks ``extensions_supported``
    - Targeting: ``supports("targeting.geo_countries")`` checks
      ``media_buy.execution.targeting``
    - Media buy features: ``supports("audience_targeting")`` checks
      ``media_buy.features``
    - Signals features: ``supports("catalog_signals")`` checks
      ``signals.features``

    Args:
        feature: Feature identifier to check.

    Returns:
        True if the seller declares the feature as supported.

    Raises:
        ADCPError: If capabilities have not been fetched yet.
    """
    return self._ensure_resolver().supports(feature)

Check if the seller supports a feature.

Supports multiple feature namespaces: - Protocol support: supports("media_buy") checks supported_protocols - Extension support: supports("ext:scope3") checks extensions_supported - Targeting: supports("targeting.geo_countries") checks media_buy.execution.targeting - Media buy features: supports("audience_targeting") checks media_buy.features - Signals features: supports("catalog_signals") checks signals.features

Args

feature
Feature identifier to check.

Returns

True if the seller declares the feature as supported.

Raises

ADCPError
If capabilities have not been fetched yet.
async def sync_accounts(self,
request: SyncAccountsRequest) ‑> TaskResult[Union[SyncAccountsResponse1SyncAccountsResponse2]]
Expand source code
async def sync_accounts(
    self,
    request: SyncAccountsRequest,
) -> TaskResult[SyncAccountsResponse]:
    """
    Sync Accounts.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing SyncAccountsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_accounts",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.sync_accounts(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_accounts",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SyncAccountsResponse)

Sync Accounts.

Args

request
Request parameters

Returns

TaskResult containing SyncAccountsResponse

async def sync_audiences(self,
request: SyncAudiencesRequest) ‑> TaskResult[Union[SyncAudiencesResponse1SyncAudiencesResponse2]]
Expand source code
async def sync_audiences(
    self,
    request: SyncAudiencesRequest,
) -> TaskResult[SyncAudiencesResponse]:
    """
    Sync Audiences.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing SyncAudiencesResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_audiences",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.sync_audiences(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_audiences",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SyncAudiencesResponse)

Sync Audiences.

Args

request
Request parameters

Returns

TaskResult containing SyncAudiencesResponse

async def sync_catalogs(self,
request: SyncCatalogsRequest) ‑> TaskResult[Union[SyncCatalogsResponse1SyncCatalogsResponse2]]
Expand source code
async def sync_catalogs(
    self,
    request: SyncCatalogsRequest,
) -> TaskResult[SyncCatalogsResponse]:
    """
    Sync Catalogs.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing SyncCatalogsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_catalogs",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.sync_catalogs(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_catalogs",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SyncCatalogsResponse)

Sync Catalogs.

Args

request
Request parameters

Returns

TaskResult containing SyncCatalogsResponse

async def sync_creatives(self,
request: SyncCreativesRequest) ‑> TaskResult[Union[SyncCreativesResponse1SyncCreativesResponse2, SyncCreativesResponse3]]
Expand source code
async def sync_creatives(
    self,
    request: SyncCreativesRequest,
) -> TaskResult[SyncCreativesResponse]:
    """
    Sync Creatives.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing SyncCreativesResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_creatives",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.sync_creatives(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_creatives",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SyncCreativesResponse)

Sync Creatives.

Args

request
Request parameters

Returns

TaskResult containing SyncCreativesResponse

async def sync_event_sources(self,
request: SyncEventSourcesRequest) ‑> TaskResult[Union[SyncEventSourcesResponse1SyncEventSourcesResponse2]]
Expand source code
async def sync_event_sources(
    self,
    request: SyncEventSourcesRequest,
) -> TaskResult[SyncEventSourcesResponse]:
    """
    Sync Event Sources.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing SyncEventSourcesResponse
    """
    self._validate_task_features("sync_event_sources")
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_event_sources",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.sync_event_sources(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_event_sources",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SyncEventSourcesResponse)

Sync Event Sources.

Args

request
Request parameters

Returns

TaskResult containing SyncEventSourcesResponse

async def sync_governance(self, request: SyncGovernanceRequest) ‑> TaskResult[Union[SyncGovernanceResponse1, SyncGovernanceResponse2]]
Expand source code
async def sync_governance(
    self,
    request: SyncGovernanceRequest,
) -> TaskResult[SyncGovernanceResponse]:
    """Sync governance agents attached to an account.

    Attach, detach, or replace the set of governance agents that must be
    consulted for plan approval on an account.

    Args:
        request: Request parameters with account and governance agents

    Returns:
        TaskResult containing SyncGovernanceResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_governance",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.sync_governance(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_governance",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SyncGovernanceResponse)

Sync governance agents attached to an account.

Attach, detach, or replace the set of governance agents that must be consulted for plan approval on an account.

Args

request
Request parameters with account and governance agents

Returns

TaskResult containing SyncGovernanceResponse

async def sync_plans(self,
request: SyncPlansRequest) ‑> TaskResult[SyncPlansResponse]
Expand source code
async def sync_plans(
    self,
    request: SyncPlansRequest,
) -> TaskResult[SyncPlansResponse]:
    """Sync campaign governance plans to the governance agent."""
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_plans",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.sync_plans(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="sync_plans",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, SyncPlansResponse)

Sync campaign governance plans to the governance agent.

async def update_collection_list(self, request: UpdateCollectionListRequest) ‑> TaskResult[UpdateCollectionListResponse]
Expand source code
async def update_collection_list(
    self,
    request: UpdateCollectionListRequest,
) -> TaskResult[UpdateCollectionListResponse]:
    """Update a collection list.

    Args:
        request: Request parameters with list_id and updates

    Returns:
        TaskResult containing UpdateCollectionListResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_collection_list",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.update_collection_list(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_collection_list",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, UpdateCollectionListResponse)

Update a collection list.

Args

request
Request parameters with list_id and updates

Returns

TaskResult containing UpdateCollectionListResponse

async def update_content_standards(self, request: UpdateContentStandardsRequest) ‑> TaskResult[Union[UpdateContentStandardsResponse1UpdateContentStandardsResponse2]]
Expand source code
async def update_content_standards(
    self,
    request: UpdateContentStandardsRequest,
) -> TaskResult[UpdateContentStandardsResponse]:
    """
    Update a content standards configuration.

    Args:
        request: Request parameters including standards_id and updates

    Returns:
        TaskResult containing UpdateContentStandardsResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_content_standards",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.update_content_standards(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_content_standards",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, UpdateContentStandardsResponse)

Update a content standards configuration.

Args

request
Request parameters including standards_id and updates

Returns

TaskResult containing UpdateContentStandardsResponse

async def update_media_buy(self,
request: UpdateMediaBuyRequest) ‑> TaskResult[Union[UpdateMediaBuyResponse1UpdateMediaBuyResponse2]]
Expand source code
async def update_media_buy(
    self,
    request: UpdateMediaBuyRequest,
) -> TaskResult[UpdateMediaBuyResponse]:
    """
    Update an existing media buy reservation.

    Modifies a previously created media buy by updating packages or publisher
    properties. The update operation uses discriminated unions to specify what
    to change - either package details or targeting properties.

    Args:
        request: Media buy update parameters including:
            - media_buy_id: Identifier from create_media_buy response
            - updates: Discriminated union specifying update type:
                * UpdateMediaBuyPackagesRequest: Modify package selections
                * UpdateMediaBuyPropertiesRequest: Change targeting properties

    Returns:
        TaskResult containing UpdateMediaBuyResponse with:
            - media_buy_id: The updated media buy identifier
            - status: Updated state of the media buy
            - packages: Updated package configurations
            - Additional platform-specific metadata

    Example:
        >>> from adcp import ADCPClient, UpdateMediaBuyPackagesRequest
        >>> client = ADCPClient(agent_config)
        >>> request = UpdateMediaBuyPackagesRequest(
        ...     media_buy_id="mb_123",
        ...     packages=[updated_package]
        ... )
        >>> result = await client.update_media_buy(request)
        >>> if result.success:
        ...     updated_packages = result.data.packages
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_media_buy",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.update_media_buy(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_media_buy",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, UpdateMediaBuyResponse)

Update an existing media buy reservation.

Modifies a previously created media buy by updating packages or publisher properties. The update operation uses discriminated unions to specify what to change - either package details or targeting properties.

Args

request
Media buy update parameters including: - media_buy_id: Identifier from create_media_buy response - updates: Discriminated union specifying update type: * UpdateMediaBuyPackagesRequest: Modify package selections * UpdateMediaBuyPropertiesRequest: Change targeting properties

Returns

TaskResult containing UpdateMediaBuyResponse with: - media_buy_id: The updated media buy identifier - status: Updated state of the media buy - packages: Updated package configurations - Additional platform-specific metadata

Example

>>> from adcp import ADCPClient, UpdateMediaBuyPackagesRequest
>>> client = ADCPClient(agent_config)
>>> request = UpdateMediaBuyPackagesRequest(
...     media_buy_id="mb_123",
...     packages=[updated_package]
... )
>>> result = await client.update_media_buy(request)
>>> if result.success:
...     updated_packages = result.data.packages
async def update_property_list(self, request: UpdatePropertyListRequest) ‑> TaskResult[UpdatePropertyListResponse]
Expand source code
async def update_property_list(
    self,
    request: UpdatePropertyListRequest,
) -> TaskResult[UpdatePropertyListResponse]:
    """
    Update a property list.

    Modifies the filters, brand manifest, or other parameters
    of an existing property list.

    Args:
        request: Request parameters with list_id and updates

    Returns:
        TaskResult containing UpdatePropertyListResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_property_list",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.update_property_list(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_property_list",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, UpdatePropertyListResponse)

Update a property list.

Modifies the filters, brand manifest, or other parameters of an existing property list.

Args

request
Request parameters with list_id and updates

Returns

TaskResult containing UpdatePropertyListResponse

async def update_rights(self, request: UpdateRightsRequest) ‑> TaskResult[Union[UpdateRightsResponse1, UpdateRightsResponse2]]
Expand source code
async def update_rights(
    self,
    request: UpdateRightsRequest,
) -> TaskResult[UpdateRightsResponse]:
    """Update terms of an existing rights acquisition.

    Modifies a previously acquired rights record — typically to extend
    the ``end_date``, raise the ``impression_cap``, pause/unpause via
    ``paused``, or swap to a compatible ``pricing_option_id``. Partial
    update: pass only the fields you want to change.

    Failure modes (surface as ``TaskResult`` with ``success=False``):

    * Acquisition is expired or revoked — the seller rejects the update
      outright; mint a fresh ``acquire_rights`` instead.
    * ``pricing_option_id`` swap to an incompatible option — rejected;
      the new option's terms must be a strict superset / compatible
      with the original acquisition.
    * No partial-state mutations on rejection: the acquisition remains
      at its prior state when any field fails validation.

    Args:
        request: Request with ``rights_id`` and at least one mutable
            field (``end_date``, ``impression_cap``, ``paused``, or
            ``pricing_option_id``).

    Returns:
        TaskResult containing UpdateRightsResponse (updated or error).
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_rights",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.update_rights(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="update_rights",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, UpdateRightsResponse)

Update terms of an existing rights acquisition.

Modifies a previously acquired rights record — typically to extend the end_date, raise the impression_cap, pause/unpause via paused, or swap to a compatible pricing_option_id. Partial update: pass only the fields you want to change.

Failure modes (surface as TaskResult with success=False):

  • Acquisition is expired or revoked — the seller rejects the update outright; mint a fresh acquire_rights instead.
  • pricing_option_id swap to an incompatible option — rejected; the new option's terms must be a strict superset / compatible with the original acquisition.
  • No partial-state mutations on rejection: the acquisition remains at its prior state when any field fails validation.

Args

request
Request with rights_id and at least one mutable field (end_date, impression_cap, paused, or pricing_option_id).

Returns

TaskResult containing UpdateRightsResponse (updated or error).

def use_idempotency_key(self, key: str) ‑> Iterator[str]
Expand source code
@contextlib.contextmanager
def use_idempotency_key(self, key: str) -> Iterator[str]:
    """Pin an ``idempotency_key`` for the next mutating call on THIS client.

    Use when you've persisted a key (e.g., in a buyer-side database) and
    want the SDK to send that exact key on resume or retry across process
    restarts. The key is validated against ``^[A-Za-z0-9_.:-]{16,255}$`` on
    entry; a ``ValueError`` is raised for malformed keys.

    Scope rules:

    * **Single-use within scope.** The first mutating call inside the
      ``with`` block consumes the pinned key; a second mutating call falls
      through to a fresh UUID. This protects against ``asyncio.gather``
      siblings accidentally sharing the key (which would trigger
      ``IDEMPOTENCY_CONFLICT`` or silently duplicate work). If you need to
      retry, wrap each attempt in its own ``with`` block.
    * **Client-scoped.** The pinned key applies only to calls on THIS
      client. A mutating call on a sibling ``ADCPClient`` inside the same
      ``with`` block generates a fresh key and emits a ``UserWarning`` —
      keys must be unique per (seller, request) pair (AdCP #2315).
    * **No nesting.** Nested ``use_idempotency_key`` on the same client
      raises ``RuntimeError``.

    Example::

        with client.use_idempotency_key(campaign.stored_key):
            result = await client.create_media_buy(request)
    """
    from adcp import _idempotency

    _idempotency.validate_key(key)
    token = self._idempotency_client_token
    if token in _idempotency._scoped_keys:
        raise RuntimeError(
            "use_idempotency_key is already active on this client; "
            "nested usage is not supported."
        )
    _idempotency._scoped_keys[token] = key
    try:
        yield key
    finally:
        _idempotency._scoped_keys.pop(token, None)

Pin an idempotency_key for the next mutating call on THIS client.

Use when you've persisted a key (e.g., in a buyer-side database) and want the SDK to send that exact key on resume or retry across process restarts. The key is validated against ^[A-Za-z0-9_.:-]{16,255}$ on entry; a ValueError is raised for malformed keys.

Scope rules:

  • Single-use within scope. The first mutating call inside the with block consumes the pinned key; a second mutating call falls through to a fresh UUID. This protects against asyncio.gather siblings accidentally sharing the key (which would trigger IDEMPOTENCY_CONFLICT or silently duplicate work). If you need to retry, wrap each attempt in its own with block.
  • Client-scoped. The pinned key applies only to calls on THIS client. A mutating call on a sibling ADCPClient inside the same with block generates a fresh key and emits a UserWarning — keys must be unique per (seller, request) pair (AdCP #2315).
  • No nesting. Nested use_idempotency_key on the same client raises RuntimeError.

Example::

with client.use_idempotency_key(campaign.stored_key):
    result = await client.create_media_buy(request)
async def validate_content_delivery(self, request: ValidateContentDeliveryRequest) ‑> TaskResult[Union[ValidateContentDeliveryResponse1ValidateContentDeliveryResponse2]]
Expand source code
async def validate_content_delivery(
    self,
    request: ValidateContentDeliveryRequest,
) -> TaskResult[ValidateContentDeliveryResponse]:
    """
    Validate content delivery against standards.

    Validates that ad delivery records comply with content standards.

    Args:
        request: Request parameters including delivery records

    Returns:
        TaskResult containing ValidateContentDeliveryResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(mode="json", exclude_none=True)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_REQUEST,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="validate_content_delivery",
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    raw_result = await self.adapter.validate_content_delivery(params)

    self._emit_activity(
        Activity(
            type=ActivityType.PROTOCOL_RESPONSE,
            operation_id=operation_id,
            agent_id=self.agent_config.id,
            task_type="validate_content_delivery",
            status=raw_result.status,
            timestamp=datetime.now(timezone.utc).isoformat(),
        )
    )

    return self.adapter._parse_response(raw_result, ValidateContentDeliveryResponse)

Validate content delivery against standards.

Validates that ad delivery records comply with content standards.

Args

request
Request parameters including delivery records

Returns

TaskResult containing ValidateContentDeliveryResponse

class ADCPConnectionError (message: str, agent_id: str | None = None, agent_uri: str | None = None)
Expand source code
class ADCPConnectionError(ADCPError):
    """Connection to agent failed."""

    def __init__(self, message: str, agent_id: str | None = None, agent_uri: str | None = None):
        """Initialize connection error."""
        suggestion = (
            "Check that the agent URI is correct and the agent is running.\n"
            "     Try testing with: python -m adcp test --config <agent-id>"
        )
        super().__init__(message, agent_id, agent_uri, suggestion)

    @property
    def is_retryable(self) -> bool:
        return True

Connection to agent failed.

Initialize connection error.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ADCPError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
suggestion: str | None = None)
Expand source code
class ADCPError(Exception):
    """Base exception for all AdCP client errors."""

    def __init__(
        self,
        message: str,
        agent_id: str | None = None,
        agent_uri: str | None = None,
        suggestion: str | None = None,
    ):
        """Initialize exception with context."""
        self.message = message
        self.agent_id = agent_id
        self.agent_uri = agent_uri
        self.suggestion = suggestion

        full_message = message
        if agent_id:
            full_message = f"[Agent: {agent_id}] {full_message}"
        if agent_uri:
            full_message = f"{full_message}\n  URI: {agent_uri}"
        if suggestion:
            full_message = f"{full_message}\n  Suggestion: {suggestion}"

        super().__init__(full_message)

    @property
    def is_retryable(self) -> bool:
        """Whether this error is safe to retry."""
        return False

Base exception for all AdCP client errors.

Initialize exception with context.

Ancestors

  • builtins.Exception
  • builtins.BaseException

Subclasses

Instance variables

prop is_retryable : bool
Expand source code
@property
def is_retryable(self) -> bool:
    """Whether this error is safe to retry."""
    return False

Whether this error is safe to retry.

class ADCPFeatureUnsupportedError (unsupported_features: list[str],
declared_features: list[str] | None = None,
agent_id: str | None = None,
agent_uri: str | None = None)
Expand source code
class ADCPFeatureUnsupportedError(ADCPError):
    """Seller does not support one or more required features."""

    def __init__(
        self,
        unsupported_features: list[str],
        declared_features: list[str] | None = None,
        agent_id: str | None = None,
        agent_uri: str | None = None,
    ):
        """Initialize feature unsupported error.

        Args:
            unsupported_features: Features that are not supported.
            declared_features: Features the seller does declare.
            agent_id: Optional agent ID for context.
            agent_uri: Optional agent URI for context.
        """
        self.unsupported_features = unsupported_features
        self.declared_features = declared_features or []

        missing = ", ".join(unsupported_features)
        message = f"Seller does not support: {missing}"

        suggestion = None
        if self.declared_features:
            declared = ", ".join(sorted(self.declared_features))
            suggestion = f"Declared features: {declared}"

        super().__init__(message, agent_id, agent_uri, suggestion)

Seller does not support one or more required features.

Initialize feature unsupported error.

Args

unsupported_features
Features that are not supported.
declared_features
Features the seller does declare.
agent_id
Optional agent ID for context.
agent_uri
Optional agent URI for context.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ADCPMultiAgentClient (agents: list[AgentConfig],
webhook_url_template: str | None = None,
webhook_secret: str | None = None,
on_activity: Callable[[Activity], None] | None = None,
handlers: dict[str, Callable[..., Any]] | None = None,
signing: SigningConfig | None = None)
Expand source code
class ADCPMultiAgentClient:
    """Client for managing multiple AdCP agents."""

    def __init__(
        self,
        agents: list[AgentConfig],
        webhook_url_template: str | None = None,
        webhook_secret: str | None = None,
        on_activity: Callable[[Activity], None] | None = None,
        handlers: dict[str, Callable[..., Any]] | None = None,
        signing: SigningConfig | None = None,
    ):
        """
        Initialize multi-agent client.

        Args:
            agents: List of agent configurations
            webhook_url_template: Template for webhook URLs
            webhook_secret: Secret for webhook verification
            on_activity: Callback for activity events
            handlers: Task completion handlers
            signing: Optional RFC 9421 signing config forwarded to every
                per-agent ADCPClient. The same identity signs traffic to
                all agents. See ADCPClient.__init__ for details.
        """
        self.agents = {
            agent.id: ADCPClient(
                agent,
                webhook_url_template=webhook_url_template,
                webhook_secret=webhook_secret,
                on_activity=on_activity,
                signing=signing,
            )
            for agent in agents
        }
        self.handlers = handlers or {}

    def agent(self, agent_id: str) -> ADCPClient:
        """Get client for specific agent."""
        if agent_id not in self.agents:
            raise ValueError(f"Agent not found: {agent_id}")
        return self.agents[agent_id]

    @property
    def agent_ids(self) -> list[str]:
        """Get list of agent IDs."""
        return list(self.agents.keys())

    async def close(self) -> None:
        """Close all agent clients and clean up resources."""
        import asyncio

        logger.debug("Closing all agent clients in multi-agent client")
        close_tasks = [client.close() for client in self.agents.values()]
        await asyncio.gather(*close_tasks, return_exceptions=True)

    async def __aenter__(self) -> ADCPMultiAgentClient:
        """Async context manager entry."""
        return self

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        """Async context manager exit."""
        await self.close()

    async def get_products(
        self,
        request: GetProductsRequest,
    ) -> list[TaskResult[GetProductsResponse]]:
        """
        Execute get_products across all agents in parallel.

        Args:
            request: Request parameters

        Returns:
            List of TaskResults containing GetProductsResponse for each agent
        """
        import asyncio

        tasks = [agent.get_products(request) for agent in self.agents.values()]
        return await asyncio.gather(*tasks)

    @classmethod
    def from_env(cls) -> ADCPMultiAgentClient:
        """Create client from environment variables."""
        agents_json = os.getenv("ADCP_AGENTS")
        if not agents_json:
            raise ValueError("ADCP_AGENTS environment variable not set")

        agents_data = json.loads(agents_json)
        agents = [AgentConfig(**agent) for agent in agents_data]

        return cls(
            agents=agents,
            webhook_url_template=os.getenv("WEBHOOK_URL_TEMPLATE"),
            webhook_secret=os.getenv("WEBHOOK_SECRET"),
        )

Client for managing multiple AdCP agents.

Initialize multi-agent client.

Args

agents
List of agent configurations
webhook_url_template
Template for webhook URLs
webhook_secret
Secret for webhook verification
on_activity
Callback for activity events
handlers
Task completion handlers
signing
Optional RFC 9421 signing config forwarded to every per-agent ADCPClient. The same identity signs traffic to all agents. See ADCPClient.init for details.

Static methods

def from_env() ‑> ADCPMultiAgentClient

Create client from environment variables.

Instance variables

prop agent_ids : list[str]
Expand source code
@property
def agent_ids(self) -> list[str]:
    """Get list of agent IDs."""
    return list(self.agents.keys())

Get list of agent IDs.

Methods

def agent(self, agent_id: str) ‑> ADCPClient
Expand source code
def agent(self, agent_id: str) -> ADCPClient:
    """Get client for specific agent."""
    if agent_id not in self.agents:
        raise ValueError(f"Agent not found: {agent_id}")
    return self.agents[agent_id]

Get client for specific agent.

async def close(self) ‑> None
Expand source code
async def close(self) -> None:
    """Close all agent clients and clean up resources."""
    import asyncio

    logger.debug("Closing all agent clients in multi-agent client")
    close_tasks = [client.close() for client in self.agents.values()]
    await asyncio.gather(*close_tasks, return_exceptions=True)

Close all agent clients and clean up resources.

async def get_products(self,
request: GetProductsRequest) ‑> list[TaskResult[GetProductsResponse]]
Expand source code
async def get_products(
    self,
    request: GetProductsRequest,
) -> list[TaskResult[GetProductsResponse]]:
    """
    Execute get_products across all agents in parallel.

    Args:
        request: Request parameters

    Returns:
        List of TaskResults containing GetProductsResponse for each agent
    """
    import asyncio

    tasks = [agent.get_products(request) for agent in self.agents.values()]
    return await asyncio.gather(*tasks)

Execute get_products across all agents in parallel.

Args

request
Request parameters

Returns

List of TaskResults containing GetProductsResponse for each agent

class ADCPProtocolError (message: str, agent_id: str | None = None, protocol: str | None = None)
Expand source code
class ADCPProtocolError(ADCPError):
    """Protocol-level error (malformed response, unexpected format)."""

    def __init__(self, message: str, agent_id: str | None = None, protocol: str | None = None):
        """Initialize protocol error."""
        suggestion = (
            f"The agent returned an unexpected {protocol} response format."
            if protocol
            else "Unexpected response format."
        )
        suggestion += "\n     Enable debug mode to see the full request/response."
        super().__init__(message, agent_id, None, suggestion)

Protocol-level error (malformed response, unexpected format).

Initialize protocol error.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ADCPSigningRequiredError (operation: str, agent_id: str | None = None, agent_uri: str | None = None)
Expand source code
class ADCPSigningRequiredError(ADCPError):
    """Raised when an operation in the seller's ``request_signing.required_for``
    is called without a ``SigningConfig`` on the client.

    Signing a ``required_for`` operation is mandatory — sending it unsigned
    would produce a ``request_signature_required`` rejection from the seller.
    Raising locally before the wire call saves a round-trip and gives the
    caller a clear, actionable error.
    """

    def __init__(
        self,
        operation: str,
        agent_id: str | None = None,
        agent_uri: str | None = None,
    ):
        self.operation = operation
        message = (
            f"Operation {operation!r} is in the seller's request_signing.required_for "
            f"list; signing is mandatory but no SigningConfig was provided"
        )
        suggestion = (
            "Pass signing=SigningConfig(private_key=..., key_id=...) when "
            "constructing ADCPClient. See adcp-keygen for key generation."
        )
        super().__init__(message, agent_id, agent_uri, suggestion)

Raised when an operation in the seller's request_signing.required_for is called without a SigningConfig on the client.

Signing a required_for operation is mandatory — sending it unsigned would produce a request_signature_required rejection from the seller. Raising locally before the wire call saves a round-trip and gives the caller a clear, actionable error.

Initialize exception with context.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ADCPTimeoutError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
timeout: float | None = None)
Expand source code
class ADCPTimeoutError(ADCPError):
    """Request timed out."""

    def __init__(
        self,
        message: str,
        agent_id: str | None = None,
        agent_uri: str | None = None,
        timeout: float | None = None,
    ):
        """Initialize timeout error."""
        suggestion = (
            f"The request took longer than {timeout}s." if timeout else "The request timed out."
        )
        suggestion += "\n     Try increasing the timeout value or check if the agent is overloaded."
        super().__init__(message, agent_id, agent_uri, suggestion)

    @property
    def is_retryable(self) -> bool:
        return True

Request timed out.

Initialize timeout error.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ADCPToolNotFoundError (tool_name: str,
agent_id: str | None = None,
available_tools: list[str] | None = None)
Expand source code
class ADCPToolNotFoundError(ADCPError):
    """Requested tool not found on agent."""

    def __init__(
        self, tool_name: str, agent_id: str | None = None, available_tools: list[str] | None = None
    ):
        """Initialize tool not found error."""
        message = f"Tool '{tool_name}' not found on agent"
        suggestion = "List available tools with: python -m adcp list-tools --config <agent-id>"
        if available_tools:
            tools_list = ", ".join(available_tools[:5])
            if len(available_tools) > 5:
                tools_list += f", ... ({len(available_tools)} total)"
            suggestion = f"Available tools: {tools_list}"
        super().__init__(message, agent_id, None, suggestion)

Requested tool not found on agent.

Initialize tool not found error.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class ADCPWebhookError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
suggestion: str | None = None)
Expand source code
class ADCPWebhookError(ADCPError):
    """Webhook handling error."""

Webhook handling error.

Initialize exception with context.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Subclasses

Inherited members

class ADCPWebhookSignatureError (message: str = 'Invalid webhook signature', agent_id: str | None = None)
Expand source code
class ADCPWebhookSignatureError(ADCPWebhookError):
    """Webhook signature verification failed."""

    def __init__(self, message: str = "Invalid webhook signature", agent_id: str | None = None):
        """Initialize webhook signature error."""
        suggestion = (
            "Verify that the webhook_secret matches the secret configured on the agent.\n"
            "     Webhook signatures use HMAC-SHA256 for security."
        )
        super().__init__(message, agent_id, None, suggestion)

Webhook signature verification failed.

Initialize webhook signature error.

Ancestors

Inherited members

class AccountReference (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class AccountReference(RootModel[AccountReference1 | AccountReference2]):
    root: Annotated[
        AccountReference1 | AccountReference2,
        Field(
            description='Reference to an account by seller-assigned ID or natural key. Use account_id for explicit accounts (require_operator_auth: true, discovered via list_accounts). Use the natural key (brand + operator) for implicit accounts (require_operator_auth: false, declared via sync_accounts). For sandbox: explicit accounts use account_id (pre-existing test account), implicit accounts use the natural key with sandbox: true.',
            examples=[
                {'account_id': 'acc_acme_001'},
                {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'},
                {
                    'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'},
                    'operator': 'pinnacle-media.com',
                },
                {
                    'brand': {'domain': 'acme-corp.com'},
                    'operator': 'acme-corp.com',
                    'sandbox': True,
                },
            ],
            title='Account Reference',
        ),
    ]

    def __getattr__(self, name: str) -> Any:
        """Proxy attribute access to the wrapped type."""
        if name.startswith('_'):
            raise AttributeError(name)
        return getattr(self.root, name)

Usage Documentation

RootModel and Custom Root Types

A Pydantic BaseModel for the root object of the model.

Attributes

root
The root object of the model.
__pydantic_root_model__
Whether the model is a RootModel.
__pydantic_private__
Private fields in the model.
__pydantic_extra__
Extra fields in the model.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.root_model.RootModel[Union[AccountReference1, AccountReference2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootAccountReference1 | AccountReference2
class AccountReferenceById (**data: Any)
Expand source code
class AccountReference1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    account_id: Annotated[
        str,
        Field(
            description='Seller-assigned account identifier (from sync_accounts or list_accounts)'
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var account_id : str
var model_config

Inherited members

class AccountReferenceByNaturalKey (**data: Any)
Expand source code
class AccountReference2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    brand: Annotated[
        brand_ref.BrandReference, Field(description='Brand reference identifying the advertiser')
    ]
    operator: Annotated[
        str,
        Field(
            description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.",
            pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$',
        ),
    ]
    sandbox: Annotated[
        bool | None,
        Field(
            description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).'
        ),
    ] = False

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var brandBrandReference
var model_config
var operator : str
var sandbox : bool | None

Inherited members

class AccountScope (*args, **kwds)
Expand source code
class AccountScope(Enum):
    operator = 'operator'
    brand = 'brand'
    operator_brand = 'operator_brand'
    agent = 'agent'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var agent
var brand
var operator
var operator_brand
class AcquireRightsRequest (**data: Any)
Expand source code
class AcquireRightsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    rights_id: Annotated[
        str, Field(description='Rights offering identifier from get_rights response')
    ]
    pricing_option_id: Annotated[
        str, Field(description='Selected pricing option from the rights offering')
    ]
    buyer: Annotated[brand_ref.BrandReference, Field(description="The buyer's brand identity")]
    campaign: Annotated[Campaign, Field(description='Campaign details for rights clearance')]
    revocation_webhook: Annotated[
        push_notification_config_1.PushNotificationConfig,
        Field(
            description='Webhook for rights revocation notifications. If the rights holder needs to revoke rights (talent scandal, contract violation, etc.), they POST a revocation-notification to this URL. The buyer is responsible for stopping creative delivery upon receipt.'
        ),
    ]
    push_notification_config: Annotated[
        push_notification_config_1.PushNotificationConfig | None,
        Field(
            description='Webhook for async status updates if the acquisition requires approval. The rights agent sends a webhook notification when the status transitions to acquired or rejected.'
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated key for safe retries. Resubmitting with the same key returns the original response rather than creating a duplicate acquisition. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var buyerBrandReference
var campaignCampaign
var contextContextObject | None
var extExtensionObject | None
var idempotency_key : str
var model_config
var pricing_option_id : str
var push_notification_configPushNotificationConfig | None
var revocation_webhookPushNotificationConfig
var rights_id : str

Inherited members

class AcquireRightsAcquiredResponse (**data: Any)
Expand source code
class AcquireRightsResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    rights_id: Annotated[str, Field(description='Rights grant identifier')]
    status: Annotated[
        Literal['acquired'], Field(description='Rights have been cleared and credentials issued')
    ]
    brand_id: Annotated[str, Field(description='Brand identifier of the rights subject')]
    terms: Annotated[rights_terms.RightsTerms, Field(description='Agreed contractual terms')]
    generation_credentials: Annotated[
        list[generation_credential.GenerationCredential],
        Field(description='Scoped credentials for generating rights-cleared content'),
    ]
    restrictions: Annotated[
        list[str] | None, Field(description='Usage restrictions and requirements')
    ] = None
    disclosure: Annotated[
        Disclosure | None, Field(description='Required disclosure for creatives using these rights')
    ] = None
    approval_webhook: Annotated[
        push_notification_config.PushNotificationConfig | None,
        Field(
            description='Authenticated webhook for submitting creatives for approval. POST a creative-approval-request to the URL using the provided authentication. The response is a creative-approval-response.'
        ),
    ] = None
    usage_reporting_url: Annotated[
        AnyUrl | None, Field(description='Endpoint for reporting usage against these rights')
    ] = None
    rights_constraint: Annotated[
        rights_constraint_1.RightsConstraint,
        Field(
            description='Pre-built rights constraint for embedding in creative manifests. Populated from the agreed terms — the buyer does not need to construct it manually.'
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var approval_webhookPushNotificationConfig | None
var brand_id : str
var contextContextObject | None
var disclosureDisclosure | None
var extExtensionObject | None
var generation_credentials : list[GenerationCredential]
var model_config
var restrictions : list[str] | None
var rights_constraintRightsConstraint
var rights_id : str
var status : Literal['acquired']
var termsRightsTerms
var usage_reporting_url : pydantic.networks.AnyUrl | None

Inherited members

class AcquireRightsPendingResponse (**data: Any)
Expand source code
class AcquireRightsResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    rights_id: str
    status: Annotated[
        Literal['pending_approval'],
        Field(description='Rights require approval from the rights holder'),
    ]
    brand_id: str
    detail: Annotated[str | None, Field(description='Explanation of what requires approval')] = None
    estimated_response_time: Annotated[
        str | None,
        Field(description="Expected time for approval decision (e.g., '48h', '3 business days')"),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var brand_id : str
var contextContextObject | None
var detail : str | None
var estimated_response_time : str | None
var extExtensionObject | None
var model_config
var rights_id : str
var status : Literal['pending_approval']

Inherited members

class AcquireRightsRejectedResponse (**data: Any)
Expand source code
class AcquireRightsResponse3(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    rights_id: str
    status: Annotated[Literal['rejected'], Field(description='Rights request was rejected')]
    brand_id: str
    reason: Annotated[
        str,
        Field(
            description="Why the rights request was rejected. May be sanitized to protect confidential brand rules — e.g., 'This violates our public figures brand guidelines' rather than naming the specific rule."
        ),
    ]
    suggestions: Annotated[
        list[str] | None,
        Field(
            description='Actionable alternatives the buyer can try. If present, the rejection is fixable — the buyer can adjust their request. If absent, the rejection is final for this talent/rights combination.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var brand_id : str
var contextContextObject | None
var extExtensionObject | None
var model_config
var reason : str
var rights_id : str
var status : Literal['rejected']
var suggestions : list[str] | None

Inherited members

class AcquireRightsErrorResponse (**data: Any)
Expand source code
class AcquireRightsResponse4(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[list[error.Error], Field(min_length=1)]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class ActivateSignalRequest (**data: Any)
Expand source code
class ActivateSignalRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    action: Annotated[
        Action | None,
        Field(
            description="Whether to activate or deactivate the signal. Deactivating removes the segment from downstream platforms, required when campaigns end to comply with data governance policies (GDPR, CCPA). Defaults to 'activate' when omitted."
        ),
    ] = Action.activate
    signal_agent_segment_id: Annotated[
        str, Field(description='The universal identifier for the signal to activate')
    ]
    destinations: Annotated[
        list[destination.Destination],
        Field(
            description='Target destination(s) for activation. If the authenticated caller matches one of these destinations, activation keys will be included in the response.',
            min_length=1,
        ),
    ]
    pricing_option_id: Annotated[
        str | None,
        Field(
            description="The pricing option selected from the signal's pricing_options in the get_signals response. Required when the signal has pricing options. Records the buyer's pricing commitment at activation time; pass this same value in report_usage for billing verification."
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for this activation. Associates with a commercial relationship established via sync_accounts.'
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for this request. Prevents duplicate activations on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var actionAction | None
var adcp_major_version : int | None
var contextContextObject | None
var destinations : list[Destination]
var extExtensionObject | None
var idempotency_key : str
var model_config
var pricing_option_id : str | None
var signal_agent_segment_id : str

Inherited members

class ActivateSignalSuccessResponse (**data: Any)
Expand source code
class ActivateSignalResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    deployments: Annotated[
        list[deployment.Deployment],
        Field(description='Array of deployment results for each deployment target'),
    ]
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var deployments : list[Deployment]
var extExtensionObject | None
var model_config
var sandbox : bool | None

Inherited members

class ActivateSignalErrorResponse (**data: Any)
Expand source code
class ActivateSignalResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[
        list[error.Error],
        Field(
            description='Array of errors explaining why activation failed (e.g., platform connectivity issues, signal definition problems, authentication failures)',
            min_length=1,
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class SegmentIdActivationKey (**data: Any)
Expand source code
class ActivationKey1(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    type: Annotated[Literal["key_value"], Field(description="Key-value pair based targeting")]
    key: Annotated[str, Field(description="The targeting parameter key")]
    value: Annotated[str, Field(description="The targeting parameter value")]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Subclasses

Class variables

var key : str
var model_config
var type : Literal['key_value']
var value : str
class PropertyIdActivationKey (**data: Any)
Expand source code
class ActivationKey1(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    type: Annotated[Literal["key_value"], Field(description="Key-value pair based targeting")]
    key: Annotated[str, Field(description="The targeting parameter key")]
    value: Annotated[str, Field(description="The targeting parameter value")]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Subclasses

Class variables

var key : str
var model_config
var type : Literal['key_value']
var value : str

Inherited members

class KeyValueActivationKey (**data: Any)
Expand source code
class ActivationKey2(ActivationKey):
    pass

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config
class PropertyTagActivationKey (**data: Any)
Expand source code
class ActivationKey2(ActivationKey):
    pass

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config

Inherited members

class AdagentsNotFoundError (publisher_domain: str)
Expand source code
class AdagentsNotFoundError(AdagentsValidationError):
    """adagents.json file not found (404)."""

    def __init__(self, publisher_domain: str):
        """Initialize not found error."""
        message = f"adagents.json not found for domain: {publisher_domain}"
        suggestion = (
            "Verify that the publisher has deployed adagents.json to:\n"
            f"     https://{publisher_domain}/.well-known/adagents.json"
        )
        super().__init__(message, None, None, suggestion)

adagents.json file not found (404).

Initialize not found error.

Ancestors

Inherited members

class AdagentsTimeoutError (publisher_domain: str, timeout: float)
Expand source code
class AdagentsTimeoutError(AdagentsValidationError):
    """Request for adagents.json timed out."""

    def __init__(self, publisher_domain: str, timeout: float):
        """Initialize timeout error."""
        message = f"Request to fetch adagents.json timed out after {timeout}s"
        suggestion = (
            "The publisher's server may be slow or unresponsive.\n"
            "     Try increasing the timeout value or check the domain is correct."
        )
        super().__init__(message, None, None, suggestion)

Request for adagents.json timed out.

Initialize timeout error.

Ancestors

Inherited members

class AdagentsValidationError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
suggestion: str | None = None)
Expand source code
class AdagentsValidationError(ADCPError):
    """Base error for adagents.json validation issues."""

Base error for adagents.json validation issues.

Initialize exception with context.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Subclasses

Inherited members

class AdvertiserIndustry (*args, **kwds)
Expand source code
class AdvertiserIndustry(Enum):
    automotive = 'automotive'
    automotive_electric_vehicles = 'automotive.electric_vehicles'
    automotive_parts_accessories = 'automotive.parts_accessories'
    automotive_luxury = 'automotive.luxury'
    beauty_cosmetics = 'beauty_cosmetics'
    beauty_cosmetics_skincare = 'beauty_cosmetics.skincare'
    beauty_cosmetics_fragrance = 'beauty_cosmetics.fragrance'
    beauty_cosmetics_haircare = 'beauty_cosmetics.haircare'
    cannabis = 'cannabis'
    cpg = 'cpg'
    cpg_personal_care = 'cpg.personal_care'
    cpg_household = 'cpg.household'
    dating = 'dating'
    education = 'education'
    education_higher_education = 'education.higher_education'
    education_online_learning = 'education.online_learning'
    education_k12 = 'education.k12'
    energy_utilities = 'energy_utilities'
    energy_utilities_renewable = 'energy_utilities.renewable'
    fashion_apparel = 'fashion_apparel'
    fashion_apparel_luxury = 'fashion_apparel.luxury'
    fashion_apparel_sportswear = 'fashion_apparel.sportswear'
    finance = 'finance'
    finance_banking = 'finance.banking'
    finance_insurance = 'finance.insurance'
    finance_investment = 'finance.investment'
    finance_cryptocurrency = 'finance.cryptocurrency'
    food_beverage = 'food_beverage'
    food_beverage_alcohol = 'food_beverage.alcohol'
    food_beverage_restaurants = 'food_beverage.restaurants'
    food_beverage_packaged_goods = 'food_beverage.packaged_goods'
    gambling_betting = 'gambling_betting'
    gambling_betting_sports_betting = 'gambling_betting.sports_betting'
    gambling_betting_casino = 'gambling_betting.casino'
    gaming = 'gaming'
    gaming_mobile = 'gaming.mobile'
    gaming_console_pc = 'gaming.console_pc'
    gaming_esports = 'gaming.esports'
    government_nonprofit = 'government_nonprofit'
    government_nonprofit_political = 'government_nonprofit.political'
    government_nonprofit_charity = 'government_nonprofit.charity'
    healthcare = 'healthcare'
    healthcare_pharmaceutical = 'healthcare.pharmaceutical'
    healthcare_medical_devices = 'healthcare.medical_devices'
    healthcare_wellness = 'healthcare.wellness'
    home_garden = 'home_garden'
    home_garden_furniture = 'home_garden.furniture'
    home_garden_home_improvement = 'home_garden.home_improvement'
    media_entertainment = 'media_entertainment'
    media_entertainment_podcasts = 'media_entertainment.podcasts'
    media_entertainment_music = 'media_entertainment.music'
    media_entertainment_film_tv = 'media_entertainment.film_tv'
    media_entertainment_publishing = 'media_entertainment.publishing'
    media_entertainment_live_events = 'media_entertainment.live_events'
    pets = 'pets'
    professional_services = 'professional_services'
    professional_services_legal = 'professional_services.legal'
    professional_services_consulting = 'professional_services.consulting'
    real_estate = 'real_estate'
    real_estate_residential = 'real_estate.residential'
    real_estate_commercial = 'real_estate.commercial'
    recruitment_hr = 'recruitment_hr'
    retail = 'retail'
    retail_ecommerce = 'retail.ecommerce'
    retail_department_stores = 'retail.department_stores'
    sports_fitness = 'sports_fitness'
    sports_fitness_equipment = 'sports_fitness.equipment'
    sports_fitness_teams_leagues = 'sports_fitness.teams_leagues'
    technology = 'technology'
    technology_software = 'technology.software'
    technology_hardware = 'technology.hardware'
    technology_ai_ml = 'technology.ai_ml'
    telecom = 'telecom'
    telecom_mobile_carriers = 'telecom.mobile_carriers'
    telecom_internet_providers = 'telecom.internet_providers'
    transportation_logistics = 'transportation_logistics'
    travel_hospitality = 'travel_hospitality'
    travel_hospitality_airlines = 'travel_hospitality.airlines'
    travel_hospitality_hotels = 'travel_hospitality.hotels'
    travel_hospitality_cruise = 'travel_hospitality.cruise'
    travel_hospitality_tourism = 'travel_hospitality.tourism'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var automotive
var automotive_electric_vehicles
var automotive_luxury
var automotive_parts_accessories
var beauty_cosmetics
var beauty_cosmetics_fragrance
var beauty_cosmetics_haircare
var beauty_cosmetics_skincare
var cannabis
var cpg
var cpg_household
var cpg_personal_care
var dating
var education
var education_higher_education
var education_k12
var education_online_learning
var energy_utilities
var energy_utilities_renewable
var fashion_apparel
var fashion_apparel_luxury
var fashion_apparel_sportswear
var finance
var finance_banking
var finance_cryptocurrency
var finance_insurance
var finance_investment
var food_beverage
var food_beverage_alcohol
var food_beverage_packaged_goods
var food_beverage_restaurants
var gambling_betting
var gambling_betting_casino
var gambling_betting_sports_betting
var gaming
var gaming_console_pc
var gaming_esports
var gaming_mobile
var government_nonprofit
var government_nonprofit_charity
var government_nonprofit_political
var healthcare
var healthcare_medical_devices
var healthcare_pharmaceutical
var healthcare_wellness
var home_garden
var home_garden_furniture
var home_garden_home_improvement
var media_entertainment
var media_entertainment_film_tv
var media_entertainment_live_events
var media_entertainment_music
var media_entertainment_podcasts
var media_entertainment_publishing
var pets
var professional_services
var professional_services_consulting
var real_estate
var real_estate_commercial
var real_estate_residential
var recruitment_hr
var retail
var retail_department_stores
var retail_ecommerce
var sports_fitness
var sports_fitness_equipment
var sports_fitness_teams_leagues
var technology
var technology_ai_ml
var technology_hardware
var technology_software
var telecom
var telecom_internet_providers
var telecom_mobile_carriers
var transportation_logistics
var travel_hospitality
var travel_hospitality_airlines
var travel_hospitality_cruise
var travel_hospitality_hotels
var travel_hospitality_tourism
class AgentCapabilities (**data: Any)
Expand source code
class AgentCapabilities(RegistryBaseModel):
    tools_count: int
    tools: list[AgentTool] | None = None
    standard_operations: AgentStandardOperations | None = None
    creative_capabilities: AgentCreativeCapabilities | None = None

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var creative_capabilitiesAgentCreativeCapabilities | None
var model_config
var standard_operationsAgentStandardOperations | None
var tools : list[AgentTool] | None
var tools_count : int
class AgentCompliance (**data: Any)
Expand source code
class AgentCompliance(RegistryBaseModel):
    status: ComplianceStatus
    lifecycle_stage: AgentLifecycleStage
    tracks: Annotated[dict[str, str], Field(examples=[{"core": "pass", "products": "fail"}])]
    streak_days: int
    last_checked_at: str | None
    headline: str | None

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var headline : str | None
var last_checked_at : str | None
var lifecycle_stageAgentLifecycleStage
var model_config
var statusComplianceStatus
var streak_days : int
var tracks : dict[str, str]
class AgentConfig (**data: Any)
Expand source code
class AgentConfig(BaseModel):
    """Agent configuration."""

    id: str
    agent_uri: str
    protocol: Protocol
    auth_token: str | None = None
    requires_auth: bool = False
    auth_header: str = "x-adcp-auth"  # Header name for authentication
    auth_type: str = "token"  # "token" for direct value, "bearer" for "Bearer {token}"
    timeout: float = 30.0  # Request timeout in seconds
    mcp_transport: str = (
        "streamable_http"  # "streamable_http" (default, modern) or "sse" (legacy fallback)
    )
    debug: bool = False  # Enable debug mode to capture request/response details

    @field_validator("agent_uri")
    @classmethod
    def validate_agent_uri(cls, v: str) -> str:
        """Validate agent URI format."""
        if not v:
            raise ValueError("agent_uri cannot be empty")

        if not v.startswith(("http://", "https://")):
            raise ValueError(
                f"agent_uri must start with http:// or https://, got: {v}\n"
                "Example: https://agent.example.com"
            )

        # Remove trailing slash for consistency
        return v.rstrip("/")

    @field_validator("timeout")
    @classmethod
    def validate_timeout(cls, v: float) -> float:
        """Validate timeout is reasonable."""
        if v <= 0:
            raise ValueError(f"timeout must be positive, got: {v}")

        if v > 300:  # 5 minutes
            raise ValueError(
                f"timeout is very large ({v}s). Consider a value under 300 seconds.\n"
                "Large timeouts can cause long hangs if agent is unresponsive."
            )

        return v

    @field_validator("mcp_transport")
    @classmethod
    def validate_mcp_transport(cls, v: str) -> str:
        """Validate MCP transport type."""
        valid_transports = ["streamable_http", "sse"]
        if v not in valid_transports:
            raise ValueError(
                f"mcp_transport must be one of {valid_transports}, got: {v}\n"
                "Use 'streamable_http' for modern agents (recommended)"
            )
        return v

    @field_validator("auth_type")
    @classmethod
    def validate_auth_type(cls, v: str) -> str:
        """Validate auth type."""
        valid_types = ["token", "bearer"]
        if v not in valid_types:
            raise ValueError(
                f"auth_type must be one of {valid_types}, got: {v}\n"
                "Use 'bearer' for OAuth2/standard Authorization header"
            )
        return v

Agent configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var agent_uri : str
var auth_header : str
var auth_token : str | None
var auth_type : str
var debug : bool
var id : str
var mcp_transport : str
var model_config
var protocolProtocol
var requires_auth : bool
var timeout : float

Static methods

def validate_agent_uri(v: str) ‑> str

Validate agent URI format.

def validate_auth_type(v: str) ‑> str

Validate auth type.

def validate_mcp_transport(v: str) ‑> str

Validate MCP transport type.

def validate_timeout(v: float) ‑> float

Validate timeout is reasonable.

class AgentHealth (**data: Any)
Expand source code
class AgentHealth(RegistryBaseModel):
    online: bool
    checked_at: str
    response_time_ms: float | None = None
    tools_count: int | None = None
    resources_count: int | None = None
    error: str | None = None

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var checked_at : str
var error : str | None
var model_config
var online : bool
var resources_count : int | None
var response_time_ms : float | None
var tools_count : int | None
class AgentStats (**data: Any)
Expand source code
class AgentStats(RegistryBaseModel):
    property_count: int | None = None
    publisher_count: int | None = None
    publishers: list[str] | None = None
    creative_formats: int | None = None

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var creative_formats : int | None
var model_config
var property_count : int | None
var publisher_count : int | None
var publishers : list[str] | None
class ArtifactWebhookPayload (**data: Any)
Expand source code
class ArtifactWebhookPayload(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    idempotency_key: Annotated[
        str,
        Field(
            description="Sender-generated key stable across retries of the same webhook event. Sales agents MUST generate a cryptographically random value (UUID v4 recommended) per distinct emission of a batch and reuse the same key on every retry. Recipients MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential) — keys from different sales agents are independent. Distinct from `batch_id`, which identifies the logical batch: `idempotency_key` identifies this specific emission event, so a re-emission of the same `batch_id` (e.g., after a correction) is a different event and MUST carry a fresh `idempotency_key`.",
            max_length=255,
            min_length=16,
            pattern="^[A-Za-z0-9_.:-]{16,255}$",
        ),
    ]
    media_buy_id: Annotated[
        str, Field(description="Media buy identifier these artifacts belong to")
    ]
    batch_id: Annotated[
        str,
        Field(
            description="Unique identifier for this batch of artifacts. Use for deduplication and acknowledgment."
        ),
    ]
    timestamp: Annotated[
        AwareDatetime, Field(description="When this batch was generated (ISO 8601)")
    ]
    artifacts: Annotated[
        list[Artifact], Field(description="Content artifacts from delivered impressions")
    ]
    pagination: Annotated[
        Pagination | None, Field(description="Pagination info when batching large artifact sets")
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var artifacts : list[Artifact]
var batch_id : str
var extExtensionObject | None
var idempotency_key : str
var media_buy_id : str
var model_config
var paginationPagination | None
var timestamp : pydantic.types.AwareDatetime

Inherited members

class AssetContentType (*args, **kwds)
Expand source code
class AssetContentType(Enum):
    image = 'image'
    video = 'video'
    audio = 'audio'
    text = 'text'
    markdown = 'markdown'
    html = 'html'
    css = 'css'
    javascript = 'javascript'
    vast = 'vast'
    daast = 'daast'
    url = 'url'
    webhook = 'webhook'
    brief = 'brief'
    catalog = 'catalog'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var audio
var brief
var catalog
var css
var daast
var html
var image
var javascript
var markdown
var text
var url
var vast
var video
var webhook
class SyncAudiencesAudience (**data: Any)
Expand source code
class Audience(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    audience_id: Annotated[
        str,
        Field(
            description="Buyer's identifier for this audience. Used to reference the audience in targeting overlays."
        ),
    ]
    name: Annotated[str | None, Field(description='Human-readable name for this audience')] = None
    description: Annotated[
        str | None,
        Field(
            description="Human-readable description of this audience's composition or purpose (e.g., 'High-value customers who purchased in the last 90 days')."
        ),
    ] = None
    audience_type: Annotated[
        AudienceType | None,
        Field(
            description="Intended use for this audience. 'crm': target these users. 'suppression': exclude these users from delivery. 'lookalike_seed': use as a seed for the seller's lookalike modeling. Sellers may handle audiences differently based on type (e.g., suppression lists bypass minimum size requirements on some platforms)."
        ),
    ] = None
    tags: Annotated[
        list[Tag] | None,
        Field(
            description="Buyer-defined tags for organizing and filtering audiences (e.g., 'holiday_2026', 'high_ltv'). Tags are stored by the seller and returned in discovery-only calls."
        ),
    ] = None
    add: Annotated[
        list[audience_member.AudienceMember] | None,
        Field(
            description='Members to add to this audience. Hashed before sending — normalize emails to lowercase+trim, phones to E.164.',
            min_length=1,
        ),
    ] = None
    remove: Annotated[
        list[audience_member.AudienceMember] | None,
        Field(
            description='Members to remove from this audience. If the same identifier appears in both add and remove in a single request, remove takes precedence.',
            min_length=1,
        ),
    ] = None
    delete: Annotated[
        bool | None,
        Field(
            description='When true, delete this audience from the account entirely. All other fields on this audience object are ignored. Use this to delete a specific audience without affecting others.'
        ),
    ] = None
    consent_basis: Annotated[
        consent_basis_1.ConsentBasis | None,
        Field(
            description='GDPR lawful basis for processing this audience list. Informational — not validated by the protocol, but required by some sellers operating in regulated markets (e.g. EU). When omitted, the buyer asserts they have a lawful basis appropriate to their jurisdiction.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var add : list[AudienceMember] | None
var audience_id : str
var audience_typeAudienceType | None
var consent_basisConsentBasis | None
var delete : bool | None
var description : str | None
var model_config
var name : str | None
var remove : list[AudienceMember] | None
var tags : list[Tag] | None

Inherited members

class AudienceSource (*args, **kwds)
Expand source code
class AudienceSource(Enum):
    synced = 'synced'
    platform = 'platform'
    third_party = 'third_party'
    lookalike = 'lookalike'
    retargeting = 'retargeting'
    unknown = 'unknown'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var lookalike
var platform
var retargeting
var synced
var third_party
var unknown
class AuthorizationContext (properties: list[dict[str, Any]])
Expand source code
class AuthorizationContext:
    """Authorization context for a publisher domain.

    Attributes:
        property_ids: List of property IDs the agent is authorized for
        property_tags: List of property tags the agent is authorized for
        raw_properties: Raw property data from adagents.json
    """

    def __init__(self, properties: list[dict[str, Any]]):
        """Initialize from list of properties.

        Args:
            properties: List of property dictionaries from adagents.json
        """
        self.property_ids: list[str] = []
        self.property_tags: list[str] = []
        self.raw_properties = properties

        # Extract property IDs and tags
        for prop in properties:
            if not isinstance(prop, dict):
                continue

            # Extract property ID (per AdCP v2 schema, the field is "property_id")
            prop_id = prop.get("property_id")
            if prop_id and isinstance(prop_id, str):
                self.property_ids.append(prop_id)

            # Extract tags
            tags = prop.get("tags", [])
            if isinstance(tags, list):
                for tag in tags:
                    if isinstance(tag, str) and tag not in self.property_tags:
                        self.property_tags.append(tag)

    def __repr__(self) -> str:
        return (
            f"AuthorizationContext("
            f"property_ids={self.property_ids}, "
            f"property_tags={self.property_tags})"
        )

Authorization context for a publisher domain.

Attributes

property_ids
List of property IDs the agent is authorized for
property_tags
List of property tags the agent is authorized for
raw_properties
Raw property data from adagents.json

Initialize from list of properties.

Args

properties
List of property dictionaries from adagents.json
class AuthorizedAgentsByPropertyId (**data: Any)
Expand source code
class AuthorizedAgents(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")]
    authorized_for: Annotated[
        str,
        Field(
            description='Human-readable description of what this agent is authorized to sell',
            max_length=500,
            min_length=1,
        ),
    ]
    authorization_type: Annotated[
        Literal['property_ids'],
        Field(description='Discriminator indicating authorization by specific property IDs'),
    ]
    property_ids: Annotated[
        list[property_id.PropertyId],
        Field(
            description='Property IDs this agent is authorized for. Resolved against the top-level properties array in this file',
            min_length=1,
        ),
    ]
    collections: Annotated[
        list[collection_selector.CollectionSelector] | None,
        Field(
            description='Optional collection constraints. When present, authorization only applies to inventory associated with these collections.',
            min_length=1,
        ),
    ] = None
    placement_ids: Annotated[
        list[str] | None,
        Field(
            description='Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.',
            min_length=1,
        ),
    ] = None
    placement_tags: Annotated[
        list[str] | None,
        Field(
            description='Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.',
            min_length=1,
        ),
    ] = None
    delegation_type: Annotated[
        DelegationType | None,
        Field(
            description="Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint."
        ),
    ] = None
    exclusive: Annotated[
        bool | None,
        Field(
            description="Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory."
        ),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description='Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.',
            min_length=1,
        ),
    ] = None
    effective_from: Annotated[
        AwareDatetime | None,
        Field(description='Optional start time for this authorization window.'),
    ] = None
    effective_until: Annotated[
        AwareDatetime | None, Field(description='Optional end time for this authorization window.')
    ] = None
    signing_keys: Annotated[
        list[agent_signing_key.AgentSigningKey] | None,
        Field(
            description='Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.',
            min_length=1,
        ),
    ] = None
    encryption_keys: Annotated[
        list[agent_encryption_key.AgentEncryptionKey] | None,
        Field(
            description='X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var authorization_type : Literal['property_ids']
var authorized_for : str
var collections : list[CollectionSelector] | None
var countries : list[Country] | None
var delegation_typeDelegationType | None
var effective_from : pydantic.types.AwareDatetime | None
var effective_until : pydantic.types.AwareDatetime | None
var encryption_keys : list[AgentEncryptionKey] | None
var exclusive : bool | None
var model_config
var placement_ids : list[str] | None
var placement_tags : list[str] | None
var property_ids : list[PropertyId]
var signing_keys : list[AgentSigningKey] | None
var url : pydantic.networks.AnyUrl

Inherited members

class AuthorizedAgentsByPropertyTag (**data: Any)
Expand source code
class AuthorizedAgents1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")]
    authorized_for: Annotated[
        str,
        Field(
            description='Human-readable description of what this agent is authorized to sell',
            max_length=500,
            min_length=1,
        ),
    ]
    authorization_type: Annotated[
        Literal['property_tags'],
        Field(description='Discriminator indicating authorization by property tags'),
    ]
    property_tags: Annotated[
        list[property_tag.PropertyTag],
        Field(
            description='Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching',
            min_length=1,
        ),
    ]
    collections: Annotated[
        list[collection_selector.CollectionSelector] | None,
        Field(
            description='Optional collection constraints. When present, authorization only applies to inventory associated with these collections.',
            min_length=1,
        ),
    ] = None
    placement_ids: Annotated[
        list[str] | None,
        Field(
            description='Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.',
            min_length=1,
        ),
    ] = None
    placement_tags: Annotated[
        list[str] | None,
        Field(
            description='Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.',
            min_length=1,
        ),
    ] = None
    delegation_type: Annotated[
        DelegationType | None,
        Field(
            description="Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint."
        ),
    ] = None
    exclusive: Annotated[
        bool | None,
        Field(
            description="Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory."
        ),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description='Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.',
            min_length=1,
        ),
    ] = None
    effective_from: Annotated[
        AwareDatetime | None,
        Field(description='Optional start time for this authorization window.'),
    ] = None
    effective_until: Annotated[
        AwareDatetime | None, Field(description='Optional end time for this authorization window.')
    ] = None
    signing_keys: Annotated[
        list[agent_signing_key.AgentSigningKey] | None,
        Field(
            description='Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.',
            min_length=1,
        ),
    ] = None
    encryption_keys: Annotated[
        list[agent_encryption_key.AgentEncryptionKey] | None,
        Field(
            description='X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var authorization_type : Literal['property_tags']
var authorized_for : str
var collections : list[CollectionSelector] | None
var countries : list[Country] | None
var delegation_typeDelegationType | None
var effective_from : pydantic.types.AwareDatetime | None
var effective_until : pydantic.types.AwareDatetime | None
var encryption_keys : list[AgentEncryptionKey] | None
var exclusive : bool | None
var model_config
var placement_ids : list[str] | None
var placement_tags : list[str] | None
var property_tags : list[PropertyTag]
var signing_keys : list[AgentSigningKey] | None
var url : pydantic.networks.AnyUrl

Inherited members

class AuthorizedAgentsByInlineProperties (**data: Any)
Expand source code
class AuthorizedAgents2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")]
    authorized_for: Annotated[
        str,
        Field(
            description='Human-readable description of what this agent is authorized to sell',
            max_length=500,
            min_length=1,
        ),
    ]
    authorization_type: Annotated[
        Literal['inline_properties'],
        Field(description='Discriminator indicating authorization by inline property definitions'),
    ]
    properties: Annotated[
        list[property.Property],
        Field(
            description='Specific properties this agent is authorized for (alternative to property_ids/property_tags)',
            min_length=1,
        ),
    ]
    collections: Annotated[
        list[collection_selector.CollectionSelector] | None,
        Field(
            description='Optional collection constraints. When present, authorization only applies to inventory associated with these collections.',
            min_length=1,
        ),
    ] = None
    placement_ids: Annotated[
        list[str] | None,
        Field(
            description='Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.',
            min_length=1,
        ),
    ] = None
    placement_tags: Annotated[
        list[str] | None,
        Field(
            description='Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.',
            min_length=1,
        ),
    ] = None
    delegation_type: Annotated[
        DelegationType | None,
        Field(
            description="Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint."
        ),
    ] = None
    exclusive: Annotated[
        bool | None,
        Field(
            description="Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory."
        ),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description='Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.',
            min_length=1,
        ),
    ] = None
    effective_from: Annotated[
        AwareDatetime | None,
        Field(description='Optional start time for this authorization window.'),
    ] = None
    effective_until: Annotated[
        AwareDatetime | None, Field(description='Optional end time for this authorization window.')
    ] = None
    signing_keys: Annotated[
        list[agent_signing_key.AgentSigningKey] | None,
        Field(
            description='Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.',
            min_length=1,
        ),
    ] = None
    encryption_keys: Annotated[
        list[agent_encryption_key.AgentEncryptionKey] | None,
        Field(
            description='X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var authorization_type : Literal['inline_properties']
var authorized_for : str
var collections : list[CollectionSelector] | None
var countries : list[Country] | None
var delegation_typeDelegationType | None
var effective_from : pydantic.types.AwareDatetime | None
var effective_until : pydantic.types.AwareDatetime | None
var encryption_keys : list[AgentEncryptionKey] | None
var exclusive : bool | None
var model_config
var placement_ids : list[str] | None
var placement_tags : list[str] | None
var properties : list[Property]
var signing_keys : list[AgentSigningKey] | None
var url : pydantic.networks.AnyUrl

Inherited members

class AuthorizedAgentsByPublisherProperties (**data: Any)
Expand source code
class AuthorizedAgents3(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")]
    authorized_for: Annotated[
        str,
        Field(
            description='Human-readable description of what this agent is authorized to sell',
            max_length=500,
            min_length=1,
        ),
    ]
    authorization_type: Annotated[
        Literal['publisher_properties'],
        Field(
            description='Discriminator indicating authorization for properties from other publisher domains'
        ),
    ]
    publisher_properties: Annotated[
        list[publisher_property_selector.PublisherPropertySelector],
        Field(
            description='Properties from other publisher domains this agent is authorized for. Each entry specifies a publisher domain and which of their properties this agent can sell',
            min_length=1,
        ),
    ]
    collections: Annotated[
        list[collection_selector.CollectionSelector] | None,
        Field(
            description='Optional collection constraints. When present, authorization only applies to inventory associated with these collections.',
            min_length=1,
        ),
    ] = None
    placement_ids: Annotated[
        list[str] | None,
        Field(
            description='Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.',
            min_length=1,
        ),
    ] = None
    placement_tags: Annotated[
        list[str] | None,
        Field(
            description='Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.',
            min_length=1,
        ),
    ] = None
    delegation_type: Annotated[
        DelegationType | None,
        Field(
            description="Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint."
        ),
    ] = None
    exclusive: Annotated[
        bool | None,
        Field(
            description="Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory."
        ),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description='Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.',
            min_length=1,
        ),
    ] = None
    effective_from: Annotated[
        AwareDatetime | None,
        Field(description='Optional start time for this authorization window.'),
    ] = None
    effective_until: Annotated[
        AwareDatetime | None, Field(description='Optional end time for this authorization window.')
    ] = None
    signing_keys: Annotated[
        list[agent_signing_key.AgentSigningKey] | None,
        Field(
            description='Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.',
            min_length=1,
        ),
    ] = None
    encryption_keys: Annotated[
        list[agent_encryption_key.AgentEncryptionKey] | None,
        Field(
            description='X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var authorization_type : Literal['publisher_properties']
var authorized_for : str
var collections : list[CollectionSelector] | None
var countries : list[Country] | None
var delegation_typeDelegationType | None
var effective_from : pydantic.types.AwareDatetime | None
var effective_until : pydantic.types.AwareDatetime | None
var encryption_keys : list[AgentEncryptionKey] | None
var exclusive : bool | None
var model_config
var placement_ids : list[str] | None
var placement_tags : list[str] | None
var publisher_properties : list[PublisherPropertySelector]
var signing_keys : list[AgentSigningKey] | None
var url : pydantic.networks.AnyUrl

Inherited members

class AuthorizedAgentsBySignalId (**data: Any)
Expand source code
class AuthorizedAgents4(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    url: Annotated[AnyUrl, Field(description="The authorized signals agent's API endpoint URL")]
    authorized_for: Annotated[
        str,
        Field(
            description='Human-readable description of what signals this agent is authorized to resell',
            max_length=500,
            min_length=1,
        ),
    ]
    authorization_type: Annotated[
        Literal['signal_ids'],
        Field(description='Discriminator indicating authorization by specific signal IDs'),
    ]
    signal_ids: Annotated[
        list[SignalId],
        Field(
            description='Signal IDs this agent is authorized to resell. Resolved against the top-level signals array in this file',
            min_length=1,
        ),
    ]
    signing_keys: Annotated[
        list[agent_signing_key.AgentSigningKey] | None,
        Field(
            description='Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.',
            min_length=1,
        ),
    ] = None
    encryption_keys: Annotated[
        list[agent_encryption_key.AgentEncryptionKey] | None,
        Field(
            description='X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var authorization_type : Literal['signal_ids']
var authorized_for : str
var encryption_keys : list[AgentEncryptionKey] | None
var model_config
var signal_ids : list[SignalId]
var signing_keys : list[AgentSigningKey] | None
var url : pydantic.networks.AnyUrl

Inherited members

class AuthorizedAgentsBySignalTag (**data: Any)
Expand source code
class AuthorizedAgents5(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    url: Annotated[AnyUrl, Field(description="The authorized signals agent's API endpoint URL")]
    authorized_for: Annotated[
        str,
        Field(
            description='Human-readable description of what signals this agent is authorized to resell',
            max_length=500,
            min_length=1,
        ),
    ]
    authorization_type: Annotated[
        Literal['signal_tags'],
        Field(description='Discriminator indicating authorization by signal tags'),
    ]
    signal_tags: Annotated[
        list[SignalTag],
        Field(
            description='Signal tags this agent is authorized for. Agent can resell all signals with these tags',
            min_length=1,
        ),
    ]
    signing_keys: Annotated[
        list[agent_signing_key.AgentSigningKey] | None,
        Field(
            description='Optional publisher-attested public signing keys for this agent. Use these as the trust anchor for verifying signed agent responses instead of relying on key discovery from the agent domain alone.',
            min_length=1,
        ),
    ] = None
    encryption_keys: Annotated[
        list[agent_encryption_key.AgentEncryptionKey] | None,
        Field(
            description='X25519 public keys for TMPX exposure token encryption. Each key identifies a cluster master that can decrypt TMPX tokens. Used with HPKE mode_base — read replicas encrypt with this public key, only the master can decrypt.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var authorization_type : Literal['signal_tags']
var authorized_for : str
var encryption_keys : list[AgentEncryptionKey] | None
var model_config
var signal_tags : list[SignalTag]
var signing_keys : list[AgentSigningKey] | None
var url : pydantic.networks.AnyUrl

Inherited members

class BrandActivity (**data: Any)
Expand source code
class BrandActivity(RegistryBaseModel):
    domain: Annotated[str, Field(examples=["acmecorp.com"])]
    total: Annotated[int, Field(examples=[3])]
    revisions: list[ActivityRevision]

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var domain : str
var model_config
var revisions : list[ActivityRevision]
var total : int
class BrandReference (**data: Any)
Expand source code
class BrandReference(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    domain: Annotated[
        str,
        Field(
            description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain",
            pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$',
        ),
    ]
    brand_id: Annotated[
        brand_id_1.BrandId | None,
        Field(
            description='Brand identifier within the house portfolio. Optional for single-brand domains.'
        ),
    ] = None
    industries: Annotated[
        list[str] | None,
        Field(
            description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json."
        ),
    ] = None
    data_subject_contestation: Annotated[
        DataSubjectContestation | DataSubjectContestation5 | None,
        Field(
            description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing."
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var brand_idBrandId | None
var data_subject_contestationDataSubjectContestation | DataSubjectContestation5 | None
var domain : str
var industries : list[str] | None
var model_config

Inherited members

class BrandRegistryItem (**data: Any)
Expand source code
class BrandRegistryItem(RegistryBaseModel):
    domain: Annotated[str, Field(examples=["acmecorp.com"])]
    brand_name: Annotated[str | None, Field(examples=["Acme Corp"])] = None
    source: BrandRegistrySource
    has_manifest: bool
    verified: bool
    house_domain: str | None = None
    keller_type: KellerType | None = None

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var brand_name : str | None
var domain : str
var has_manifest : bool
var house_domain : str | None
var keller_typeKellerType | None
var model_config
var sourceBrandRegistrySource
var verified : bool
class BrandSource (*args, **kwds)
Expand source code
class BrandSource(Enum):
    brand_json = "brand_json"
    community = "community"
    enriched = "enriched"

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var brand_json
var community
var enriched
class BuildCreativeRequest (**data: Any)
Expand source code
class BuildCreativeRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    message: Annotated[
        str | None,
        Field(
            description='Natural language instructions for the transformation or generation. For pure generation, this is the creative brief. For transformation, this provides guidance on how to adapt the creative. For refinement, this describes the desired changes.'
        ),
    ] = None
    creative_manifest: Annotated[
        creative_manifest_1.CreativeManifest | None,
        Field(
            description='Creative manifest to transform or generate from. For pure generation, this should include the target format_id and any required input assets. For transformation (e.g., resizing, reformatting), this is the complete creative to adapt. When creative_id is provided, the agent resolves the creative from its library and this field is ignored.'
        ),
    ] = None
    creative_id: Annotated[
        str | None,
        Field(
            description="Reference to a creative in the agent's library. The creative agent resolves this to a manifest from its library. Use this instead of creative_manifest when retrieving an existing creative for tag generation or format adaptation."
        ),
    ] = None
    concept_id: Annotated[
        str | None,
        Field(
            description='Creative concept containing the creative. Creative agents SHOULD assign globally unique creative_id values; when they cannot guarantee uniqueness, concept_id is REQUIRED to disambiguate.'
        ),
    ] = None
    media_buy_id: Annotated[
        str | None,
        Field(
            description='Media buy identifier for tag generation context. When the creative agent is also the ad server, this provides the trafficking context needed to generate placement-specific tags (e.g., CM360 placement ID). Not needed when tags are generated at the creative level (most creative platforms).'
        ),
    ] = None
    package_id: Annotated[
        str | None,
        Field(
            description='Package identifier within the media buy. Used with media_buy_id when the creative agent needs line-item-level context for tag generation. Omit to get a tag not scoped to a specific package.'
        ),
    ] = None
    target_format_id: Annotated[
        format_id.FormatId | None,
        Field(
            description='Single format ID to generate. Mutually exclusive with target_format_ids. The format definition specifies required input assets and output structure.'
        ),
    ] = None
    target_format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description='Array of format IDs to generate in a single call. Mutually exclusive with target_format_id. The creative agent produces one manifest per format. Each format definition specifies its own required input assets and output structure.',
            min_length=1,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account reference for pricing and billing. When present, the creative agent applies account-specific pricing from the rate card, records the build against the account for billing, and can enforce account-level quotas or entitlements. Required by creative agents that charge for their services.'
        ),
    ] = None
    brand: Annotated[
        brand_ref.BrandReference | None,
        Field(
            description='Brand reference for creative generation. Resolved to full brand identity (colors, logos, tone) at execution time.'
        ),
    ] = None
    quality: Annotated[
        creative_quality.CreativeQuality | None,
        Field(
            description="Quality tier for generation. 'draft' produces fast, lower-fidelity output for iteration and review. 'production' produces full-quality output for final delivery. If omitted, the creative agent uses its own default. For non-generative transforms (e.g., format resizing), creative agents MAY ignore this field."
        ),
    ] = None
    item_limit: Annotated[
        int | None,
        Field(
            description="Maximum number of catalog items to use when generating. When a catalog asset contains more items than this limit, the creative agent selects the top items based on relevance or catalog ordering. When item_limit exceeds the format's max_items, the creative agent SHOULD use the lesser of the two. Ignored when the manifest contains no catalog assets.",
            ge=1,
        ),
    ] = None
    include_preview: Annotated[
        bool | None,
        Field(
            description="When true, requests the creative agent to include preview renders in the response alongside the manifest. Agents that support this return a 'preview' object in the response using the same structure as preview_creative. Agents that do not support inline preview simply omit the field. This avoids a separate preview_creative round trip for platforms that generate previews as a byproduct of building."
        ),
    ] = None
    preview_inputs: Annotated[
        list[PreviewInput] | None,
        Field(
            description='Input sets for preview generation when include_preview is true. Each input set defines macros and context values for one preview variant. If include_preview is true but this is omitted, the agent generates a single default preview. Only supported with target_format_id (single-format requests). Ignored when using target_format_ids — multi-format requests generate one default preview per format. Ignored when include_preview is false or omitted.',
            min_length=1,
        ),
    ] = None
    preview_quality: Annotated[
        creative_quality.CreativeQuality | None,
        Field(
            description="Render quality for inline preview when include_preview is true. 'draft' produces fast, lower-fidelity renderings. 'production' produces full-quality renderings. Independent of the build quality parameter — you can build at draft quality and preview at production quality, or vice versa. If omitted, the creative agent uses its own default. Ignored when include_preview is false or omitted."
        ),
    ] = None
    preview_output_format: Annotated[
        preview_output_format_1.PreviewOutputFormat | None,
        Field(
            description="Output format for preview renders when include_preview is true. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). Ignored when include_preview is false or omitted."
        ),
    ] = preview_output_format_1.PreviewOutputFormat.url
    macro_values: Annotated[
        dict[str, str] | None,
        Field(
            description="Macro values to pre-substitute into the output manifest's assets. Keys are universal macro names (e.g., CLICK_URL, CACHEBUSTER); values are the substitution strings. The creative agent translates universal macros to its platform's native syntax. Substitution is literal — all occurrences of each macro in output assets are replaced with the provided value. The caller is responsible for URL-encoding values if the output context requires it. Macros not provided here remain as {MACRO} placeholders for the sales agent to resolve at serve time. Creative agents MUST ignore keys they do not recognize — unknown macro names are not an error."
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for this request. Prevents duplicate creative generation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var brandBrandReference | None
var concept_id : str | None
var contextContextObject | None
var creative_id : str | None
var creative_manifestCreativeManifest | None
var extExtensionObject | None
var idempotency_key : str
var include_preview : bool | None
var item_limit : int | None
var macro_values : dict[str, str] | None
var media_buy_id : str | None
var message : str | None
var model_config
var package_id : str | None
var preview_inputs : list[PreviewInput] | None
var preview_output_formatPreviewOutputFormat | None
var preview_qualityCreativeQuality | None
var qualityCreativeQuality | None
var target_format_idFormatId | None
var target_format_ids : list[FormatId] | None

Inherited members

class BuildCreativeSuccessResponse (**data: Any)
Expand source code
class BuildCreativeResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    creative_manifest: Annotated[
        creative_manifest_1.CreativeManifest,
        Field(description='The generated or transformed creative manifest'),
    ]
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    expires_at: Annotated[
        AwareDatetime | None,
        Field(
            description='ISO 8601 timestamp when generated asset URLs in the manifest expire. Set to the earliest expiration across all generated assets. Re-build the creative after this time to get fresh URLs.'
        ),
    ] = None
    preview: Annotated[
        Preview | None,
        Field(
            description='Preview renders included when the request set include_preview to true and the agent supports it. Contains the same content fields as a preview_creative single response (previews, interactive_url, expires_at) minus the response_type discriminator, so clients can reuse the same preview rendering logic.'
        ),
    ] = None
    preview_error: Annotated[
        error.Error | None,
        Field(
            description="When include_preview was true in the request but preview generation failed. Uses the standard error structure with code, message, and recovery classification. Distinguishes 'agent does not support inline preview' (preview and preview_error both absent) from 'preview generation failed' (preview absent, preview_error present). Omitted when preview succeeded or was not requested."
        ),
    ] = None
    pricing_option_id: Annotated[
        str | None,
        Field(
            description='Which rate card pricing option was applied for this build. Present when the creative agent charges for its services. Pass this in report_usage to identify which pricing option was applied.'
        ),
    ] = None
    vendor_cost: Annotated[
        float | None,
        Field(
            description='Cost incurred for this build, denominated in currency. May be 0 for CPM-priced creatives where cost accrues at serve time rather than build time.',
            ge=0.0,
        ),
    ] = None
    currency: Annotated[
        str | None,
        Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'),
    ] = None
    consumption: Annotated[
        creative_consumption.CreativeConsumption | None,
        Field(
            description='Structured consumption details for this build. Informational — lets the buyer verify that vendor_cost is consistent with the rate card. vendor_cost is the billing source of truth.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var consumptionCreativeConsumption | None
var contextContextObject | None
var creative_manifestCreativeManifest
var currency : str | None
var expires_at : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var model_config
var previewPreview | None
var preview_errorError | None
var pricing_option_id : str | None
var sandbox : bool | None
var vendor_cost : float | None

Inherited members

class BuildCreativeErrorResponse (**data: Any)
Expand source code
class BuildCreativeResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    creative_manifests: Annotated[
        list[creative_manifest_1.CreativeManifest],
        Field(
            description='Array of generated creative manifests, one per requested format. Each manifest contains its own format_id identifying which format it was generated for.',
            min_length=1,
        ),
    ]
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    expires_at: Annotated[
        AwareDatetime | None,
        Field(
            description='ISO 8601 timestamp when the earliest generated asset URL expires across all manifests. Re-build after this time to get fresh URLs.'
        ),
    ] = None
    preview: Annotated[
        Preview2 | None,
        Field(
            description='Preview renders included when the request set include_preview to true and the agent supports it. Contains one default preview per requested format. preview_inputs is ignored for multi-format requests.'
        ),
    ] = None
    preview_error: Annotated[
        error.Error | None,
        Field(
            description='When include_preview was true in the request but preview generation failed. Uses the standard error structure with code, message, and recovery classification. Omitted when preview succeeded or was not requested.'
        ),
    ] = None
    pricing_option_id: Annotated[
        str | None,
        Field(
            description='Which rate card pricing option was applied for this build. Represents the total cost of the entire multi-format build call. Present when the creative agent charges for its services.'
        ),
    ] = None
    vendor_cost: Annotated[
        float | None,
        Field(
            description='Total cost incurred for this multi-format build, denominated in currency. May be 0 for CPM-priced creatives where cost accrues at serve time.',
            ge=0.0,
        ),
    ] = None
    currency: Annotated[
        str | None,
        Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'),
    ] = None
    consumption: Annotated[
        creative_consumption.CreativeConsumption | None,
        Field(
            description='Structured consumption details for this build. Informational — lets the buyer verify that vendor_cost is consistent with the rate card.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var consumptionCreativeConsumption | None
var contextContextObject | None
var creative_manifests : list[CreativeManifest]
var currency : str | None
var expires_at : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var model_config
var previewPreview2 | None
var preview_errorError | None
var pricing_option_id : str | None
var sandbox : bool | None
var vendor_cost : float | None

Inherited members

class BuyingMode (*args, **kwds)
Expand source code
class BuyingMode(Enum):
    brief = 'brief'
    wholesale = 'wholesale'
    refine = 'refine'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var brief
var refine
var wholesale
class CalibrateContentSuccessResponse (**data: Any)
Expand source code
class CalibrateContentResponse1(AdCPBaseModel):
    verdict: Annotated[
        Verdict, Field(description='Overall pass/fail verdict for the content evaluation')
    ]
    confidence: Annotated[
        float | None, Field(description='Model confidence in the verdict (0-1)', ge=0.0, le=1.0)
    ] = None
    explanation: Annotated[
        str | None, Field(description='Detailed natural language explanation of the decision')
    ] = None
    features: Annotated[
        list[Feature] | None,
        Field(
            description='Per-feature breakdown with explanations. Mirrors validate_content_delivery feature shape so calibration loops can correlate against production verdicts by policy_id.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var confidence : float | None
var contextContextObject | None
var explanation : str | None
var extExtensionObject | None
var features : list[Feature] | None
var model_config
var verdictVerdict

Inherited members

class CalibrateContentErrorResponse (**data: Any)
Expand source code
class CalibrateContentResponse2(AdCPBaseModel):
    errors: list[error.Error]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class Catalog (**data: Any)
Expand source code
class Catalog(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    catalog_id: Annotated[
        str | None,
        Field(
            description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account."
        ),
    ] = None
    name: Annotated[
        str | None,
        Field(
            description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')."
        ),
    ] = None
    type: Annotated[
        catalog_type.CatalogType,
        Field(
            description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema."
        ),
    ]
    url: Annotated[
        AnyUrl | None,
        Field(
            description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog."
        ),
    ] = None
    feed_format: Annotated[
        feed_format_1.FeedFormat | None,
        Field(
            description='Format of the external feed at url. Required when url points to a non-AdCP feed (e.g., Google Merchant Center XML, Meta Product Catalog). Omit for offering-type catalogs where the feed is native AdCP JSON.'
        ),
    ] = None
    update_frequency: Annotated[
        update_frequency_1.UpdateFrequency | None,
        Field(
            description='How often the platform should re-fetch the feed from url. Only applicable when url is provided. Platforms may use this as a hint for polling schedules.'
        ),
    ] = None
    items: Annotated[
        list[dict[str, Any]] | None,
        Field(
            description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.",
            min_length=1,
        ),
    ] = None
    ids: Annotated[
        list[str] | None,
        Field(
            description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.',
            min_length=1,
        ),
    ] = None
    gtins: Annotated[
        list[Gtin] | None,
        Field(
            description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.",
            min_length=1,
        ),
    ] = None
    tags: Annotated[
        list[str] | None,
        Field(
            description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.',
            min_length=1,
        ),
    ] = None
    category: Annotated[
        str | None,
        Field(
            description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')."
        ),
    ] = None
    query: Annotated[
        str | None,
        Field(
            description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')."
        ),
    ] = None
    conversion_events: Annotated[
        list[event_type.EventType] | None,
        Field(
            description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.",
            min_length=1,
        ),
    ] = None
    content_id_type: Annotated[
        content_id_type_1.ContentIdType | None,
        Field(
            description="Identifier type that the event's content_ids field should be matched against for items in this catalog. For example, 'gtin' means content_ids values are Global Trade Item Numbers, 'sku' means retailer SKUs. Omit when using a custom identifier scheme not listed in the enum."
        ),
    ] = None
    feed_field_mappings: Annotated[
        list[catalog_field_mapping.CatalogFieldMapping] | None,
        Field(
            description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Subclasses

Class variables

var catalog_id : str | None
var category : str | None
var content_id_typeContentIdType | None
var conversion_events : list[EventType] | None
var feed_field_mappings : list[CatalogFieldMapping] | None
var feed_formatFeedFormat | None
var gtins : list[Gtin] | None
var ids : list[str] | None
var items : list[dict[str, typing.Any]] | None
var model_config
var name : str | None
var query : str | None
var tags : list[str] | None
var typeCatalogType
var update_frequencyUpdateFrequency | None
var url : pydantic.networks.AnyUrl | None
class SyncCatalogResult (**data: Any)
Expand source code
class Catalog(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    catalog_id: Annotated[str, Field(description='Catalog ID from the request')]
    action: Annotated[
        catalog_action.CatalogAction, Field(description='Action taken for this catalog')
    ]
    platform_id: Annotated[
        str | None, Field(description='Platform-specific ID assigned to the catalog')
    ] = None
    item_count: Annotated[
        int | None, Field(description='Total number of items in the catalog after sync', ge=0)
    ] = None
    items_approved: Annotated[
        int | None,
        Field(
            description='Number of items approved by the platform. Populated when the platform performs item-level review.',
            ge=0,
        ),
    ] = None
    items_pending: Annotated[
        int | None,
        Field(
            description='Number of items pending platform review. Common for product catalogs where items must pass content policy checks.',
            ge=0,
        ),
    ] = None
    items_rejected: Annotated[
        int | None,
        Field(
            description='Number of items rejected by the platform. Check item_issues for rejection reasons.',
            ge=0,
        ),
    ] = None
    item_issues: Annotated[
        list[ItemIssue] | None,
        Field(
            description='Per-item issues reported by the platform (rejections, warnings). Only present when the platform performs item-level review.'
        ),
    ] = None
    last_synced_at: Annotated[
        AwareDatetime | None,
        Field(
            description='ISO 8601 timestamp of when the most recent sync was accepted by the platform'
        ),
    ] = None
    next_fetch_at: Annotated[
        AwareDatetime | None,
        Field(
            description='ISO 8601 timestamp of when the platform will next fetch the feed URL. Only present for URL-based catalogs with update_frequency.'
        ),
    ] = None
    changes: Annotated[
        list[str] | None,
        Field(description="Field names that were modified (only present when action='updated')"),
    ] = None
    errors: Annotated[
        list[error.Error] | None,
        Field(description="Validation or processing errors (only present when action='failed')"),
    ] = None
    warnings: Annotated[
        list[str] | None, Field(description='Non-fatal warnings about this catalog')
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var actionCatalogAction
var catalog_id : str
var changes : list[str] | None
var errors : list[Error] | None
var item_count : int | None
var item_issues : list[ItemIssue] | None
var items_approved : int | None
var items_pending : int | None
var items_rejected : int | None
var last_synced_at : pydantic.types.AwareDatetime | None
var model_config
var next_fetch_at : pydantic.types.AwareDatetime | None
var platform_id : str | None
var warnings : list[str] | None

Inherited members

class CatalogAction (*args, **kwds)
Expand source code
class CatalogAction(Enum):
    created = 'created'
    updated = 'updated'
    unchanged = 'unchanged'
    failed = 'failed'
    deleted = 'deleted'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var created
var deleted
var failed
var unchanged
var updated
class CatalogFieldBinding (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class CatalogFieldBinding(RootModel[ScalarBinding | AssetPoolBinding | CatalogFieldBinding1]):
    root: Annotated[
        ScalarBinding | AssetPoolBinding | CatalogFieldBinding1,
        Field(
            description="Maps a format template slot to a catalog item field or typed asset pool. The 'kind' field identifies the binding variant. All bindings are optional — agents can still infer mappings without them.",
            examples=[
                {
                    'description': 'Scalar binding — hotel name to headline slot',
                    'data': {'kind': 'scalar', 'asset_id': 'headline', 'catalog_field': 'name'},
                },
                {
                    'description': 'Scalar binding — nested field (nightly rate)',
                    'data': {
                        'kind': 'scalar',
                        'asset_id': 'price_badge',
                        'catalog_field': 'price.amount',
                    },
                },
                {
                    'description': 'Asset pool binding — hero image from landscape pool',
                    'data': {
                        'kind': 'asset_pool',
                        'asset_id': 'hero_image',
                        'asset_group_id': 'images_landscape',
                    },
                },
                {
                    'description': 'Asset pool binding — Snap vertical background from vertical pool',
                    'data': {
                        'kind': 'asset_pool',
                        'asset_id': 'snap_background',
                        'asset_group_id': 'images_vertical',
                    },
                },
                {
                    'description': 'Catalog group binding — carousel where each slide is one hotel',
                    'data': {
                        'kind': 'catalog_group',
                        'format_group_id': 'slide',
                        'catalog_item': True,
                        'per_item_bindings': [
                            {'kind': 'scalar', 'asset_id': 'title', 'catalog_field': 'name'},
                            {
                                'kind': 'scalar',
                                'asset_id': 'price',
                                'catalog_field': 'price.amount',
                            },
                            {
                                'kind': 'asset_pool',
                                'asset_id': 'image',
                                'asset_group_id': 'images_landscape',
                            },
                        ],
                    },
                },
            ],
            title='Catalog Field Binding',
        ),
    ]

    def __getattr__(self, name: str) -> Any:
        """Proxy attribute access to the wrapped type."""
        if name.startswith('_'):
            raise AttributeError(name)
        return getattr(self.root, name)

Usage Documentation

RootModel and Custom Root Types

A Pydantic BaseModel for the root object of the model.

Attributes

root
The root object of the model.
__pydantic_root_model__
Whether the model is a RootModel.
__pydantic_private__
Private fields in the model.
__pydantic_extra__
Extra fields in the model.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.root_model.RootModel[Union[ScalarBinding, AssetPoolBinding, CatalogFieldBinding1]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootScalarBinding | AssetPoolBinding | CatalogFieldBinding1
class CatalogGroupBinding (**data: Any)
Expand source code
class CatalogFieldBinding1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    kind: Literal['catalog_group']
    format_group_id: Annotated[
        str,
        Field(description="The asset_group_id of a repeatable_group in the format's assets array."),
    ]
    catalog_item: Annotated[
        Literal[True],
        Field(
            description="Each repetition of the format's repeatable_group maps to one item from the catalog."
        ),
    ]
    per_item_bindings: Annotated[
        list[ScalarBinding | AssetPoolBinding] | None,
        Field(
            description='Scalar and asset pool bindings that apply within each repetition of the group. Nested catalog_group bindings are not permitted.',
            min_length=1,
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var catalog_item : Literal[True]
var extExtensionObject | None
var format_group_id : str
var kind : Literal['catalog_group']
var model_config
var per_item_bindings : list[ScalarBinding | AssetPoolBinding] | None

Inherited members

class CatalogFieldMapping (**data: Any)
Expand source code
class CatalogFieldMapping(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    feed_field: Annotated[
        str | None,
        Field(
            description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).'
        ),
    ] = None
    catalog_field: Annotated[
        str | None,
        Field(
            description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id."
        ),
    ] = None
    asset_group_id: Annotated[
        str | None,
        Field(
            description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field."
        ),
    ] = None
    value: Annotated[
        Any | None,
        Field(
            description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).'
        ),
    ] = None
    transform: Annotated[
        Transform | None,
        Field(
            description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).'
        ),
    ] = None
    format: Annotated[
        str | None,
        Field(
            description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens."
        ),
    ] = None
    timezone: Annotated[
        str | None,
        Field(
            description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted."
        ),
    ] = None
    by: Annotated[
        float | None,
        Field(
            description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).",
            gt=0.0,
        ),
    ] = None
    separator: Annotated[
        str | None,
        Field(
            description="For transform 'split': the separator character or string to split on. Defaults to ','."
        ),
    ] = ','
    default: Annotated[
        Any | None,
        Field(
            description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var asset_group_id : str | None
var by : float | None
var catalog_field : str | None
var default : typing.Any | None
var extExtensionObject | None
var feed_field : str | None
var format : str | None
var model_config
var separator : str | None
var timezone : str | None
var transformTransform | None
var value : typing.Any | None

Inherited members

class CatalogItemStatus (*args, **kwds)
Expand source code
class CatalogItemStatus(Enum):
    approved = 'approved'
    pending = 'pending'
    rejected = 'rejected'
    warning = 'warning'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var approved
var pending
var rejected
var warning
class CatalogRequirements (**data: Any)
Expand source code
class CatalogRequirements(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    catalog_type: Annotated[
        catalog_type_1.CatalogType,
        Field(description='The catalog type this requirement applies to'),
    ]
    required: Annotated[
        bool | None,
        Field(
            description='Whether this catalog type must be present. When true, creatives using this format must reference a synced catalog of this type.'
        ),
    ] = True
    min_items: Annotated[
        int | None,
        Field(
            description='Minimum number of items the catalog must contain for this format to render properly (e.g., a carousel might require at least 3 products)',
            ge=1,
        ),
    ] = None
    max_items: Annotated[
        int | None,
        Field(
            description='Maximum number of items the format can render. Items beyond this limit are ignored. Useful for fixed-slot layouts (e.g., a 3-product card) or feed-size constraints.',
            ge=1,
        ),
    ] = None
    required_fields: Annotated[
        list[str] | None,
        Field(
            description="Fields that must be present and non-empty on every item in the catalog. Field names are catalog-type-specific (e.g., 'title', 'price', 'image_url' for product catalogs; 'store_id', 'quantity' for inventory feeds).",
            min_length=1,
        ),
    ] = None
    feed_formats: Annotated[
        list[feed_format.FeedFormat] | None,
        Field(
            description='Accepted feed formats for this catalog type. When specified, the synced catalog must use one of these formats. When omitted, any format is accepted.',
            min_length=1,
        ),
    ] = None
    offering_asset_constraints: Annotated[
        list[offering_asset_constraint.OfferingAssetConstraint] | None,
        Field(
            description="Per-item creative asset requirements. Declares what asset groups (headlines, images, videos) each catalog item must provide in its assets array, along with count bounds and per-asset technical constraints. Applicable to 'offering' and all vertical catalog types (hotel, flight, job, etc.) whose items carry typed assets.",
            min_length=1,
        ),
    ] = None
    field_bindings: Annotated[
        list[catalog_field_binding.CatalogFieldBinding] | None,
        Field(
            description='Explicit mappings from format template slots to catalog item fields or typed asset pools. Optional — creative agents can infer mappings without them, but bindings make the relationship self-describing and enable validation. Covers scalar fields (asset_id → catalog_field), asset pools (asset_id → asset_group_id on the catalog item), and repeatable groups that iterate over catalog items.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var catalog_typeCatalogType
var feed_formats : list[FeedFormat] | None
var field_bindings : list[CatalogFieldBinding] | None
var max_items : int | None
var min_items : int | None
var model_config
var offering_asset_constraints : list[OfferingAssetConstraint] | None
var required : bool | None
var required_fields : list[str] | None

Inherited members

class CatalogType (*args, **kwds)
Expand source code
class CatalogType(Enum):
    offering = 'offering'
    product = 'product'
    inventory = 'inventory'
    store = 'store'
    promotion = 'promotion'
    hotel = 'hotel'
    flight = 'flight'
    job = 'job'
    vehicle = 'vehicle'
    real_estate = 'real_estate'
    education = 'education'
    destination = 'destination'
    app = 'app'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var app
var destination
var education
var flight
var hotel
var inventory
var job
var offering
var product
var promotion
var real_estate
var store
var vehicle
class CheckGovernanceRequest (**data: Any)
Expand source code
class CheckGovernanceRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    plan_id: Annotated[str, Field(description='Campaign governance plan identifier.')]
    caller: Annotated[AnyUrl, Field(description='URL of the agent making the request.')]
    purchase_type: Annotated[
        purchase_type_1.PurchaseType | None,
        Field(
            description="The type of financial commitment being checked. Determines which budget allocation (if any) to validate against. Defaults to 'media_buy' when omitted."
        ),
    ] = purchase_type_1.PurchaseType.media_buy
    tool: Annotated[
        str | None,
        Field(
            description="The AdCP tool being checked (e.g., 'create_media_buy', 'acquire_rights', 'activate_signal'). Present on intent checks (orchestrator). The governance agent uses the presence of tool+payload to identify an intent check."
        ),
    ] = None
    payload: Annotated[
        dict[str, Any] | None,
        Field(
            description='The full tool arguments as they would be sent to the seller. Present on intent checks. The governance agent can inspect any field to validate against the plan.'
        ),
    ] = None
    governance_context: Annotated[
        str | None,
        Field(
            description='Governance context token from a prior check_governance response. Pass this on subsequent checks for the same governed action so the governance agent can maintain continuity across the lifecycle. In 3.0 governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context); callers persist and forward the value verbatim.',
            max_length=4096,
            min_length=1,
            pattern='^[\\x20-\\x7E]+$',
        ),
    ] = None
    phase: Annotated[
        governance_phase.GovernancePhase | None,
        Field(
            description="The phase of the governed action's lifecycle. 'purchase': initial commitment (create_media_buy, acquire_rights, activate_signal). 'modification': update to existing commitment. 'delivery': periodic delivery or usage reporting. Defaults to 'purchase' if omitted."
        ),
    ] = governance_phase.GovernancePhase.purchase
    planned_delivery: Annotated[
        planned_delivery_1.PlannedDelivery | None,
        Field(description='What the seller will actually deliver. Present on execution checks.'),
    ] = None
    delivery_metrics: Annotated[
        DeliveryMetrics | None,
        Field(
            description="Actual delivery performance data. MUST be present for 'delivery' phase. The governance agent compares these metrics against the planned delivery to detect drift."
        ),
    ] = None
    modification_summary: Annotated[
        str | None,
        Field(
            description="Human-readable summary of what changed. SHOULD be present for 'modification' phase.",
            max_length=1000,
        ),
    ] = None
    invoice_recipient: Annotated[
        business_entity.BusinessEntity | None,
        Field(
            description='Invoice recipient from the purchase request. MUST be present when the tool payload includes invoice_recipient, so the governance agent can validate billing changes.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var caller : pydantic.networks.AnyUrl
var contextContextObject | None
var delivery_metricsDeliveryMetrics | None
var extExtensionObject | None
var governance_context : str | None
var invoice_recipientBusinessEntity | None
var model_config
var modification_summary : str | None
var payload : dict[str, typing.Any] | None
var phaseGovernancePhase | None
var plan_id : str
var planned_deliveryPlannedDelivery | None
var purchase_typePurchaseType | None
var tool : str | None

Inherited members

class CheckGovernanceResponse (**data: Any)
Expand source code
class CheckGovernanceResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    check_id: Annotated[
        str,
        Field(
            description='Unique identifier for this governance check record. Use in report_plan_outcome to link outcomes to the check that authorized them.'
        ),
    ]
    status: Annotated[
        Status,
        Field(
            description="Governance decision. 'approved': proceed as planned. 'denied': do not proceed. 'conditions': approved if the caller accepts the listed conditions, then re-calls check_governance with the adjusted parameters."
        ),
    ]
    plan_id: Annotated[str, Field(description='Echoed from request.')]
    explanation: Annotated[
        str, Field(description='Human-readable explanation of the governance decision.')
    ]
    findings: Annotated[
        list[Finding] | None,
        Field(
            description="Specific issues found during the governance check. Present when status is 'denied' or 'conditions'. MAY also be present on 'approved' for informational findings (e.g., budget approaching limit)."
        ),
    ] = None
    conditions: Annotated[
        list[Condition] | None,
        Field(
            description="Present when status is 'conditions'. Specific adjustments the caller must make. After applying conditions, the caller MUST re-call check_governance with the adjusted parameters before proceeding."
        ),
    ] = None
    expires_at: Annotated[
        AwareDatetime | None,
        Field(
            description="When this approval expires. Present when status is 'approved' or 'conditions'. The caller must act before this time or re-call check_governance. A lapsed approval is no approval."
        ),
    ] = None
    next_check: Annotated[
        AwareDatetime | None,
        Field(
            description='When the seller should next call check_governance with delivery metrics. Present when the governance agent expects ongoing delivery reporting.'
        ),
    ] = None
    categories_evaluated: Annotated[
        list[str] | None, Field(description='Governance categories evaluated during this check.')
    ] = None
    policies_evaluated: Annotated[
        list[str] | None, Field(description='Registry policy IDs evaluated during this check.')
    ] = None
    governance_context: Annotated[
        str | None,
        Field(
            description="Governance context token for this governed action. The buyer MUST attach this to the protocol envelope when sending the purchase request (media buy, rights acquisition, signal activation) to the seller. The seller MUST persist it and include it on all subsequent check_governance calls for this action's lifecycle.\n\nValue format: in 3.0 governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged so auditors can verify downstream. In 3.1 all sellers MUST verify per the checklist. Non-JWS values from pre-3.0 governance agents are deprecated and will be rejected in 3.1.\n\nSellers that implement verification MUST verify signature, `aud`, `exp`, `jti` replay, and revocation per the profile before treating the request as governance-approved. This is the primary correlation key for audit and reporting across the governance lifecycle — the governance agent decodes its own signed token to look up internal plan state (buyer correlation IDs, policy decision log, etc.).",
            max_length=4096,
            min_length=1,
            pattern='^[\\x20-\\x7E]+$',
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var categories_evaluated : list[str] | None
var check_id : str
var conditions : list[Condition] | None
var contextContextObject | None
var expires_at : pydantic.types.AwareDatetime | None
var explanation : str
var extExtensionObject | None
var findings : list[Finding] | None
var governance_context : str | None
var model_config
var next_check : pydantic.types.AwareDatetime | None
var plan_id : str
var policies_evaluated : list[str] | None
var statusStatus

Inherited members

class ComplyTestControllerRequest (**data: Any)
Expand source code
class ComplyTestControllerRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    scenario: Annotated[
        Scenario,
        Field(
            description="Test scenario to execute. 'list_scenarios' discovers supported scenarios. Others trigger state transitions for the specified domain."
        ),
    ]
    params: Annotated[
        Params | None,
        Field(
            description='Scenario-specific parameters. Required for all scenarios except list_scenarios.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var paramsParams | None
var scenarioScenario

Inherited members

class ConsentBasis (*args, **kwds)
Expand source code
class ConsentBasis(Enum):
    consent = 'consent'
    legitimate_interest = 'legitimate_interest'
    contract = 'contract'
    legal_obligation = 'legal_obligation'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var consent
var contract
var legal_obligation
var legitimate_interest
class ContentIdType (*args, **kwds)
Expand source code
class ContentIdType(Enum):
    sku = 'sku'
    gtin = 'gtin'
    offering_id = 'offering_id'
    job_id = 'job_id'
    hotel_id = 'hotel_id'
    flight_id = 'flight_id'
    vehicle_id = 'vehicle_id'
    listing_id = 'listing_id'
    store_id = 'store_id'
    program_id = 'program_id'
    destination_id = 'destination_id'
    app_id = 'app_id'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var app_id
var destination_id
var flight_id
var gtin
var hotel_id
var job_id
var listing_id
var offering_id
var program_id
var sku
var store_id
var vehicle_id
class ContextMatchRequest (**data: Any)
Expand source code
class ContextMatchRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    field_schema: Annotated[
        AnyUrl | None,
        Field(
            alias='$schema', description='Optional schema URI for validation. Ignored at runtime.'
        ),
    ] = None
    type: Annotated[
        Literal['context_match_request'],
        Field(description='Message type discriminator for deserialization.'),
    ]
    protocol_version: Annotated[
        str | None,
        Field(
            description='TMP protocol version. Allows receivers to handle semantic differences across versions.'
        ),
    ] = '1.0'
    request_id: Annotated[
        str,
        Field(
            description='Unique request identifier. MUST NOT correlate with any identity match request_id.'
        ),
    ]
    property_rid: Annotated[
        UUID,
        Field(
            description='Property catalog UUID (UUID v7). Globally unique, stable identifier assigned by the property catalog. The primary key for TMP matching and property list targeting.'
        ),
    ]
    property_id: Annotated[
        property_id_1.PropertyId | None,
        Field(
            description="Publisher's human-readable property slug (e.g., 'cnn_homepage'). Optional when property_rid is present. Useful for logging and debugging."
        ),
    ] = None
    property_type: Annotated[
        property_type_1.PropertyType, Field(description='Type of the publisher property')
    ]
    placement_id: Annotated[
        str,
        Field(
            description="Placement identifier from the publisher's placement registry in adagents.json. Identifies where on the property this ad opportunity exists. One placement per request."
        ),
    ]
    artifact: Annotated[
        artifact_1.Artifact | None,
        Field(
            description='Full content artifact adjacent to this ad opportunity. Same schema used for content standards evaluation. The publisher sends the artifact when they want the buyer to evaluate the full content. Contractual protections govern buyer use. TEE deployment upgrades contractual trust to cryptographic verification. Publishers MUST NOT include asset access credentials (bearer tokens, service accounts) — the router fans out to multiple buyer agents. For secured assets, use signed URLs with short expiry. Routers MUST strip access fields from artifacts before forwarding.'
        ),
    ] = None
    artifact_refs: Annotated[
        list[ArtifactRef] | None,
        Field(
            description='Public content references adjacent to this ad opportunity. Each artifact identifies content via a public identifier the buyer can resolve independently — no private registry sync required.',
            max_length=20,
            min_length=1,
        ),
    ] = None
    geo: Annotated[
        Geo | None,
        Field(
            description='Coarse geographic location of the viewer. Publisher controls granularity — country is sufficient for regulatory compliance and volume filtering, region or metro helps with campaign targeting and valuation. Coarsened to prevent user identification: no postcode, no coordinates. All fields optional.'
        ),
    ] = None
    context_signals: Annotated[
        ContextSignals | None,
        Field(
            description='Pre-computed classifier outputs for the content environment. Use when the publisher wants to provide classified context without sharing content or public references. Can supplement artifact_refs (e.g., URL + pre-classified topics) or replace them entirely (e.g., ephemeral conversation turns). Raw content MUST NOT be included — only classified outputs. The publisher is the classifier boundary.'
        ),
    ] = None
    package_ids: Annotated[
        list[str] | None,
        Field(
            description='Restrict evaluation to specific packages. When omitted, the provider evaluates all eligible packages for this placement (the common case). MUST NOT vary by user — the same package_ids must be sent for every user on a given placement. User-dependent filtering leaks identity into the context path.',
            max_length=500,
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var artifactArtifact | None
var artifact_refs : list[ArtifactRef] | None
var context_signalsContextSignals | None
var field_schema : pydantic.networks.AnyUrl | None
var geoGeo | None
var model_config
var package_ids : list[str] | None
var placement_id : str
var property_idPropertyId | None
var property_rid : uuid.UUID
var property_typePropertyType
var protocol_version : str | None
var request_id : str
var type : Literal['context_match_request']

Inherited members

class ContextMatchResponse (**data: Any)
Expand source code
class ContextMatchResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    type: Annotated[
        Literal['context_match_response'],
        Field(description='Message type discriminator for deserialization.'),
    ]
    request_id: Annotated[
        str, Field(description='Echoed request identifier from the context match request')
    ]
    offers: Annotated[
        list[offer.Offer],
        Field(
            description='Offers from the buyer, one per activated package. An empty array means no packages matched. For simple activation, each offer has just package_id. For richer responses, offers include brand, price, summary, and creative manifest.'
        ),
    ]
    cache_ttl: Annotated[
        int | None,
        Field(
            description='Optional override for the default 5-minute cache TTL, in seconds. When present, the router MUST use this value instead of its default. Set to 0 to disable caching (e.g., when targeting configuration has just changed).',
            ge=0,
            le=86400,
        ),
    ] = None
    signals: Annotated[
        Signals | None,
        Field(
            description='Response-level targeting signals for ad server pass-through. In the GAM case, these carry the key-value pairs that trigger line items. Not per-offer — applies to the response as a whole.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var cache_ttl : int | None
var model_config
var offers : list[Offer]
var request_id : str
var signalsSignals | None
var type : Literal['context_match_response']

Inherited members

class ContextObject (**data: Any)
Expand source code
class ContextObject(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config

Inherited members

class CpaPricingOption (**data: Any)
Expand source code
class CpaPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[
        Literal['cpa'], Field(description='Cost per acquisition (conversion event)')
    ]
    event_type: Annotated[
        event_type_1.EventType,
        Field(
            description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)'
        ),
    ]
    custom_event_name: Annotated[
        str | None,
        Field(
            description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise."
        ),
    ] = None
    event_source_id: Annotated[
        str | None,
        Field(
            description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.'
        ),
    ] = None
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0)
    ]
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var custom_event_name : str | None
var eligible_adjustments : list[PriceAdjustmentKind] | None
var event_source_id : str | None
var event_typeEventType
var fixed_price : float
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var pricing_model : Literal['cpa']
var pricing_option_id : str

Inherited members

class CpcPricingOption (**data: Any)
Expand source code
class CpcPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['cpc']
var pricing_option_id : str

Inherited members

class CpcvPricingOption (**data: Any)
Expand source code
class CpcvPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    pricing_option_id: Annotated[
        str, Field(description="Unique identifier for this pricing option within the product")
    ]
    pricing_model: Annotated[
        Literal["cpcv"], Field(description="Cost per completed view (100% completion)")
    ]
    currency: Annotated[
        str,
        Field(
            description="ISO 4217 currency code",
            examples=["USD", "EUR", "GBP", "JPY"],
            pattern="^[A-Z]{3}$",
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description="Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.",
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description="Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.",
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description="Optional pricing guidance for auction-based bidding"),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description="Minimum spend requirement per package using this pricing option, in the specified currency",
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description="Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present."
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description="Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present."
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['cpcv']
var pricing_option_id : str

Inherited members

class CpmPricingOption (**data: Any)
Expand source code
class CpmPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['cpm']
var pricing_option_id : str
class CpmAuctionPricingOption (**data: Any)
Expand source code
class CpmPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['cpm']
var pricing_option_id : str
class CpmFixedRatePricingOption (**data: Any)
Expand source code
class CpmPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['cpm']
var pricing_option_id : str

Inherited members

class CppPricingOption (**data: Any)
Expand source code
class CppPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    parameters: Annotated[
        Parameters, Field(description='CPP-specific parameters for demographic targeting')
    ]
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var min_spend_per_package : float | None
var model_config
var parametersParameters
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['cpp']
var pricing_option_id : str

Inherited members

class CpvPricingOption (**data: Any)
Expand source code
class CpvPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    parameters: Annotated[
        Parameters, Field(description='CPV-specific parameters defining the view threshold')
    ]
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var parametersParameters
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['cpv']
var pricing_option_id : str

Inherited members

class CreateContentStandardsSuccessResponse (**data: Any)
Expand source code
class CreateContentStandardsResponse1(AdCPBaseModel):
    standards_id: Annotated[
        str, Field(description='Unique identifier for the created standards configuration')
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var standards_id : str

Inherited members

class CreateContentStandardsErrorResponse (**data: Any)
Expand source code
class CreateContentStandardsResponse2(AdCPBaseModel):
    errors: list[error.Error]
    conflicting_standards_id: Annotated[
        str | None,
        Field(
            description='If the error is a scope conflict, the ID of the existing standards that conflict'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var conflicting_standards_id : str | None
var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class CreateMediaBuyRequest (**data: Any)
Expand source code
class CreateMediaBuyRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for this request. If a request with the same idempotency_key and account has already been processed, the seller returns the existing media buy rather than creating a duplicate. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    plan_id: Annotated[
        str | None,
        Field(
            description='Campaign governance plan identifier. Required when the account has governance_agents. The seller includes this in the committed check_governance request so the governance agent can validate against the correct plan.'
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference,
        Field(
            description='Account to bill for this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.'
        ),
    ]
    proposal_id: Annotated[
        str | None,
        Field(
            description="ID of a proposal from get_products to execute. When provided with total_budget, the publisher converts the proposal's allocation percentages into packages automatically. Alternative to providing packages array."
        ),
    ] = None
    total_budget: Annotated[
        TotalBudget | None,
        Field(
            description="Total budget for the media buy when executing a proposal. The publisher applies the proposal's allocation percentages to this amount to derive package budgets."
        ),
    ] = None
    packages: Annotated[
        list[package_request.PackageRequest] | None,
        Field(
            description="Array of package configurations. Required when not using proposal_id. When executing a proposal, this can be omitted and packages will be derived from the proposal's allocations.",
            min_length=1,
        ),
    ] = None
    brand: Annotated[
        brand_ref.BrandReference,
        Field(
            description='Brand reference for this media buy. Resolved to full brand identity at execution time from brand.json or the registry.'
        ),
    ]
    advertiser_industry: Annotated[
        advertiser_industry_1.AdvertiserIndustry | None,
        Field(
            description="Industry classification for this specific campaign. A brand may operate across multiple industries (brand.json industries field), but each media buy targets one. For example, a consumer health company running a wellness campaign sends 'healthcare.wellness', not 'cpg'. Sellers map this to platform-native codes (e.g., Spotify ADV categories, LinkedIn industry IDs). When omitted, sellers may infer from the brand manifest's industries field."
        ),
    ] = None
    invoice_recipient: Annotated[
        business_entity.BusinessEntity | None,
        Field(
            description="Override the account's default billing entity for this specific buy. When provided, the seller invoices this entity instead. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request."
        ),
    ] = None
    io_acceptance: Annotated[
        IoAcceptance | None,
        Field(
            description="Acceptance of an insertion order from a committed proposal. Required when the proposal's insertion_order has requires_signature: true. References the io_id from the proposal's insertion_order."
        ),
    ] = None
    po_number: Annotated[str | None, Field(description='Purchase order number for tracking')] = None
    agency_estimate_number: Annotated[
        str | None,
        Field(
            description="Agency estimate or authorization number. Primary financial reference for broadcast buys — links the order to the agency's media plan and billing system. Travels with the order and Ad-IDs through the transaction lifecycle.",
            max_length=100,
        ),
    ] = None
    start_time: start_timing.StartTiming
    end_time: Annotated[
        AwareDatetime, Field(description='Campaign end date/time in ISO 8601 format')
    ]
    push_notification_config: Annotated[
        push_notification_config_1.PushNotificationConfig | None,
        Field(
            description='Optional webhook configuration for async task status notifications. Publisher will send webhooks when status changes (working, input-required, completed, failed). The client generates an operation_id and embeds it in the URL before sending — the publisher echoes it back in webhook payloads for correlation.'
        ),
    ] = None
    reporting_webhook: Annotated[
        reporting_webhook_1.ReportingWebhook | None,
        Field(description='Optional webhook configuration for automated reporting delivery'),
    ] = None
    artifact_webhook: Annotated[
        ArtifactWebhook | None,
        Field(
            description='Optional webhook configuration for content artifact delivery. Used by governance agents to validate content adjacency. Seller pushes artifacts to this endpoint; orchestrator forwards to governance agent for validation.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var advertiser_industryAdvertiserIndustry | None
var agency_estimate_number : str | None
var artifact_webhookArtifactWebhook | None
var brandBrandReference
var contextContextObject | None
var end_time : pydantic.types.AwareDatetime
var extExtensionObject | None
var idempotency_key : str
var invoice_recipientBusinessEntity | None
var io_acceptanceIoAcceptance | None
var model_config
var packages : list[PackageRequest] | None
var plan_id : str | None
var po_number : str | None
var proposal_id : str | None
var push_notification_configPushNotificationConfig | None
var reporting_webhookReportingWebhook | None
var start_timeStartTiming
var total_budgetTotalBudget | None

Inherited members

class CreateMediaBuySuccessResponse (**data: Any)
Expand source code
class CreateMediaBuyResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    media_buy_id: Annotated[
        str, Field(description="Seller's unique identifier for the created media buy")
    ]
    account: Annotated[
        account_1.Account | None,
        Field(
            description="Account billed for this media buy. Includes advertiser, billing proxy (if any), and rate card applied."
        ),
    ] = None
    invoice_recipient: Annotated[
        business_entity.BusinessEntity | None,
        Field(
            description="Per-buy invoice recipient, echoed from the request when provided. Confirms the seller accepted the billing override. Bank details are omitted (write-only)."
        ),
    ] = None
    status: Annotated[
        media_buy_status.MediaBuyStatus | None,
        Field(
            description="Initial media buy status. Either 'pending_creatives' (awaiting creative assets), 'pending_start' (ready to serve, waiting for flight date), or 'active' (immediate activation)."
        ),
    ] = None
    confirmed_at: Annotated[
        AwareDatetime | None,
        Field(
            description="ISO 8601 timestamp when this media buy was confirmed by the seller. A successful create_media_buy response constitutes order confirmation."
        ),
    ] = None
    creative_deadline: Annotated[
        AwareDatetime | None, Field(description="ISO 8601 timestamp for creative upload deadline")
    ] = None
    revision: Annotated[
        int | None,
        Field(
            description="Initial revision number for this media buy. Use in subsequent update_media_buy requests for optimistic concurrency.",
            ge=1,
        ),
    ] = None
    valid_actions: Annotated[
        list[ValidAction] | None,
        Field(
            description="Actions the buyer can perform on this media buy after creation. Saves a round-trip to get_media_buys."
        ),
    ] = None
    packages: Annotated[
        list[package.Package],
        Field(description="Array of created packages with complete state information"),
    ]
    planned_delivery: Annotated[
        planned_delivery_1.PlannedDelivery | None,
        Field(
            description="The seller's interpreted delivery parameters. Describes what the seller will actually run -- geo, channels, flight dates, frequency caps, and budget. Present when the account has governance_agents or when the seller chooses to provide delivery transparency."
        ),
    ] = None
    sandbox: Annotated[
        bool | None,
        Field(description="When true, this response contains simulated data from sandbox mode."),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccount | None
var confirmed_at : pydantic.types.AwareDatetime | None
var contextContextObject | None
var creative_deadline : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var invoice_recipientBusinessEntity | None
var media_buy_id : str
var model_config
var packages : list[Package]
var planned_deliveryPlannedDelivery | None
var revision : int | None
var sandbox : bool | None
var statusMediaBuyStatus | None
var valid_actions : list[ValidAction] | None

Inherited members

class CreateMediaBuyErrorResponse (**data: Any)
Expand source code
class CreateMediaBuyResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    errors: Annotated[
        list[error.Error],
        Field(description="Array of errors explaining why the operation failed", min_length=1),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class Creative (**data: Any)
Expand source code
class Creative(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    creative_id: Annotated[str, Field(description='Creative identifier')]
    media_buy_id: Annotated[
        str | None,
        Field(
            description="Publisher's media buy identifier for this creative. Present when the request spanned multiple media buys, so the buyer can correlate each creative to its media buy."
        ),
    ] = None
    format_id: Annotated[
        format_id_1.FormatId | None, Field(description='Format of this creative')
    ] = None
    totals: Annotated[
        delivery_metrics.DeliveryMetrics | None,
        Field(description='Aggregate delivery metrics across all variants of this creative'),
    ] = None
    variant_count: Annotated[
        int | None,
        Field(
            description='Total number of variants for this creative. When max_variants was specified in the request, this may exceed the number of items in the variants array.',
            ge=0,
        ),
    ] = None
    variants: Annotated[
        list[creative_variant.CreativeVariant],
        Field(
            description='Variant-level delivery breakdown. Each variant includes the rendered manifest and delivery metrics. For standard creatives, contains a single variant. For asset group optimization, one per combination. For generative creative, one per generated execution. Empty when a creative has no variants yet.'
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var creative_id : str
var format_idFormatId | None
var media_buy_id : str | None
var model_config
var totalsDeliveryMetrics | None
var variant_count : int | None
var variants : list[CreativeVariant]
class SyncCreativeResult (**data: Any)
Expand source code
class Creative(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    creative_id: Annotated[str, Field(description="Creative ID from the request")]
    account: Annotated[
        account_1.Account | None, Field(description="Account that owns this creative")
    ] = None
    action: Annotated[
        creative_action.CreativeAction,
        Field(
            description="Action taken for this creative during this sync operation (lifecycle operation, not approval state)."
        ),
    ]
    status: Annotated[
        creative_status.CreativeStatus | None,
        Field(
            description="Advisory review-lifecycle state of the creative after this sync — a UI hint and polling-scheduling signal, NOT a spend-authorization gate. Orthogonal to action — action says what the sync did (created, updated, ...); status says where the creative sits in review. Values come from CreativeStatus only (processing, pending_review, approved, rejected, archived) — never from CreativeAction. Sellers with async review return processing or pending_review; sellers with synchronous review MAY return a terminal value (approved, rejected). Buyers MUST NOT gate downstream spend or package activation on status: approved from this response — a compromised or buggy seller could declare approved while bypassing content-policy review. Reconcile via list_creatives or a signed review webhook before committing spend. Authoritative state is always via list_creatives. MUST be omitted when action is failed or deleted (the creative has no meaningful review state — failure details belong in the errors array; deleted creatives are gone from the library). Omit entirely when the seller has no review lifecycle at all."
        ),
    ] = None
    platform_id: Annotated[
        str | None, Field(description="Platform-specific ID assigned to the creative")
    ] = None
    changes: Annotated[
        list[str] | None,
        Field(description="Field names that were modified (only present when action='updated')"),
    ] = None
    errors: Annotated[
        list[error.Error] | None,
        Field(description="Validation or processing errors (only present when action='failed')"),
    ] = None
    warnings: Annotated[
        list[str] | None, Field(description="Non-fatal warnings about this creative")
    ] = None
    preview_url: Annotated[
        AnyUrl | None,
        Field(
            description="Preview URL for generative creatives (only present for generative formats)"
        ),
    ] = None
    expires_at: Annotated[
        AwareDatetime | None,
        Field(
            description="ISO 8601 timestamp when preview link expires (only present when preview_url exists)"
        ),
    ] = None
    assigned_to: Annotated[
        list[str] | None,
        Field(
            description="Package IDs this creative was successfully assigned to (only present when assignments were requested)"
        ),
    ] = None
    assignment_errors: Annotated[
        dict[Annotated[str, StringConstraints(pattern=r"^[a-zA-Z0-9_-]+$")], str] | None,
        Field(
            description="Assignment errors by package ID (only present when assignment failures occurred)"
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccount | None
var actionCreativeAction
var assigned_to : list[str] | None
var assignment_errors : dict[str, str] | None
var changes : list[str] | None
var creative_id : str
var errors : list[Error] | None
var expires_at : pydantic.types.AwareDatetime | None
var model_config
var platform_id : str | None
var preview_url : pydantic.networks.AnyUrl | None
var statusCreativeStatus | None
var warnings : list[str] | None

Inherited members

class CreativeApproval (**data: Any)
Expand source code
class CreativeApproval(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    creative_id: Annotated[str, Field(description='Creative identifier')]
    approval_status: creative_approval_status.CreativeApprovalStatus
    rejection_reason: Annotated[
        str | None,
        Field(
            description="Human-readable explanation of why the creative was rejected. Present only when approval_status is 'rejected'."
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var approval_statusCreativeApprovalStatus
var creative_id : str
var model_config
var rejection_reason : str | None

Inherited members

class CreativeApprovalStatus (*args, **kwds)
Expand source code
class CreativeApprovalStatus(Enum):
    pending_review = 'pending_review'
    approved = 'approved'
    rejected = 'rejected'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var approved
var pending_review
var rejected
class CreativeFilters (**data: Any)
Expand source code
class CreativeFilters(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    accounts: Annotated[
        list[account_ref.AccountReference] | None,
        Field(
            description='Filter creatives by owning accounts. Useful for agencies managing multiple client accounts.',
            min_length=1,
        ),
    ] = None
    statuses: Annotated[
        list[creative_status.CreativeStatus] | None,
        Field(description='Filter by creative approval statuses', min_length=1),
    ] = None
    tags: Annotated[
        list[str] | None,
        Field(description='Filter by creative tags (all tags must match)', min_length=1),
    ] = None
    tags_any: Annotated[
        list[str] | None,
        Field(description='Filter by creative tags (any tag must match)', min_length=1),
    ] = None
    name_contains: Annotated[
        str | None,
        Field(description='Filter by creative names containing this text (case-insensitive)'),
    ] = None
    creative_ids: Annotated[
        list[str] | None,
        Field(description='Filter by specific creative IDs', max_length=100, min_length=1),
    ] = None
    created_after: Annotated[
        AwareDatetime | None,
        Field(description='Filter creatives created after this date (ISO 8601)'),
    ] = None
    created_before: Annotated[
        AwareDatetime | None,
        Field(description='Filter creatives created before this date (ISO 8601)'),
    ] = None
    updated_after: Annotated[
        AwareDatetime | None,
        Field(description='Filter creatives last updated after this date (ISO 8601)'),
    ] = None
    updated_before: Annotated[
        AwareDatetime | None,
        Field(description='Filter creatives last updated before this date (ISO 8601)'),
    ] = None
    assigned_to_packages: Annotated[
        list[str] | None,
        Field(
            description='Filter creatives assigned to any of these packages. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.',
            min_length=1,
        ),
    ] = None
    media_buy_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter creatives assigned to any of these media buys. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.',
            min_length=1,
        ),
    ] = None
    unassigned: Annotated[
        bool | None,
        Field(
            description='Filter for unassigned creatives when true, assigned creatives when false. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.'
        ),
    ] = None
    has_served: Annotated[
        bool | None,
        Field(
            description='When true, return only creatives that have served at least one impression. When false, return only creatives that have never served.'
        ),
    ] = None
    concept_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter by creative concept IDs. Concepts group related creatives across sizes and formats (e.g., Flashtalking concepts, Celtra campaign folders, CM360 creative groups).',
            min_length=1,
        ),
    ] = None
    format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description='Filter by structured format IDs. Returns creatives that match any of these formats.',
            min_length=1,
        ),
    ] = None
    has_variables: Annotated[
        bool | None,
        Field(
            description='When true, return only creatives with dynamic variables (DCO). When false, return only static creatives.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accounts : list[AccountReference] | None
var assigned_to_packages : list[str] | None
var concept_ids : list[str] | None
var created_after : pydantic.types.AwareDatetime | None
var created_before : pydantic.types.AwareDatetime | None
var creative_ids : list[str] | None
var format_ids : list[FormatId] | None
var has_served : bool | None
var has_variables : bool | None
var media_buy_ids : list[str] | None
var model_config
var name_contains : str | None
var statuses : list[CreativeStatus] | None
var tags : list[str] | None
var tags_any : list[str] | None
var unassigned : bool | None
var updated_after : pydantic.types.AwareDatetime | None
var updated_before : pydantic.types.AwareDatetime | None

Inherited members

class CreativeManifest (**data: Any)
Expand source code
class CreativeManifest(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    format_id: Annotated[
        format_id_1.FormatId,
        Field(
            description="Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height/unit in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250, unit: 'px'})."
        ),
    ]
    assets: Annotated[
        dict[
            Annotated[str, StringConstraints(pattern=r"^[a-z0-9_]+$")],
            image_asset.ImageAsset
            | video_asset.VideoAsset
            | audio_asset.AudioAsset
            | vast_asset.VastAsset
            | text_asset.TextAsset
            | url_asset.UrlAsset
            | html_asset.HtmlAsset
            | javascript_asset.JavascriptAsset
            | webhook_asset.WebhookAsset
            | css_asset.CssAsset
            | daast_asset.DaastAsset
            | markdown_asset.MarkdownAsset
            | brief_asset.BriefAsset
            | catalog_asset.CatalogAsset,
        ],
        Field(
            description="Map of asset IDs to actual asset content. Each key MUST match an asset_id from the format's assets array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). The asset_id is the technical identifier used to match assets to format requirements.\n\nIMPORTANT: Full validation requires format context. The format defines what type each asset_id should be. Standalone schema validation only checks structural conformance — each asset must match at least one valid asset type schema."
        ),
    ]
    rights: Annotated[
        list[rights_constraint.RightsConstraint] | None,
        Field(
            description="Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms."
        ),
    ] = None
    industry_identifiers: Annotated[
        list[industry_identifier.IndustryIdentifier] | None,
        Field(
            description="Industry-standard identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct Ad-IDs (e.g., the :15 and :30 cuts)."
        ),
    ] = None
    provenance: Annotated[
        provenance_1.Provenance | None,
        Field(
            description="Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging)."
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var assets : dict[str, ImageAsset | VideoAsset | AudioAsset | VastAsset | TextAsset | UrlAsset | HtmlAsset | JavascriptAsset | WebhookAsset | CssAsset | DaastAsset | MarkdownAsset | BriefAsset | CatalogAsset]
var extExtensionObject | None
var format_idFormatId
var industry_identifiers : list[IndustryIdentifier] | None
var model_config
var provenanceProvenance | None
var rights : list[RightsConstraint] | None

Inherited members

class CreativeStatus (*args, **kwds)
Expand source code
class CreativeStatus(Enum):
    processing = 'processing'
    pending_review = 'pending_review'
    approved = 'approved'
    rejected = 'rejected'
    archived = 'archived'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var approved
var archived
var pending_review
var processing
var rejected
class CreativeVariant (**data: Any)
Expand source code
class CreativeVariant(DeliveryMetrics):
    variant_id: Annotated[str, Field(description='Platform-assigned identifier for this variant')]
    manifest: Annotated[
        creative_manifest.CreativeManifest | None,
        Field(
            description='The rendered creative manifest for this variant — the actual output that was served, not the input assets. Contains format_id and the resolved assets (specific headline, image, video, etc. the platform selected or generated). For Tier 2, shows which asset combination was picked. For Tier 3, contains the generated assets which may differ entirely from the input brand identity. Pass to preview_creative to re-render.'
        ),
    ] = None
    generation_context: Annotated[
        GenerationContext | None,
        Field(
            description='Input signals that triggered generation of this variant (Tier 3). Describes why the platform created this specific variant. Platforms should provide summarized or anonymized signals rather than raw user input. For web contexts, may include page topic or URL. For conversational contexts, an anonymized content signal. For search, query category or intent. When the content context is managed through AdCP content standards, reference the artifact directly via the artifact field.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var generation_contextGenerationContext | None
var manifestCreativeManifest | None
var model_config
var variant_id : str

Inherited members

class CursorStore (*args, **kwargs)
Expand source code
@runtime_checkable
class CursorStore(Protocol):
    """Protocol for persisting the feed cursor."""

    async def load(self) -> str | None:
        """Load the saved cursor, or None if no cursor exists."""
        ...

    async def save(self, cursor: str) -> None:
        """Save the current cursor."""
        ...

Protocol for persisting the feed cursor.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

async def load(self) ‑> str | None
Expand source code
async def load(self) -> str | None:
    """Load the saved cursor, or None if no cursor exists."""
    ...

Load the saved cursor, or None if no cursor exists.

async def save(self, cursor: str) ‑> None
Expand source code
async def save(self, cursor: str) -> None:
    """Save the current cursor."""
    ...

Save the current cursor.

class UrlDaastAsset (**data: Any)
Expand source code
class DaastAsset1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    delivery_type: Annotated[
        Literal['url'],
        Field(description='Discriminator indicating DAAST is delivered via URL endpoint'),
    ]
    url: Annotated[AnyUrl, Field(description='URL endpoint that returns DAAST XML')]
    daast_version: Annotated[
        daast_version_1.DaastVersion | None, Field(description='DAAST specification version')
    ] = None
    duration_ms: Annotated[
        int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0)
    ] = None
    tracking_events: Annotated[
        list[daast_tracking_event.DaastTrackingEvent] | None,
        Field(description='Tracking events supported by this DAAST tag'),
    ] = None
    companion_ads: Annotated[
        bool | None, Field(description='Whether companion display ads are included')
    ] = None
    transcript_url: Annotated[
        AnyUrl | None, Field(description='URL to text transcript of the audio content')
    ] = None
    provenance: Annotated[
        provenance_1.Provenance | None,
        Field(
            description='Provenance metadata for this asset, overrides manifest-level provenance'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var companion_ads : bool | None
var daast_versionDaastVersion | None
var delivery_type : Literal['url']
var duration_ms : int | None
var model_config
var provenanceProvenance | None
var tracking_events : list[DaastTrackingEvent] | None
var transcript_url : pydantic.networks.AnyUrl | None
var url : pydantic.networks.AnyUrl

Inherited members

class InlineDaastAsset (**data: Any)
Expand source code
class DaastAsset2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    delivery_type: Annotated[
        Literal['inline'],
        Field(description='Discriminator indicating DAAST is delivered as inline XML content'),
    ]
    content: Annotated[str, Field(description='Inline DAAST XML content')]
    daast_version: Annotated[
        daast_version_1.DaastVersion | None, Field(description='DAAST specification version')
    ] = None
    duration_ms: Annotated[
        int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0)
    ] = None
    tracking_events: Annotated[
        list[daast_tracking_event.DaastTrackingEvent] | None,
        Field(description='Tracking events supported by this DAAST tag'),
    ] = None
    companion_ads: Annotated[
        bool | None, Field(description='Whether companion display ads are included')
    ] = None
    transcript_url: Annotated[
        AnyUrl | None, Field(description='URL to text transcript of the audio content')
    ] = None
    provenance: Annotated[
        provenance_1.Provenance | None,
        Field(
            description='Provenance metadata for this asset, overrides manifest-level provenance'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var companion_ads : bool | None
var content : str
var daast_versionDaastVersion | None
var delivery_type : Literal['inline']
var duration_ms : int | None
var model_config
var provenanceProvenance | None
var tracking_events : list[DaastTrackingEvent] | None
var transcript_url : pydantic.networks.AnyUrl | None

Inherited members

class DateRange (**data: Any)
Expand source code
class DateRange(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    start: Annotated[date_aliased, Field(description='Start date (inclusive), ISO 8601')]
    end: Annotated[date_aliased, Field(description='End date (inclusive), ISO 8601')]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var end : datetime.date
var model_config
var start : datetime.date

Inherited members

class DatetimeRange (**data: Any)
Expand source code
class DatetimeRange(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    start: Annotated[AwareDatetime, Field(description='Start timestamp (inclusive), ISO 8601')]
    end: Annotated[AwareDatetime, Field(description='End timestamp (inclusive), ISO 8601')]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var end : pydantic.types.AwareDatetime
var model_config
var start : pydantic.types.AwareDatetime

Inherited members

class DeliveryStatus (*args, **kwds)
Expand source code
class DeliveryStatus(Enum):
    delivering = 'delivering'
    not_delivering = 'not_delivering'
    completed = 'completed'
    budget_exhausted = 'budget_exhausted'
    flight_ended = 'flight_ended'
    goal_met = 'goal_met'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var budget_exhausted
var completed
var delivering
var flight_ended
var goal_met
var not_delivering
class PlatformDeployment (**data: Any)
Expand source code
class Deployment1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    type: Annotated[
        Literal['platform'],
        Field(description='Discriminator indicating this is a platform-based deployment'),
    ]
    platform: Annotated[str, Field(description='Platform identifier for DSPs')]
    account: Annotated[str | None, Field(description='Account identifier if applicable')] = None
    is_live: Annotated[
        bool, Field(description='Whether signal is currently active on this deployment')
    ]
    activation_key: Annotated[
        activation_key_1.ActivationKey | None,
        Field(
            description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.'
        ),
    ] = None
    estimated_activation_duration_minutes: Annotated[
        float | None,
        Field(
            description='Estimated time to activate if not live, or to complete activation if in progress',
            ge=0.0,
        ),
    ] = None
    deployed_at: Annotated[
        AwareDatetime | None,
        Field(description='Timestamp when activation completed (if is_live=true)'),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var account : str | None
var activation_keyActivationKey | None
var deployed_at : pydantic.types.AwareDatetime | None
var estimated_activation_duration_minutes : float | None
var is_live : bool
var model_config
var platform : str
var type : Literal['platform']

Inherited members

class AgentDeployment (**data: Any)
Expand source code
class Deployment2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    type: Annotated[
        Literal['agent'],
        Field(description='Discriminator indicating this is an agent URL-based deployment'),
    ]
    agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')]
    account: Annotated[str | None, Field(description='Account identifier if applicable')] = None
    is_live: Annotated[
        bool, Field(description='Whether signal is currently active on this deployment')
    ]
    activation_key: Annotated[
        activation_key_1.ActivationKey | None,
        Field(
            description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.'
        ),
    ] = None
    estimated_activation_duration_minutes: Annotated[
        float | None,
        Field(
            description='Estimated time to activate if not live, or to complete activation if in progress',
            ge=0.0,
        ),
    ] = None
    deployed_at: Annotated[
        AwareDatetime | None,
        Field(description='Timestamp when activation completed (if is_live=true)'),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var account : str | None
var activation_keyActivationKey | None
var agent_url : pydantic.networks.AnyUrl
var deployed_at : pydantic.types.AwareDatetime | None
var estimated_activation_duration_minutes : float | None
var is_live : bool
var model_config
var type : Literal['agent']

Inherited members

class PlatformDestination (**data: Any)
Expand source code
class Destination1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    type: Annotated[
        Literal['platform'],
        Field(description='Discriminator indicating this is a platform-based deployment'),
    ]
    platform: Annotated[
        str,
        Field(description="Platform identifier for DSPs (e.g., 'the-trade-desk', 'amazon-dsp')"),
    ]
    account: Annotated[
        str | None, Field(description='Optional account identifier on the platform')
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var account : str | None
var model_config
var platform : str
var type : Literal['platform']

Inherited members

class AgentDestination (**data: Any)
Expand source code
class Destination2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    type: Annotated[
        Literal['agent'],
        Field(description='Discriminator indicating this is an agent URL-based deployment'),
    ]
    agent_url: Annotated[
        AnyUrl, Field(description='URL identifying the deployment agent (for sales agents, etc.)')
    ]
    account: Annotated[
        str | None, Field(description='Optional account identifier on the agent')
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var account : str | None
var agent_url : pydantic.networks.AnyUrl
var model_config
var type : Literal['agent']

Inherited members

class DevicePlatform (*args, **kwds)
Expand source code
class DevicePlatform(Enum):
    ios = 'ios'
    android = 'android'
    windows = 'windows'
    macos = 'macos'
    linux = 'linux'
    chromeos = 'chromeos'
    tvos = 'tvos'
    tizen = 'tizen'
    webos = 'webos'
    fire_os = 'fire_os'
    roku_os = 'roku_os'
    unknown = 'unknown'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var android
var chromeos
var fire_os
var ios
var linux
var macos
var roku_os
var tizen
var tvos
var unknown
var webos
var windows
class DeviceType (*args, **kwds)
Expand source code
class DeviceType(Enum):
    desktop = 'desktop'
    mobile = 'mobile'
    tablet = 'tablet'
    ctv = 'ctv'
    dooh = 'dooh'
    unknown = 'unknown'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var ctv
var desktop
var dooh
var mobile
var tablet
var unknown
class DomainLookupResult (**data: Any)
Expand source code
class DomainLookupResult(RegistryBaseModel):
    domain: Annotated[str, Field(examples=["examplepub.com"])]
    authorized_agents: list[DomainAuthorizedAgent]
    sales_agents_claiming: list[SalesAgentClaim]

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var authorized_agents : list[DomainAuthorizedAgent]
var domain : str
var model_config
var sales_agents_claiming : list[SalesAgentClaim]
class Duration (**data: Any)
Expand source code
class Duration(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    interval: Annotated[
        int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1)
    ]
    unit: Annotated[
        Unit,
        Field(
            description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight."
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var interval : int
var model_config
var unitUnit

Inherited members

class Error (**data: Any)
Expand source code
class Error(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    code: Annotated[
        str,
        Field(
            description='Error code for programmatic handling. Standard codes are defined in error-code.json and enable autonomous agent recovery. Sellers MAY use codes not in the standard vocabulary for platform-specific errors; agents MUST handle unknown codes gracefully by falling back to the recovery classification.',
            max_length=64,
            min_length=1,
        ),
    ]
    message: Annotated[str, Field(description='Human-readable error message')]
    field: Annotated[
        str | None,
        Field(description="Field path associated with the error (e.g., 'packages[0].targeting')"),
    ] = None
    suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None
    retry_after: Annotated[
        float | None,
        Field(
            description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.',
            ge=1.0,
            le=3600.0,
        ),
    ] = None
    details: Annotated[
        dict[str, Any] | None, Field(description='Additional task-specific error details')
    ] = None
    recovery: Annotated[
        Recovery | None,
        Field(
            description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found).'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var code : str
var details : dict[str, typing.Any] | None
var field : str | None
var message : str
var model_config
var recoveryRecovery | None
var retry_after : float | None
var suggestion : str | None

Inherited members

class ErrorCode (*args, **kwds)
Expand source code
class ErrorCode(Enum):
    INVALID_REQUEST = "INVALID_REQUEST"
    AUTH_REQUIRED = "AUTH_REQUIRED"
    RATE_LIMITED = "RATE_LIMITED"
    SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE"
    POLICY_VIOLATION = "POLICY_VIOLATION"
    PRODUCT_NOT_FOUND = "PRODUCT_NOT_FOUND"
    PRODUCT_UNAVAILABLE = "PRODUCT_UNAVAILABLE"
    PROPOSAL_EXPIRED = "PROPOSAL_EXPIRED"
    BUDGET_TOO_LOW = "BUDGET_TOO_LOW"
    CREATIVE_REJECTED = "CREATIVE_REJECTED"
    UNSUPPORTED_FEATURE = "UNSUPPORTED_FEATURE"
    AUDIENCE_TOO_SMALL = "AUDIENCE_TOO_SMALL"
    ACCOUNT_NOT_FOUND = "ACCOUNT_NOT_FOUND"
    ACCOUNT_SETUP_REQUIRED = "ACCOUNT_SETUP_REQUIRED"
    ACCOUNT_AMBIGUOUS = "ACCOUNT_AMBIGUOUS"
    ACCOUNT_PAYMENT_REQUIRED = "ACCOUNT_PAYMENT_REQUIRED"
    ACCOUNT_SUSPENDED = "ACCOUNT_SUSPENDED"
    COMPLIANCE_UNSATISFIED = "COMPLIANCE_UNSATISFIED"
    GOVERNANCE_DENIED = "GOVERNANCE_DENIED"
    BUDGET_EXHAUSTED = "BUDGET_EXHAUSTED"
    BUDGET_EXCEEDED = "BUDGET_EXCEEDED"
    CONFLICT = "CONFLICT"
    IDEMPOTENCY_CONFLICT = "IDEMPOTENCY_CONFLICT"
    IDEMPOTENCY_EXPIRED = "IDEMPOTENCY_EXPIRED"
    CREATIVE_DEADLINE_EXCEEDED = "CREATIVE_DEADLINE_EXCEEDED"
    INVALID_STATE = "INVALID_STATE"
    MEDIA_BUY_NOT_FOUND = "MEDIA_BUY_NOT_FOUND"
    NOT_CANCELLABLE = "NOT_CANCELLABLE"
    PACKAGE_NOT_FOUND = "PACKAGE_NOT_FOUND"
    CREATIVE_NOT_FOUND = "CREATIVE_NOT_FOUND"
    SIGNAL_NOT_FOUND = "SIGNAL_NOT_FOUND"
    SESSION_NOT_FOUND = "SESSION_NOT_FOUND"
    SESSION_TERMINATED = "SESSION_TERMINATED"
    VALIDATION_ERROR = "VALIDATION_ERROR"
    PRODUCT_EXPIRED = "PRODUCT_EXPIRED"
    PROPOSAL_NOT_COMMITTED = "PROPOSAL_NOT_COMMITTED"
    IO_REQUIRED = "IO_REQUIRED"
    TERMS_REJECTED = "TERMS_REJECTED"
    VERSION_UNSUPPORTED = "VERSION_UNSUPPORTED"

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var ACCOUNT_AMBIGUOUS
var ACCOUNT_NOT_FOUND
var ACCOUNT_PAYMENT_REQUIRED
var ACCOUNT_SETUP_REQUIRED
var ACCOUNT_SUSPENDED
var AUDIENCE_TOO_SMALL
var AUTH_REQUIRED
var BUDGET_EXCEEDED
var BUDGET_EXHAUSTED
var BUDGET_TOO_LOW
var COMPLIANCE_UNSATISFIED
var CONFLICT
var CREATIVE_DEADLINE_EXCEEDED
var CREATIVE_NOT_FOUND
var CREATIVE_REJECTED
var GOVERNANCE_DENIED
var IDEMPOTENCY_CONFLICT
var IDEMPOTENCY_EXPIRED
var INVALID_REQUEST
var INVALID_STATE
var IO_REQUIRED
var MEDIA_BUY_NOT_FOUND
var NOT_CANCELLABLE
var PACKAGE_NOT_FOUND
var POLICY_VIOLATION
var PRODUCT_EXPIRED
var PRODUCT_NOT_FOUND
var PRODUCT_UNAVAILABLE
var PROPOSAL_EXPIRED
var PROPOSAL_NOT_COMMITTED
var RATE_LIMITED
var SERVICE_UNAVAILABLE
var SESSION_NOT_FOUND
var SESSION_TERMINATED
var SIGNAL_NOT_FOUND
var TERMS_REJECTED
var UNSUPPORTED_FEATURE
var VALIDATION_ERROR
var VERSION_UNSUPPORTED
class EventType (*args, **kwds)
Expand source code
class EventType(Enum):
    page_view = 'page_view'
    view_content = 'view_content'
    select_content = 'select_content'
    select_item = 'select_item'
    search = 'search'
    share = 'share'
    add_to_cart = 'add_to_cart'
    remove_from_cart = 'remove_from_cart'
    viewed_cart = 'viewed_cart'
    add_to_wishlist = 'add_to_wishlist'
    initiate_checkout = 'initiate_checkout'
    add_payment_info = 'add_payment_info'
    purchase = 'purchase'
    refund = 'refund'
    lead = 'lead'
    qualify_lead = 'qualify_lead'
    close_convert_lead = 'close_convert_lead'
    disqualify_lead = 'disqualify_lead'
    complete_registration = 'complete_registration'
    subscribe = 'subscribe'
    start_trial = 'start_trial'
    app_install = 'app_install'
    app_launch = 'app_launch'
    contact = 'contact'
    schedule = 'schedule'
    donate = 'donate'
    submit_application = 'submit_application'
    custom = 'custom'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var add_payment_info
var add_to_cart
var add_to_wishlist
var app_install
var app_launch
var close_convert_lead
var complete_registration
var contact
var custom
var disqualify_lead
var donate
var initiate_checkout
var lead
var page_view
var purchase
var qualify_lead
var refund
var remove_from_cart
var schedule
var search
var select_content
var select_item
var share
var start_trial
var submit_application
var subscribe
var view_content
var viewed_cart
class ExtensionObject (**data: Any)
Expand source code
class ExtensionObject(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config

Inherited members

class FeatureResolver (capabilities: GetAdcpCapabilitiesResponse)
Expand source code
class FeatureResolver:
    """Resolves feature support from a GetAdcpCapabilitiesResponse.

    Supports multiple feature namespaces:

    - Protocol support: ``"media_buy"`` checks ``supported_protocols``
    - Extension support: ``"ext:scope3"`` checks ``extensions_supported``
    - Targeting: ``"targeting.geo_countries"`` checks
      ``media_buy.execution.targeting``
    - Media buy features: ``"inline_creative_management"`` checks
      ``media_buy.features``
    - Signals features: ``"catalog_signals"`` checks
      ``signals.features``
    """

    def __init__(self, capabilities: GetAdcpCapabilitiesResponse) -> None:
        self._caps = capabilities

        # Pre-compute the set of valid protocol names so supports() doesn't
        # need a runtime import on every call.
        from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import (
            SupportedProtocol,
        )

        self._valid_protocols = {p.value for p in SupportedProtocol}
        self._declared_protocols = {p.value for p in capabilities.supported_protocols}

    @property
    def capabilities(self) -> GetAdcpCapabilitiesResponse:
        return self._caps

    def supports_v3(self) -> bool:
        """Check if the seller supports ADCP v3.

        Returns:
            True if major_versions includes 3.
        """
        if self._caps.adcp is None:
            return False
        for v in self._caps.adcp.major_versions:
            if (v.root if hasattr(v, "root") else v) == 3:
                return True
        return False

    def supports(self, feature: str) -> bool:
        """Check if a feature is supported."""
        caps = self._caps

        # Extension check: "ext:scope3"
        if feature.startswith("ext:"):
            ext_name = feature[4:]
            if caps.extensions_supported is None:
                return False
            return any(item.root == ext_name for item in caps.extensions_supported)

        # Targeting check: "targeting.geo_countries"
        if feature.startswith("targeting."):
            attr_name = feature[len("targeting."):]
            if caps.media_buy is None or caps.media_buy.execution is None:
                return False
            targeting = caps.media_buy.execution.targeting
            if targeting is None:
                return False
            if attr_name not in type(targeting).model_fields:
                return False
            val = getattr(targeting, attr_name, None)
            # For bool fields, check truthiness. For object fields (like geo_metros),
            # presence means supported.
            return val is not None and val is not False

        # Protocol check: if the string is a known protocol name, resolve it
        # against supported_protocols and stop — don't fall through to features.
        if feature in self._declared_protocols:
            return True
        if feature in self._valid_protocols:
            return False

        # Media buy features check
        if caps.media_buy is not None and caps.media_buy.features is not None:
            if feature in type(caps.media_buy.features).model_fields:
                val = getattr(caps.media_buy.features, feature, None)
                if val is True:
                    return True

        # Signals features check
        if caps.signals is not None and caps.signals.features is not None:
            if feature in type(caps.signals.features).model_fields:
                val = getattr(caps.signals.features, feature, None)
                if val is True:
                    return True

        return False

    def require(
        self,
        *features: str,
        agent_id: str | None = None,
        agent_uri: str | None = None,
    ) -> None:
        """Assert that all listed features are supported.

        Args:
            *features: Feature identifiers to require.
            agent_id: Optional agent ID for error context.
            agent_uri: Optional agent URI for error context.

        Raises:
            ADCPFeatureUnsupportedError: If any features are not supported.
        """
        unsupported = [f for f in features if not self.supports(f)]
        if not unsupported:
            return

        declared = self.get_declared_features()

        raise ADCPFeatureUnsupportedError(
            unsupported_features=unsupported,
            declared_features=declared,
            agent_id=agent_id,
            agent_uri=agent_uri,
        )

    def get_declared_features(self) -> list[str]:
        """Collect all features the response declares as supported."""
        caps = self._caps
        declared: list[str] = []

        # Supported protocols
        for p in caps.supported_protocols:
            declared.append(p.value)

        # Media buy features
        if caps.media_buy is not None and caps.media_buy.features is not None:
            for field_name in type(caps.media_buy.features).model_fields:
                if getattr(caps.media_buy.features, field_name, None) is True:
                    declared.append(field_name)

        # Signals features
        if caps.signals is not None and caps.signals.features is not None:
            for field_name in type(caps.signals.features).model_fields:
                if getattr(caps.signals.features, field_name, None) is True:
                    declared.append(field_name)

        # Targeting features
        if caps.media_buy is not None and caps.media_buy.execution is not None:
            targeting = caps.media_buy.execution.targeting
            if targeting is not None:
                for field_name in type(targeting).model_fields:
                    val = getattr(targeting, field_name, None)
                    if val is not None and val is not False:
                        declared.append(f"targeting.{field_name}")

        # Extensions
        if caps.extensions_supported is not None:
            for item in caps.extensions_supported:
                declared.append(f"ext:{item.root}")

        return declared

Resolves feature support from a GetAdcpCapabilitiesResponse.

Supports multiple feature namespaces:

  • Protocol support: "media_buy" checks supported_protocols
  • Extension support: "ext:scope3" checks extensions_supported
  • Targeting: "targeting.geo_countries" checks media_buy.execution.targeting
  • Media buy features: "inline_creative_management" checks media_buy.features
  • Signals features: "catalog_signals" checks signals.features

Instance variables

prop capabilities : GetAdcpCapabilitiesResponse
Expand source code
@property
def capabilities(self) -> GetAdcpCapabilitiesResponse:
    return self._caps

Methods

def get_declared_features(self) ‑> list[str]
Expand source code
def get_declared_features(self) -> list[str]:
    """Collect all features the response declares as supported."""
    caps = self._caps
    declared: list[str] = []

    # Supported protocols
    for p in caps.supported_protocols:
        declared.append(p.value)

    # Media buy features
    if caps.media_buy is not None and caps.media_buy.features is not None:
        for field_name in type(caps.media_buy.features).model_fields:
            if getattr(caps.media_buy.features, field_name, None) is True:
                declared.append(field_name)

    # Signals features
    if caps.signals is not None and caps.signals.features is not None:
        for field_name in type(caps.signals.features).model_fields:
            if getattr(caps.signals.features, field_name, None) is True:
                declared.append(field_name)

    # Targeting features
    if caps.media_buy is not None and caps.media_buy.execution is not None:
        targeting = caps.media_buy.execution.targeting
        if targeting is not None:
            for field_name in type(targeting).model_fields:
                val = getattr(targeting, field_name, None)
                if val is not None and val is not False:
                    declared.append(f"targeting.{field_name}")

    # Extensions
    if caps.extensions_supported is not None:
        for item in caps.extensions_supported:
            declared.append(f"ext:{item.root}")

    return declared

Collect all features the response declares as supported.

def require(self, *features: str, agent_id: str | None = None, agent_uri: str | None = None) ‑> None
Expand source code
def require(
    self,
    *features: str,
    agent_id: str | None = None,
    agent_uri: str | None = None,
) -> None:
    """Assert that all listed features are supported.

    Args:
        *features: Feature identifiers to require.
        agent_id: Optional agent ID for error context.
        agent_uri: Optional agent URI for error context.

    Raises:
        ADCPFeatureUnsupportedError: If any features are not supported.
    """
    unsupported = [f for f in features if not self.supports(f)]
    if not unsupported:
        return

    declared = self.get_declared_features()

    raise ADCPFeatureUnsupportedError(
        unsupported_features=unsupported,
        declared_features=declared,
        agent_id=agent_id,
        agent_uri=agent_uri,
    )

Assert that all listed features are supported.

Args

*features
Feature identifiers to require.
agent_id
Optional agent ID for error context.
agent_uri
Optional agent URI for error context.

Raises

ADCPFeatureUnsupportedError
If any features are not supported.
def supports(self, feature: str) ‑> bool
Expand source code
def supports(self, feature: str) -> bool:
    """Check if a feature is supported."""
    caps = self._caps

    # Extension check: "ext:scope3"
    if feature.startswith("ext:"):
        ext_name = feature[4:]
        if caps.extensions_supported is None:
            return False
        return any(item.root == ext_name for item in caps.extensions_supported)

    # Targeting check: "targeting.geo_countries"
    if feature.startswith("targeting."):
        attr_name = feature[len("targeting."):]
        if caps.media_buy is None or caps.media_buy.execution is None:
            return False
        targeting = caps.media_buy.execution.targeting
        if targeting is None:
            return False
        if attr_name not in type(targeting).model_fields:
            return False
        val = getattr(targeting, attr_name, None)
        # For bool fields, check truthiness. For object fields (like geo_metros),
        # presence means supported.
        return val is not None and val is not False

    # Protocol check: if the string is a known protocol name, resolve it
    # against supported_protocols and stop — don't fall through to features.
    if feature in self._declared_protocols:
        return True
    if feature in self._valid_protocols:
        return False

    # Media buy features check
    if caps.media_buy is not None and caps.media_buy.features is not None:
        if feature in type(caps.media_buy.features).model_fields:
            val = getattr(caps.media_buy.features, feature, None)
            if val is True:
                return True

    # Signals features check
    if caps.signals is not None and caps.signals.features is not None:
        if feature in type(caps.signals.features).model_fields:
            val = getattr(caps.signals.features, feature, None)
            if val is True:
                return True

    return False

Check if a feature is supported.

def supports_v3(self) ‑> bool
Expand source code
def supports_v3(self) -> bool:
    """Check if the seller supports ADCP v3.

    Returns:
        True if major_versions includes 3.
    """
    if self._caps.adcp is None:
        return False
    for v in self._caps.adcp.major_versions:
        if (v.root if hasattr(v, "root") else v) == 3:
            return True
    return False

Check if the seller supports ADCP v3.

Returns

True if major_versions includes 3.

class FederatedAgentWithDetails (**data: Any)
Expand source code
class FederatedAgentWithDetails(RegistryBaseModel):
    url: str
    name: str
    type: AgentType
    protocol: AgentProtocol | None = None
    description: str | None = None
    mcp_endpoint: str | None = None
    contact: AgentDetailedContact | None = None
    added_date: str | None = None
    source: AgentSource | None = None
    member: AgentMember | None = None
    discovered_from: AgentDiscoveredFrom | None = None
    health: AgentHealth | None = None
    stats: AgentStats | None = None
    capabilities: AgentCapabilities | None = None
    compliance: AgentCompliance | None = None
    publisher_domains: list[str] | None = None
    property_summary: PropertySummary | None = None

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var added_date : str | None
var capabilitiesAgentCapabilities | None
var complianceAgentCompliance | None
var contactAgentDetailedContact | None
var description : str | None
var discovered_fromAgentDiscoveredFrom | None
var healthAgentHealth | None
var mcp_endpoint : str | None
var memberAgentMember | None
var model_config
var name : str
var property_summaryPropertySummary | None
var protocolAgentProtocol | None
var publisher_domains : list[str] | None
var sourceAgentSource | None
var statsAgentStats | None
var typeAgentType
var url : str
class FederatedPublisher (**data: Any)
Expand source code
class FederatedPublisher(RegistryBaseModel):
    domain: str
    source: AgentSource | None = None
    member: AgentMember | None = None
    agent_count: int | None = None
    last_validated: str | None = None
    discovered_from: PublisherDiscoveredFrom | None = None
    has_valid_adagents: bool | None = None
    discovered_at: str | None = None

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var agent_count : int | None
var discovered_at : str | None
var discovered_fromPublisherDiscoveredFrom | None
var domain : str
var has_valid_adagents : bool | None
var last_validated : str | None
var memberAgentMember | None
var model_config
var sourceAgentSource | None
class FeedEvent (**data: Any)
Expand source code
class FeedEvent(RegistryBaseModel):
    """Single event from the registry change feed."""

    event_id: str
    event_type: str
    entity_type: str
    entity_id: str
    payload: dict[str, Any] = Field(default_factory=dict)
    actor: str
    created_at: str

Single event from the registry change feed.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var actor : str
var created_at : str
var entity_id : str
var entity_type : str
var event_id : str
var event_type : str
var model_config
var payload : dict[str, typing.Any]
class FeedFormat (*args, **kwds)
Expand source code
class FeedFormat(Enum):
    google_merchant_center = 'google_merchant_center'
    facebook_catalog = 'facebook_catalog'
    shopify = 'shopify'
    linkedin_jobs = 'linkedin_jobs'
    custom = 'custom'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var custom
var facebook_catalog
var google_merchant_center
var linkedin_jobs
var shopify
class FeedPage (**data: Any)
Expand source code
class FeedPage(RegistryBaseModel):
    """Page of events from the registry change feed."""

    events: list[FeedEvent] = Field(default_factory=list)
    cursor: str | None = None
    has_more: bool

Page of events from the registry change feed.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var cursor : str | None
var events : list[FeedEvent]
var has_more : bool
var model_config
class FileCursorStore (path: str | Path = '.adcp-sync-cursor.json')
Expand source code
class FileCursorStore:
    """Default cursor store using a local JSON file.

    Args:
        path: Path to the cursor file. Defaults to .adcp-sync-cursor.json
    """

    def __init__(self, path: str | Path = ".adcp-sync-cursor.json") -> None:
        self._path = Path(path)

    async def load(self) -> str | None:
        try:
            data = json.loads(self._path.read_text())
            return data.get("cursor")  # type: ignore[no-any-return]
        except (FileNotFoundError, json.JSONDecodeError, KeyError):
            return None

    async def save(self, cursor: str) -> None:
        temp = self._path.with_suffix(".tmp")
        temp.write_text(json.dumps({"cursor": cursor}))
        temp.replace(self._path)  # Atomic rename

Default cursor store using a local JSON file.

Args

path
Path to the cursor file. Defaults to .adcp-sync-cursor.json

Methods

async def load(self) ‑> str | None
Expand source code
async def load(self) -> str | None:
    try:
        data = json.loads(self._path.read_text())
        return data.get("cursor")  # type: ignore[no-any-return]
    except (FileNotFoundError, json.JSONDecodeError, KeyError):
        return None
async def save(self, cursor: str) ‑> None
Expand source code
async def save(self, cursor: str) -> None:
    temp = self._path.with_suffix(".tmp")
    temp.write_text(json.dumps({"cursor": cursor}))
    temp.replace(self._path)  # Atomic rename
class FlatRatePricingOption (**data: Any)
Expand source code
class FlatRatePricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[
        Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume')
    ]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    parameters: Annotated[
        Parameters | None,
        Field(
            description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.',
            title='DoohParameters',
        ),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var min_spend_per_package : float | None
var model_config
var parametersParameters | None
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['flat_rate']
var pricing_option_id : str

Inherited members

class Format (**data: Any)
Expand source code
class Format(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    format_id: Annotated[
        format_id_1.FormatId,
        Field(description='Structured format identifier with agent URL and format name'),
    ]
    name: Annotated[str, Field(description='Human-readable format name')]
    description: Annotated[
        str | None,
        Field(
            description='Plain text explanation of what this format does and what assets it requires'
        ),
    ] = None
    example_url: Annotated[
        AnyUrl | None,
        Field(
            description='Optional URL to showcase page with examples and interactive demos of this format'
        ),
    ] = None
    accepts_parameters: Annotated[
        list[format_id_parameter.FormatIdParameter] | None,
        Field(
            description='List of parameters this format accepts in format_id. Template formats define which parameters (dimensions, duration, etc.) can be specified when instantiating the format. Empty or omitted means this is a concrete format with fixed parameters.'
        ),
    ] = None
    renders: Annotated[
        list[Renders | Renders7] | None,
        Field(
            description='Specification of rendered pieces for this format. Most formats produce a single render. Companion ad formats (video + banner), adaptive formats, and multi-placement formats produce multiple renders. Each render specifies its role and dimensions.',
            min_length=1,
        ),
    ] = None
    assets: Annotated[
        list[
            Assets
            | Assets81
            | Assets82
            | Assets83
            | Assets84
            | Assets85
            | Assets86
            | Assets87
            | Assets88
            | Assets89
            | Assets90
            | Assets91
            | Assets92
            | Assets93
            | Assets94
        ]
        | None,
        Field(
            description="Array of all assets supported for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Use the 'required' boolean on each asset to indicate whether it's mandatory."
        ),
    ] = None
    delivery: Annotated[
        dict[str, Any] | None,
        Field(description='Delivery method specifications (e.g., hosted, VAST, third-party tags)'),
    ] = None
    supported_macros: Annotated[
        list[universal_macro.UniversalMacro | str] | None,
        Field(
            description='List of universal macros supported by this format (e.g., MEDIA_BUY_ID, CACHEBUSTER, DEVICE_ID). Used for validation and developer tooling. See docs/creative/universal-macros.mdx for full documentation.'
        ),
    ] = None
    input_format_ids: Annotated[
        list[format_id_1.FormatId] | None,
        Field(
            description='Array of format IDs this format accepts as input creative manifests. When present, indicates this format can take existing creatives in these formats as input. Omit for formats that work from raw assets (images, text, etc.) rather than existing creatives.'
        ),
    ] = None
    output_format_ids: Annotated[
        list[format_id_1.FormatId] | None,
        Field(
            description='Array of format IDs that this format can produce as output. When present, indicates this format can build creatives in these output formats (e.g., a multi-publisher template format might produce standard display formats across many publishers). Omit for formats that produce a single fixed output (the format itself).'
        ),
    ] = None
    format_card: Annotated[
        FormatCard | None,
        Field(
            description='Optional standard visual card (300x400px) for displaying this format in user interfaces. Can be rendered via preview_creative or pre-generated.'
        ),
    ] = None
    accessibility: Annotated[
        Accessibility | None,
        Field(
            description='Accessibility posture of this format. Declares the WCAG conformance level that creatives produced by this format will meet.'
        ),
    ] = None
    supported_disclosure_positions: Annotated[
        list[disclosure_position.DisclosurePosition] | None,
        Field(
            description='Disclosure positions this format can render. Buyers use this to determine whether a format can satisfy their compliance requirements before submitting a creative. When omitted, the format makes no disclosure rendering guarantees — creative agents SHOULD treat this as incompatible with briefs that require specific disclosure positions. Values correspond to positions on creative-brief.json required_disclosures.',
            min_length=1,
        ),
    ] = None
    disclosure_capabilities: Annotated[
        list[DisclosureCapability] | None,
        Field(
            description='Structured disclosure capabilities per position with persistence modes. Declares which persistence behaviors each disclosure position supports, enabling persistence-aware matching against provenance render guidance and brief requirements. When present, supersedes supported_disclosure_positions for persistence-aware queries. The flat supported_disclosure_positions field is retained for backward compatibility. Each position MUST appear at most once; validators and agents SHOULD reject duplicates.',
            min_length=1,
        ),
    ] = None
    format_card_detailed: Annotated[
        FormatCardDetailed | None,
        Field(
            description='Optional detailed card with carousel and full specifications. Provides rich format documentation similar to ad spec pages.'
        ),
    ] = None
    reported_metrics: Annotated[
        list[available_metric.AvailableMetric] | None,
        Field(
            description='Metrics this format can produce in delivery reporting. Buyers receive the intersection of format reported_metrics and product available_metrics. If omitted, the format defers entirely to product-level metric declarations.',
            min_length=1,
        ),
    ] = None
    pricing_options: Annotated[
        list[vendor_pricing_option.VendorPricingOption] | None,
        Field(
            description='Pricing options for this format. Used by transformation and generation agents that charge per format adapted, per image generated, or per unit of work. Present when the request included include_pricing=true and account. Ad servers and library-based agents expose pricing on list_creatives instead.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accepts_parameters : list[FormatIdParameter] | None
var accessibilityAccessibility | None
var assets : list[Assets | Assets81 | Assets82 | Assets83 | Assets84 | Assets85 | Assets86 | Assets87 | Assets88 | Assets89 | Assets90 | Assets91 | Assets92 | Assets93 | Assets94] | None
var delivery : dict[str, typing.Any] | None
var description : str | None
var disclosure_capabilities : list[DisclosureCapability] | None
var example_url : pydantic.networks.AnyUrl | None
var format_cardFormatCard | None
var format_card_detailedFormatCardDetailed | None
var format_idFormatId
var input_format_ids : list[FormatId] | None
var model_config
var name : str
var output_format_ids : list[FormatId] | None
var pricing_options : list[VendorPricingOption] | None
var renders : list[Renders | Renders7] | None
var reported_metrics : list[AvailableMetric] | None
var supported_disclosure_positions : list[DisclosurePosition] | None
var supported_macros : list[UniversalMacro | str] | None

Inherited members

class FormatId (**data: Any)
Expand source code
class FormatId(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    agent_url: Annotated[
        AnyUrl,
        Field(
            description="URL of the agent that defines this format (e.g., 'https://creatives.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats)"
        ),
    ]
    id: Annotated[
        str,
        Field(
            description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.",
            pattern='^[a-zA-Z0-9_-]+$',
        ),
    ]
    width: Annotated[
        int | None,
        Field(
            description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.',
            ge=1,
        ),
    ] = None
    height: Annotated[
        int | None,
        Field(
            description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.',
            ge=1,
        ),
    ] = None
    duration_ms: Annotated[
        float | None,
        Field(
            description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.',
            ge=1.0,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var agent_url : pydantic.networks.AnyUrl
var duration_ms : float | None
var height : int | None
var id : str
var model_config
var width : int | None

Inherited members

class GetAccountFinancialsRequest (**data: Any)
Expand source code
class GetAccountFinancialsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference,
        Field(description="Account to query financials for. Must be an operator-billed account."),
    ]
    period: Annotated[
        date_range.DateRange | None,
        Field(
            description="Date range for the spend summary. Defaults to the current billing cycle if omitted."
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var model_config
var periodDateRange | None

Inherited members

class GetAccountFinancialsSuccessResponse (**data: Any)
Expand source code
class GetAccountFinancialsResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    account: Annotated[
        account_ref.AccountReference,
        Field(description="Account reference, echoed from the request"),
    ]
    currency: Annotated[
        str,
        Field(
            description="ISO 4217 currency code for all monetary amounts in this response",
            pattern="^[A-Z]{3}$",
        ),
    ]
    period: Annotated[
        date_range.DateRange,
        Field(
            description="The actual period covered by spend data. May differ from the requested period if the seller adjusts to billing cycle boundaries."
        ),
    ]
    timezone: Annotated[
        str,
        Field(
            description="IANA timezone of the seller's billing day boundaries (e.g., 'America/New_York'). All dates in this response — period, invoice periods, due dates — are calendar dates in this timezone. Buyers in a different timezone should expect spend boundaries to differ from their own calendar day."
        ),
    ]
    spend: Annotated[Spend | None, Field(description="Spend summary for the period")] = None
    credit: Annotated[
        Credit | None,
        Field(
            description="Credit status. Present for credit-based accounts (payment_terms like net_30)."
        ),
    ] = None
    balance: Annotated[
        Balance | None, Field(description="Prepay balance. Present for prepay accounts.")
    ] = None
    payment_status: Annotated[
        PaymentStatus | None,
        Field(
            description="Overall payment status. current: all obligations met. past_due: one or more invoices overdue. suspended: account suspended due to payment issues."
        ),
    ] = None
    payment_terms: Annotated[
        PaymentTerms | None, Field(description="Payment terms in effect for this account")
    ] = None
    invoices: Annotated[
        list[Invoice] | None,
        Field(description="Recent invoices. Sellers may limit the number returned."),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var balanceBalance | None
var contextContextObject | None
var creditCredit | None
var currency : str
var extExtensionObject | None
var invoices : list[Invoice] | None
var model_config
var payment_statusPaymentStatus | None
var payment_termsPaymentTerms | None
var periodDateRange
var spendSpend | None
var timezone : str

Inherited members

class GetAccountFinancialsErrorResponse (**data: Any)
Expand source code
class GetAccountFinancialsResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    errors: Annotated[list[error.Error], Field(description="Operation-level errors", min_length=1)]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class GetBrandIdentityRequest (**data: Any)
Expand source code
class GetBrandIdentityRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    brand_id: Annotated[str, Field(description='Brand identifier from brand.json brands array')]
    fields: Annotated[
        list[FieldModel] | None,
        Field(
            description='Optional identity sections to include in the response. When omitted, all sections the caller is authorized to see are returned. Core fields (brand_id, house, names) are always returned and do not need to be requested.',
            min_length=1,
        ),
    ] = None
    use_case: Annotated[
        str | None,
        Field(
            description="Intended use case, so the agent can tailor the response. A 'voice_synthesis' use case returns voice configs; a 'likeness' use case returns high-res photos and appearance guidelines."
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var brand_id : str
var contextContextObject | None
var extExtensionObject | None
var fields : list[FieldModel] | None
var model_config
var use_case : str | None

Inherited members

class GetBrandIdentitySuccessResponse (**data: Any)
Expand source code
class GetBrandIdentityResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    brand_id: Annotated[str, Field(description='Brand identifier')]
    house: Annotated[
        House,
        Field(
            description='The house (corporate entity) this brand belongs to. Always returned regardless of authorization level.'
        ),
    ]
    names: Annotated[
        list[dict[str, str]],
        Field(
            description="Localized brand names with BCP 47 locale code keys (e.g., 'en_US', 'fr_CA'). Bare language codes ('en') are accepted as wildcards for backwards compatibility."
        ),
    ]
    description: Annotated[str | None, Field(description='Brand description')] = None
    industries: Annotated[
        list[str] | None, Field(description='Brand industries.', min_length=1)
    ] = None
    keller_type: Annotated[
        KellerType | None,
        Field(
            description='Brand architecture type: master (primary brand of house), sub_brand (carries parent name), endorsed (independent identity backed by parent), independent (operates separately)'
        ),
    ] = None
    logos: Annotated[
        list[Logo] | None,
        Field(
            description='Brand logos. Public callers get standard logos; authorized callers also receive high-res variants. Shape matches brand.json logo definition.'
        ),
    ] = None
    colors: Annotated[
        Colors | None,
        Field(
            description='Brand color palette. Each role accepts a single hex color or an array of hex colors. Shape matches brand.json colors definition.'
        ),
    ] = None
    fonts: Annotated[
        Fonts | None,
        Field(
            description="Brand typography. Each key is a role name (e.g., 'primary', 'secondary') referenced by type_scale entries. Values are either a CSS font-family string or a structured object with family name and font files. Shape matches brand.json fonts definition."
        ),
    ] = None
    visual_guidelines: Annotated[
        dict[str, Any] | None,
        Field(
            description='Structured visual rules for generative creative systems (photography, graphic_style, colorways, type_scale, motion). Matches brand.json visual_guidelines definition. Authorized callers only.'
        ),
    ] = None
    tone: Annotated[Tone | None, Field(description='Brand voice and messaging guidelines')] = None
    tagline: Annotated[
        str | Tagline | None,
        Field(
            description='Brand tagline or slogan. Accepts a plain string or a localized array matching the names pattern.'
        ),
    ] = None
    voice_synthesis: Annotated[
        VoiceSynthesis | None,
        Field(description='Voice synthesis configuration for AI-generated audio'),
    ] = None
    assets: Annotated[
        list[Asset] | None,
        Field(
            description='Available brand assets (images, audio, video). Authorized callers only. Shape matches brand.json asset definition.'
        ),
    ] = None
    rights: Annotated[
        Rights | None,
        Field(description='Rights availability summary. For detailed pricing, use get_rights.'),
    ] = None
    available_fields: Annotated[
        list[AvailableField] | None,
        Field(
            description='Fields available but not returned in this response due to authorization level. Tells the caller what they would gain by linking their account via sync_accounts. Values match the request fields enum.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var assets : list[Asset] | None
var available_fields : list[AvailableField] | None
var brand_id : str
var colorsColors | None
var contextContextObject | None
var description : str | None
var extExtensionObject | None
var fontsFonts | None
var houseHouse
var industries : list[str] | None
var keller_typeKellerType | None
var logos : list[Logo] | None
var model_config
var names : list[dict[str, str]]
var rightsRights | None
var tagline : str | Tagline | None
var toneTone | None
var visual_guidelines : dict[str, typing.Any] | None
var voice_synthesisVoiceSynthesis | None

Inherited members

class GetBrandIdentityErrorResponse (**data: Any)
Expand source code
class GetBrandIdentityResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[list[error.Error], Field(min_length=1)]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class GetContentStandardsSuccessResponse (**data: Any)
Expand source code
class GetContentStandardsResponse1(ContentStandards):
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config

Inherited members

class GetContentStandardsErrorResponse (**data: Any)
Expand source code
class GetContentStandardsResponse2(AdCPBaseModel):
    errors: list[error.Error]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class GetCreativeDeliveryRequest (**data: Any)
Expand source code
class GetCreativeDeliveryRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for routing and scoping. Limits results to creatives within this account.'
        ),
    ] = None
    media_buy_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.',
            min_length=1,
        ),
    ] = None
    creative_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.',
            min_length=1,
        ),
    ] = None
    start_date: Annotated[
        str | None,
        Field(
            description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    end_date: Annotated[
        str | None,
        Field(
            description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    max_variants: Annotated[
        int | None,
        Field(
            description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.',
            ge=1,
        ),
    ] = None
    pagination: Annotated[
        pagination_request.PaginationRequest | None,
        Field(
            description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var creative_ids : list[str] | None
var end_date : str | None
var extExtensionObject | None
var max_variants : int | None
var media_buy_ids : list[str] | None
var model_config
var paginationPaginationRequest | None
var start_date : str | None
class GetCreativeDeliveryByBuyerRefRequest (**data: Any)
Expand source code
class GetCreativeDeliveryRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for routing and scoping. Limits results to creatives within this account.'
        ),
    ] = None
    media_buy_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.',
            min_length=1,
        ),
    ] = None
    creative_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.',
            min_length=1,
        ),
    ] = None
    start_date: Annotated[
        str | None,
        Field(
            description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    end_date: Annotated[
        str | None,
        Field(
            description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    max_variants: Annotated[
        int | None,
        Field(
            description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.',
            ge=1,
        ),
    ] = None
    pagination: Annotated[
        pagination_request.PaginationRequest | None,
        Field(
            description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var creative_ids : list[str] | None
var end_date : str | None
var extExtensionObject | None
var max_variants : int | None
var media_buy_ids : list[str] | None
var model_config
var paginationPaginationRequest | None
var start_date : str | None
class GetCreativeDeliveryByCreativeRequest (**data: Any)
Expand source code
class GetCreativeDeliveryRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for routing and scoping. Limits results to creatives within this account.'
        ),
    ] = None
    media_buy_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.',
            min_length=1,
        ),
    ] = None
    creative_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.',
            min_length=1,
        ),
    ] = None
    start_date: Annotated[
        str | None,
        Field(
            description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    end_date: Annotated[
        str | None,
        Field(
            description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    max_variants: Annotated[
        int | None,
        Field(
            description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.',
            ge=1,
        ),
    ] = None
    pagination: Annotated[
        pagination_request.PaginationRequest | None,
        Field(
            description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var creative_ids : list[str] | None
var end_date : str | None
var extExtensionObject | None
var max_variants : int | None
var media_buy_ids : list[str] | None
var model_config
var paginationPaginationRequest | None
var start_date : str | None
class GetCreativeDeliveryByMediaBuyRequest (**data: Any)
Expand source code
class GetCreativeDeliveryRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for routing and scoping. Limits results to creatives within this account.'
        ),
    ] = None
    media_buy_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.',
            min_length=1,
        ),
    ] = None
    creative_ids: Annotated[
        list[str] | None,
        Field(
            description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.',
            min_length=1,
        ),
    ] = None
    start_date: Annotated[
        str | None,
        Field(
            description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    end_date: Annotated[
        str | None,
        Field(
            description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    max_variants: Annotated[
        int | None,
        Field(
            description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.',
            ge=1,
        ),
    ] = None
    pagination: Annotated[
        pagination_request.PaginationRequest | None,
        Field(
            description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var creative_ids : list[str] | None
var end_date : str | None
var extExtensionObject | None
var max_variants : int | None
var media_buy_ids : list[str] | None
var model_config
var paginationPaginationRequest | None
var start_date : str | None

Inherited members

class GetCreativeDeliveryResponse (**data: Any)
Expand source code
class GetCreativeDeliveryResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    account_id: Annotated[
        str | None,
        Field(
            description='Account identifier. Present when the response spans or is scoped to a specific account.'
        ),
    ] = None
    media_buy_id: Annotated[
        str | None,
        Field(
            description="Publisher's media buy identifier. Present when the request was scoped to a single media buy."
        ),
    ] = None
    currency: Annotated[
        str,
        Field(
            description="ISO 4217 currency code for monetary values in this response (e.g., 'USD', 'EUR')",
            pattern='^[A-Z]{3}$',
        ),
    ]
    reporting_period: Annotated[ReportingPeriod, Field(description='Date range for the report.')]
    creatives: Annotated[
        list[Creative], Field(description='Creative delivery data with variant breakdowns')
    ]
    pagination: Annotated[
        Pagination | None,
        Field(
            description='Pagination information. Present when the request included pagination parameters.'
        ),
    ] = None
    errors: Annotated[
        list[error.Error] | None, Field(description='Task-specific errors and warnings')
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var account_id : str | None
var contextContextObject | None
var creatives : list[Creative]
var currency : str
var errors : list[Error] | None
var extExtensionObject | None
var media_buy_id : str | None
var model_config
var paginationPagination | None
var reporting_periodReportingPeriod

Inherited members

class GetCreativeFeaturesRequest (**data: Any)
Expand source code
class GetCreativeFeaturesRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    creative_manifest: Annotated[
        creative_manifest_1.CreativeManifest,
        Field(description='The creative manifest to evaluate. Contains format_id and assets.'),
    ]
    feature_ids: Annotated[
        list[str] | None,
        Field(
            description='Optional filter to specific features. If omitted, returns all available features.',
            min_length=1,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for billing this evaluation. Required when the governance agent charges per evaluation.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var creative_manifestCreativeManifest
var extExtensionObject | None
var feature_ids : list[str] | None
var model_config

Inherited members

class GetCreativeFeaturesSuccessResponse (**data: Any)
Expand source code
class GetCreativeFeaturesResponse1(AdCPBaseModel):
    results: Annotated[
        list[creative_feature_result.CreativeFeatureResult],
        Field(description='Feature values for the evaluated creative'),
    ]
    detail_url: Annotated[
        AnyUrl | None,
        Field(
            description="URL to the vendor's full assessment report. The vendor controls what information is disclosed and access control."
        ),
    ] = None
    pricing_option_id: Annotated[
        str | None,
        Field(
            description='Which rate card pricing option was applied for this evaluation. Present when the governance agent charges for evaluations and account was provided in the request.'
        ),
    ] = None
    vendor_cost: Annotated[
        float | None,
        Field(description='Cost incurred for this evaluation, denominated in currency.', ge=0.0),
    ] = None
    currency: Annotated[
        str | None,
        Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'),
    ] = None
    consumption: Annotated[
        creative_consumption.CreativeConsumption | None,
        Field(
            description='Structured consumption details for this evaluation. Informational — lets the buyer verify that vendor_cost is consistent with the rate card. vendor_cost is the billing source of truth.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var consumptionCreativeConsumption | None
var contextContextObject | None
var currency : str | None
var detail_url : pydantic.networks.AnyUrl | None
var extExtensionObject | None
var model_config
var pricing_option_id : str | None
var results : list[CreativeFeatureResult]
var vendor_cost : float | None

Inherited members

class GetCreativeFeaturesErrorResponse (**data: Any)
Expand source code
class GetCreativeFeaturesResponse2(AdCPBaseModel):
    errors: list[error.Error]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class GetMediaBuyArtifactsSuccessResponse (**data: Any)
Expand source code
class GetMediaBuyArtifactsResponse1(AdCPBaseModel):
    media_buy_id: Annotated[str, Field(description='Media buy these artifacts belong to')]
    artifacts: Annotated[
        list[Artifact], Field(description='Delivery records with full artifact content')
    ]
    collection_info: Annotated[
        CollectionInfo | None,
        Field(
            description='Information about artifact collection for this media buy. Sampling is configured at buy creation time — this reports what was actually collected.'
        ),
    ] = None
    pagination: pagination_response.PaginationResponse | None = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var artifacts : list[Artifact]
var collection_infoCollectionInfo | None
var contextContextObject | None
var extExtensionObject | None
var media_buy_id : str
var model_config
var paginationPaginationResponse | None

Inherited members

class GetMediaBuyArtifactsErrorResponse (**data: Any)
Expand source code
class GetMediaBuyArtifactsResponse2(AdCPBaseModel):
    errors: list[error.Error]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class GetMediaBuyDeliveryRequest (**data: Any)
Expand source code
class GetMediaBuyDeliveryRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Filter delivery data to a specific account. When omitted, returns data across all accessible accounts.'
        ),
    ] = None
    media_buy_ids: Annotated[
        list[str] | None,
        Field(description='Array of media buy IDs to get delivery data for', min_length=1),
    ] = None
    status_filter: Annotated[
        media_buy_status.MediaBuyStatus | StatusFilter | None,
        Field(description='Filter by status. Can be a single status or array of statuses'),
    ] = None
    start_date: Annotated[
        str | None,
        Field(
            description="Start date for reporting period (YYYY-MM-DD). When omitted along with end_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    end_date: Annotated[
        str | None,
        Field(
            description="End date for reporting period (YYYY-MM-DD). When omitted along with start_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'.",
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    include_package_daily_breakdown: Annotated[
        bool | None,
        Field(
            description='When true, include daily_breakdown arrays within each package in by_package. Useful for per-package pacing analysis and line-item monitoring. Omit or set false to reduce response size — package daily data can be large for multi-package buys over long flights.'
        ),
    ] = False
    attribution_window: Annotated[
        AttributionWindow | None,
        Field(
            description='Attribution window to apply for conversion metrics. When provided, the seller returns conversion data using the requested lookback windows instead of their platform default. The seller echoes the applied window in the response. Sellers that do not support configurable windows ignore this field and return their default. Check get_adcp_capabilities conversion_tracking.attribution_windows for available options.'
        ),
    ] = None
    reporting_dimensions: Annotated[
        ReportingDimensions | None,
        Field(
            description='Request dimensional breakdowns in delivery reporting. Each key enables a specific breakdown dimension within by_package — include as an empty object (e.g., "device_type": {}) to activate with defaults. Omit entirely for no breakdowns (backward compatible). Unsupported dimensions are silently omitted from the response. Note: keyword, catalog_item, and creative breakdowns are returned automatically when the seller supports them and are not controlled by this object.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var attribution_windowAttributionWindow | None
var contextContextObject | None
var end_date : str | None
var extExtensionObject | None
var include_package_daily_breakdown : bool | None
var media_buy_ids : list[str] | None
var model_config
var reporting_dimensionsReportingDimensions | None
var start_date : str | None
var status_filterMediaBuyStatus | StatusFilter | None

Inherited members

class GetMediaBuyDeliveryResponse (**data: Any)
Expand source code
class GetMediaBuyDeliveryResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    notification_type: Annotated[
        NotificationType | None,
        Field(
            description='Type of webhook notification (only present in webhook deliveries): scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = resending period with corrected data (same window), window_update = resending period with a wider measurement window (e.g., C3 superseding live, C7 superseding C3)'
        ),
    ] = None
    partial_data: Annotated[
        bool | None,
        Field(
            description='Indicates if any media buys in this webhook have missing/delayed data (only present in webhook deliveries)'
        ),
    ] = None
    unavailable_count: Annotated[
        int | None,
        Field(
            description='Number of media buys with reporting_delayed or failed status (only present in webhook deliveries when partial_data is true)',
            ge=0,
        ),
    ] = None
    sequence_number: Annotated[
        int | None,
        Field(
            description='Sequential notification number (only present in webhook deliveries, starts at 1)',
            ge=1,
        ),
    ] = None
    next_expected_at: Annotated[
        AwareDatetime | None,
        Field(
            description="ISO 8601 timestamp for next expected notification (only present in webhook deliveries when notification_type is not 'final')"
        ),
    ] = None
    reporting_period: Annotated[
        ReportingPeriod,
        Field(description='Date range for the report. All periods use UTC timezone.'),
    ]
    currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')]
    attribution_window: Annotated[
        attribution_window_1.AttributionWindow | None,
        Field(
            description='Attribution methodology and lookback windows used for conversion metrics in this response. All media buys from a single seller share the same attribution methodology. Enables cross-platform comparison (e.g., Amazon 14-day click vs. Criteo 30-day click).'
        ),
    ] = None
    aggregated_totals: Annotated[
        AggregatedTotals | None,
        Field(
            description='Combined metrics across all returned media buys. Only included in API responses (get_media_buy_delivery), not in webhook notifications.'
        ),
    ] = None
    media_buy_deliveries: Annotated[
        list[MediaBuyDelivery],
        Field(
            description='Array of delivery data for media buys. When used in webhook notifications, may contain multiple media buys aggregated by publisher. When used in get_media_buy_delivery API responses, typically contains requested media buys.'
        ),
    ]
    errors: Annotated[
        list[error.Error] | None,
        Field(
            description='Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)'
        ),
    ] = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var aggregated_totalsAggregatedTotals | None
var attribution_windowAttributionWindow | None
var contextContextObject | None
var currency : str
var errors : list[Error] | None
var extExtensionObject | None
var media_buy_deliveries : list[MediaBuyDelivery]
var model_config
var next_expected_at : pydantic.types.AwareDatetime | None
var notification_typeNotificationType | None
var partial_data : bool | None
var reporting_periodReportingPeriod
var sandbox : bool | None
var sequence_number : int | None
var unavailable_count : int | None

Inherited members

class GetMediaBuysRequest (**data: Any)
Expand source code
class GetMediaBuysRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account to retrieve media buys for. When omitted, returns data across all accessible accounts.'
        ),
    ] = None
    media_buy_ids: Annotated[
        list[str] | None,
        Field(
            description='Array of media buy IDs to retrieve. When omitted, returns a paginated set of accessible media buys matching status_filter.',
            min_length=1,
        ),
    ] = None
    status_filter: Annotated[
        media_buy_status.MediaBuyStatus | StatusFilter | None,
        Field(
            description='Filter by status. Can be a single status or array of statuses. Defaults to ["active"] when media_buy_ids is omitted. When media_buy_ids is provided, no implicit status filter is applied.'
        ),
    ] = None
    include_snapshot: Annotated[
        bool | None,
        Field(
            description='When true, include a near-real-time delivery snapshot for each package. Snapshots reflect the latest available entity-level stats from the platform (e.g., updated every ~15 minutes on GAM, ~1 hour on batch-only platforms). The staleness_seconds field on each snapshot indicates data freshness. If a snapshot cannot be returned, package.snapshot_unavailable_reason explains why. Defaults to false.'
        ),
    ] = False
    include_history: Annotated[
        int | None,
        Field(
            description='When present, include the last N revision history entries for each media buy (returns min(N, available entries)). Each entry contains revision number, timestamp, actor, and a summary of what changed. Omit or set to 0 to exclude history (default). Recommended: 5-10 for monitoring, 50+ for audit.',
            ge=0,
            le=1000,
        ),
    ] = 0
    pagination: Annotated[
        pagination_request.PaginationRequest | None,
        Field(
            description='Cursor-based pagination controls. Strongly recommended when querying broad scopes (for example, all active media buys in an account).'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var include_history : int | None
var include_snapshot : bool | None
var media_buy_ids : list[str] | None
var model_config
var paginationPaginationRequest | None
var status_filterMediaBuyStatus | StatusFilter | None

Inherited members

class GetMediaBuysResponse (**data: Any)
Expand source code
class GetMediaBuysResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    media_buys: Annotated[
        list[MediaBuy],
        Field(
            description='Array of media buys with status, creative approval state, and optional delivery snapshots'
        ),
    ]
    errors: Annotated[
        list[error.Error] | None,
        Field(description='Task-specific errors (e.g., media buy not found)'),
    ] = None
    pagination: Annotated[
        pagination_response.PaginationResponse | None,
        Field(description='Pagination metadata for the media_buys array.'),
    ] = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error] | None
var extExtensionObject | None
var media_buys : list[MediaBuy]
var model_config
var paginationPaginationResponse | None
var sandbox : bool | None

Inherited members

class GetPlanAuditLogsRequest (**data: Any)
Expand source code
class GetPlanAuditLogsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    plan_ids: Annotated[
        list[str] | None,
        Field(
            description='Plan IDs to retrieve. For a single plan, pass a one-element array.',
            min_length=1,
        ),
    ] = None
    portfolio_plan_ids: Annotated[
        list[str] | None,
        Field(
            description='Portfolio plan IDs. The governance agent expands each to its member_plan_ids and returns combined audit data.',
            min_length=1,
        ),
    ] = None
    governance_contexts: Annotated[
        list[str] | None,
        Field(
            description='Filter audit entries by governance context. Returns only checks and outcomes that share these governance contexts, enabling lifecycle tracing across purchase types.',
            min_length=1,
        ),
    ] = None
    purchase_types: Annotated[
        list[purchase_type.PurchaseType] | None,
        Field(
            description="Filter audit entries by purchase type. Returns only checks and outcomes matching these purchase types (e.g., ['rights_license'] to see all rights activity).",
            min_length=1,
        ),
    ] = None
    include_entries: Annotated[
        bool | None, Field(description='Include the full audit trail. Default: false.')
    ] = False
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var governance_contexts : list[str] | None
var include_entries : bool | None
var model_config
var plan_ids : list[str] | None
var portfolio_plan_ids : list[str] | None
var purchase_types : list[PurchaseType] | None

Inherited members

class GetPlanAuditLogsResponse (**data: Any)
Expand source code
class GetPlanAuditLogsResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    plans: Annotated[list[Plan], Field(description='Audit data for each requested plan.')]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var plans : list[Plan]

Inherited members

class GetProductsRequest (**data: Any)
Expand source code
class GetProductsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    buying_mode: Annotated[
        BuyingMode,
        Field(
            description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'."
        ),
    ]
    brief: Annotated[
        str | None,
        Field(
            description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'."
        ),
    ] = None
    refine: Annotated[
        list[Refine | Refine1 | Refine2] | None,
        Field(
            description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.",
            min_length=1,
        ),
    ] = None
    brand: Annotated[
        brand_ref.BrandReference | None,
        Field(
            description='Brand reference for product discovery context. Resolved to full brand identity at execution time.'
        ),
    ] = None
    catalog: Annotated[
        catalog_1.Catalog | None,
        Field(
            description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.'
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description="Account for product lookup. Returns products with pricing specific to this account's rate card."
        ),
    ] = None
    preferred_delivery_types: Annotated[
        list[delivery_type_1.DeliveryType] | None,
        Field(
            description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.',
            min_length=1,
        ),
    ] = None
    filters: product_filters.ProductFilters | None = None
    property_list: Annotated[
        property_list_ref.PropertyListReference | None,
        Field(
            description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.'
        ),
    ] = None
    fields: Annotated[
        list[Field1] | None,
        Field(
            description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.',
            min_length=1,
        ),
    ] = None
    time_budget: Annotated[
        duration.Duration | None,
        Field(
            description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.'
        ),
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    context: context_1.ContextObject | None = None
    required_policies: Annotated[
        list[str] | None,
        Field(
            description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var brandBrandReference | None
var brief : str | None
var buying_modeBuyingMode | None
var catalogCatalog | None
var contextContextObject | None
var extExtensionObject | None
var fields : list[Field1] | None
var filtersProductFilters | None
var model_config
var paginationPaginationRequest | None
var preferred_delivery_types : list[DeliveryType] | None
var property_listPropertyListReference | None
var refine : list[Refine | Refine1 | Refine2] | None
var required_policies : list[str] | None
var time_budgetDuration | None
class GetProductsBriefRequest (**data: Any)
Expand source code
class GetProductsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    buying_mode: Annotated[
        BuyingMode,
        Field(
            description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'."
        ),
    ]
    brief: Annotated[
        str | None,
        Field(
            description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'."
        ),
    ] = None
    refine: Annotated[
        list[Refine | Refine1 | Refine2] | None,
        Field(
            description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.",
            min_length=1,
        ),
    ] = None
    brand: Annotated[
        brand_ref.BrandReference | None,
        Field(
            description='Brand reference for product discovery context. Resolved to full brand identity at execution time.'
        ),
    ] = None
    catalog: Annotated[
        catalog_1.Catalog | None,
        Field(
            description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.'
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description="Account for product lookup. Returns products with pricing specific to this account's rate card."
        ),
    ] = None
    preferred_delivery_types: Annotated[
        list[delivery_type_1.DeliveryType] | None,
        Field(
            description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.',
            min_length=1,
        ),
    ] = None
    filters: product_filters.ProductFilters | None = None
    property_list: Annotated[
        property_list_ref.PropertyListReference | None,
        Field(
            description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.'
        ),
    ] = None
    fields: Annotated[
        list[Field1] | None,
        Field(
            description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.',
            min_length=1,
        ),
    ] = None
    time_budget: Annotated[
        duration.Duration | None,
        Field(
            description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.'
        ),
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    context: context_1.ContextObject | None = None
    required_policies: Annotated[
        list[str] | None,
        Field(
            description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var brandBrandReference | None
var brief : str | None
var buying_modeBuyingMode | None
var catalogCatalog | None
var contextContextObject | None
var extExtensionObject | None
var fields : list[Field1] | None
var filtersProductFilters | None
var model_config
var paginationPaginationRequest | None
var preferred_delivery_types : list[DeliveryType] | None
var property_listPropertyListReference | None
var refine : list[Refine | Refine1 | Refine2] | None
var required_policies : list[str] | None
var time_budgetDuration | None
class GetProductsRefineRequest (**data: Any)
Expand source code
class GetProductsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    buying_mode: Annotated[
        BuyingMode,
        Field(
            description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'."
        ),
    ]
    brief: Annotated[
        str | None,
        Field(
            description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'."
        ),
    ] = None
    refine: Annotated[
        list[Refine | Refine1 | Refine2] | None,
        Field(
            description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.",
            min_length=1,
        ),
    ] = None
    brand: Annotated[
        brand_ref.BrandReference | None,
        Field(
            description='Brand reference for product discovery context. Resolved to full brand identity at execution time.'
        ),
    ] = None
    catalog: Annotated[
        catalog_1.Catalog | None,
        Field(
            description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.'
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description="Account for product lookup. Returns products with pricing specific to this account's rate card."
        ),
    ] = None
    preferred_delivery_types: Annotated[
        list[delivery_type_1.DeliveryType] | None,
        Field(
            description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.',
            min_length=1,
        ),
    ] = None
    filters: product_filters.ProductFilters | None = None
    property_list: Annotated[
        property_list_ref.PropertyListReference | None,
        Field(
            description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.'
        ),
    ] = None
    fields: Annotated[
        list[Field1] | None,
        Field(
            description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.',
            min_length=1,
        ),
    ] = None
    time_budget: Annotated[
        duration.Duration | None,
        Field(
            description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.'
        ),
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    context: context_1.ContextObject | None = None
    required_policies: Annotated[
        list[str] | None,
        Field(
            description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var brandBrandReference | None
var brief : str | None
var buying_modeBuyingMode | None
var catalogCatalog | None
var contextContextObject | None
var extExtensionObject | None
var fields : list[Field1] | None
var filtersProductFilters | None
var model_config
var paginationPaginationRequest | None
var preferred_delivery_types : list[DeliveryType] | None
var property_listPropertyListReference | None
var refine : list[Refine | Refine1 | Refine2] | None
var required_policies : list[str] | None
var time_budgetDuration | None
class GetProductsWholesaleRequest (**data: Any)
Expand source code
class GetProductsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    buying_mode: Annotated[
        BuyingMode,
        Field(
            description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'."
        ),
    ]
    brief: Annotated[
        str | None,
        Field(
            description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'."
        ),
    ] = None
    refine: Annotated[
        list[Refine | Refine1 | Refine2] | None,
        Field(
            description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.",
            min_length=1,
        ),
    ] = None
    brand: Annotated[
        brand_ref.BrandReference | None,
        Field(
            description='Brand reference for product discovery context. Resolved to full brand identity at execution time.'
        ),
    ] = None
    catalog: Annotated[
        catalog_1.Catalog | None,
        Field(
            description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.'
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description="Account for product lookup. Returns products with pricing specific to this account's rate card."
        ),
    ] = None
    preferred_delivery_types: Annotated[
        list[delivery_type_1.DeliveryType] | None,
        Field(
            description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.',
            min_length=1,
        ),
    ] = None
    filters: product_filters.ProductFilters | None = None
    property_list: Annotated[
        property_list_ref.PropertyListReference | None,
        Field(
            description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.'
        ),
    ] = None
    fields: Annotated[
        list[Field1] | None,
        Field(
            description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.',
            min_length=1,
        ),
    ] = None
    time_budget: Annotated[
        duration.Duration | None,
        Field(
            description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.'
        ),
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    context: context_1.ContextObject | None = None
    required_policies: Annotated[
        list[str] | None,
        Field(
            description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var brandBrandReference | None
var brief : str | None
var buying_modeBuyingMode | None
var catalogCatalog | None
var contextContextObject | None
var extExtensionObject | None
var fields : list[Field1] | None
var filtersProductFilters | None
var model_config
var paginationPaginationRequest | None
var preferred_delivery_types : list[DeliveryType] | None
var property_listPropertyListReference | None
var refine : list[Refine | Refine1 | Refine2] | None
var required_policies : list[str] | None
var time_budgetDuration | None

Inherited members

class GetProductsResponse (**data: Any)
Expand source code
class GetProductsResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    products: Annotated[list[product_1.Product], Field(description='Array of matching products')]
    proposals: Annotated[
        list[proposal_1.Proposal] | None,
        Field(
            description='Optional array of proposed media plans with budget allocations across products. Publishers include proposals when they can provide strategic guidance based on the brief. Proposals are actionable - buyers can refine them via follow-up get_products calls within the same session, or execute them directly via create_media_buy.'
        ),
    ] = None
    errors: Annotated[
        list[error.Error] | None,
        Field(description='Task-specific errors and warnings (e.g., product filtering issues)'),
    ] = None
    property_list_applied: Annotated[
        bool | None,
        Field(
            description='[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent.'
        ),
    ] = None
    catalog_applied: Annotated[
        bool | None,
        Field(
            description='Whether the seller filtered results based on the provided catalog. True if the seller matched catalog items against its inventory. Absent or false if no catalog was provided or the seller does not support catalog matching.'
        ),
    ] = None
    refinement_applied: Annotated[
        list[RefinementAppliedItem] | None,
        Field(
            description="Seller's response to each change request in the refine array, matched by position. Each entry acknowledges whether the corresponding ask was applied, partially applied, or unable to be fulfilled. MUST contain the same number of entries in the same order as the request's refine array. Only present when the request used buying_mode: 'refine'."
        ),
    ] = None
    incomplete: Annotated[
        list[IncompleteItem] | None,
        Field(
            description="Declares what the seller could not finish within the buyer's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.",
            min_length=1,
        ),
    ] = None
    pagination: pagination_response.PaginationResponse | None = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var catalog_applied : bool | None
var contextContextObject | None
var errors : list[Error] | None
var extExtensionObject | None
var incomplete : list[IncompleteItem] | None
var model_config
var paginationPaginationResponse | None
var products : list[Product]
var property_list_applied : bool | None
var proposals : list[Proposal] | None
var refinement_applied : list[RefinementAppliedItem] | None
var sandbox : bool | None

Inherited members

class GetRightsRequest (**data: Any)
Expand source code
class GetRightsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    query: Annotated[
        str,
        Field(
            description='Natural language description of desired rights. The agent interprets intent, budget signals, and compatibility from this text.',
            max_length=2000,
        ),
    ]
    uses: Annotated[
        list[right_use.RightUse],
        Field(
            description='Rights uses being requested. The agent returns options covering these uses, potentially bundled into composite pricing.',
            min_length=1,
        ),
    ]
    buyer_brand: Annotated[
        brand_ref.BrandReference | None,
        Field(
            description="The buyer's brand. The agent fetches the buyer's brand.json for compatibility filtering (e.g., dietary conflicts, competitor exclusions)."
        ),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description='Countries where rights are needed (ISO 3166-1 alpha-2). Filters to rights available in these markets.'
        ),
    ] = None
    brand_id: Annotated[
        str | None,
        Field(
            description="Search within a specific brand's rights. If omitted, searches across the agent's full roster."
        ),
    ] = None
    right_type: Annotated[
        right_type_1.RightType | None,
        Field(description='Filter by type of rights (talent, music, stock_media, etc.)'),
    ] = None
    include_excluded: Annotated[
        bool | None,
        Field(
            description='Include filtered-out results in the excluded array with reasons. Defaults to false.'
        ),
    ] = False
    pagination: Annotated[
        pagination_request.PaginationRequest | None,
        Field(description='Pagination parameters for large result sets'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var brand_id : str | None
var buyer_brandBrandReference | None
var contextContextObject | None
var countries : list[Country] | None
var extExtensionObject | None
var include_excluded : bool | None
var model_config
var paginationPaginationRequest | None
var query : str
var right_typeRightType | None
var uses : list[RightUse]

Inherited members

class GetRightsSuccessResponse (**data: Any)
Expand source code
class GetRightsResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    rights: Annotated[
        list[Right], Field(description='Matching rights with pricing options, ranked by relevance')
    ]
    excluded: Annotated[
        list[ExcludedItem] | None,
        Field(description='Results that matched but were filtered out, with reasons'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var excluded : list[ExcludedItem] | None
var extExtensionObject | None
var model_config
var rights : list[Right]

Inherited members

class GetRightsErrorResponse (**data: Any)
Expand source code
class GetRightsResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[list[error.Error], Field(min_length=1)]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class GetSignalsRequest (**data: Any)
Expand source code
class GetSignalsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for this request. When provided, the signals agent returns per-account pricing options if configured.'
        ),
    ] = None
    signal_spec: Annotated[
        str | None,
        Field(
            description='Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_ids, provides context for the agent but signal_ids matches are returned first.'
        ),
    ] = None
    signal_ids: Annotated[
        list[signal_id.SignalId] | None,
        Field(
            description="Specific signals to look up by data provider and ID. Returns exact matches from the data provider's catalog. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments.",
            min_length=1,
        ),
    ] = None
    destinations: Annotated[
        list[destination.Destination] | None,
        Field(
            description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.',
            min_length=1,
        ),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.',
            min_length=1,
        ),
    ] = None
    filters: signal_filters.SignalFilters | None = None
    max_results: Annotated[
        int | None, Field(description='Maximum number of results to return', ge=1)
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var countries : list[Country] | None
var destinations : list[Destination] | None
var extExtensionObject | None
var filtersSignalFilters | None
var max_results : int | None
var model_config
var paginationPaginationRequest | None
var signal_ids : list[SignalId] | None
var signal_spec : str | None
class GetSignalsDiscoveryRequest (**data: Any)
Expand source code
class GetSignalsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for this request. When provided, the signals agent returns per-account pricing options if configured.'
        ),
    ] = None
    signal_spec: Annotated[
        str | None,
        Field(
            description='Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_ids, provides context for the agent but signal_ids matches are returned first.'
        ),
    ] = None
    signal_ids: Annotated[
        list[signal_id.SignalId] | None,
        Field(
            description="Specific signals to look up by data provider and ID. Returns exact matches from the data provider's catalog. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments.",
            min_length=1,
        ),
    ] = None
    destinations: Annotated[
        list[destination.Destination] | None,
        Field(
            description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.',
            min_length=1,
        ),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.',
            min_length=1,
        ),
    ] = None
    filters: signal_filters.SignalFilters | None = None
    max_results: Annotated[
        int | None, Field(description='Maximum number of results to return', ge=1)
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var countries : list[Country] | None
var destinations : list[Destination] | None
var extExtensionObject | None
var filtersSignalFilters | None
var max_results : int | None
var model_config
var paginationPaginationRequest | None
var signal_ids : list[SignalId] | None
var signal_spec : str | None
class GetSignalsLookupRequest (**data: Any)
Expand source code
class GetSignalsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description='Account for this request. When provided, the signals agent returns per-account pricing options if configured.'
        ),
    ] = None
    signal_spec: Annotated[
        str | None,
        Field(
            description='Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_ids, provides context for the agent but signal_ids matches are returned first.'
        ),
    ] = None
    signal_ids: Annotated[
        list[signal_id.SignalId] | None,
        Field(
            description="Specific signals to look up by data provider and ID. Returns exact matches from the data provider's catalog. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments.",
            min_length=1,
        ),
    ] = None
    destinations: Annotated[
        list[destination.Destination] | None,
        Field(
            description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.',
            min_length=1,
        ),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.',
            min_length=1,
        ),
    ] = None
    filters: signal_filters.SignalFilters | None = None
    max_results: Annotated[
        int | None, Field(description='Maximum number of results to return', ge=1)
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var countries : list[Country] | None
var destinations : list[Destination] | None
var extExtensionObject | None
var filtersSignalFilters | None
var max_results : int | None
var model_config
var paginationPaginationRequest | None
var signal_ids : list[SignalId] | None
var signal_spec : str | None

Inherited members

class GetSignalsResponse (**data: Any)
Expand source code
class GetSignalsResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    signals: Annotated[list[Signal], Field(description='Array of matching signals')]
    errors: Annotated[
        list[error.Error] | None,
        Field(
            description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)'
        ),
    ] = None
    pagination: pagination_response.PaginationResponse | None = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error] | None
var extExtensionObject | None
var model_config
var paginationPaginationResponse | None
var sandbox : bool | None
var signals : list[Signal]

Inherited members

class Gtin (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class Gtin(RootModel[str]):
    root: Annotated[str, Field(pattern='^[0-9]{8,14}$')]

Usage Documentation

RootModel and Custom Root Types

A Pydantic BaseModel for the root object of the model.

Attributes

root
The root object of the model.
__pydantic_root_model__
Whether the model is a RootModel.
__pydantic_private__
Private fields in the model.
__pydantic_extra__
Extra fields in the model.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.root_model.RootModel[str]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var root : str
class IdentityMatchRequest (**data: Any)
Expand source code
class IdentityMatchRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    field_schema: Annotated[
        AnyUrl | None,
        Field(
            alias='$schema', description='Optional schema URI for validation. Ignored at runtime.'
        ),
    ] = None
    type: Annotated[
        Literal['identity_match_request'],
        Field(description='Message type discriminator for deserialization.'),
    ]
    protocol_version: Annotated[
        str | None,
        Field(
            description='TMP protocol version. Allows receivers to handle semantic differences across versions.'
        ),
    ] = '1.0'
    request_id: Annotated[
        str,
        Field(
            description='Unique request identifier. MUST NOT correlate with any context match request_id.'
        ),
    ]
    identities: Annotated[
        list[Identity],
        Field(
            description='Identity tokens for the user, each tagged with its type. Publishers SHOULD include every token they have available — the buyer resolves on whichever graph matches. Entry order is not semantically significant; buyers use their own preference order when multiple entries resolve. Duplicate `(uid_type, user_token)` pairs MUST NOT appear; routers MAY reject or dedupe. `maxItems: 3` matches the TMPX plaintext budget (~120 bytes after HPKE overhead fits three 32-byte tokens); exceeding it forces buyer-side truncation.',
            max_length=3,
            min_length=1,
        ),
    ]
    consent: Annotated[
        Consent | None,
        Field(
            description='Privacy consent signals. Buyers in regulated jurisdictions MUST NOT process the user token without consent information.'
        ),
    ] = None
    package_ids: Annotated[
        list[str],
        Field(
            description='ALL active package identifiers for this buyer at this publisher. MUST include every active package, not just those on the current page, to prevent correlation with Context Match requests.',
            min_length=1,
        ),
    ]
    country: Annotated[
        str | None,
        Field(
            description='ISO 3166-1 alpha-2 country code. Routing directive for the TMP Router — used to select the correct regional provider. The router MUST strip this field before forwarding the request to the buyer agent. Not an identity signal.',
            pattern='^[A-Z]{2}$',
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var consentConsent | None
var country : str | None
var field_schema : pydantic.networks.AnyUrl | None
var identities : list[Identity]
var model_config
var package_ids : list[str]
var protocol_version : str | None
var request_id : str
var type : Literal['identity_match_request']

Inherited members

class IdentityMatchResponse (**data: Any)
Expand source code
class IdentityMatchResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    type: Annotated[
        Literal['identity_match_response'],
        Field(description='Message type discriminator for deserialization.'),
    ]
    request_id: Annotated[
        str, Field(description='Echoed request identifier from the identity match request')
    ]
    eligible_package_ids: Annotated[
        list[str],
        Field(
            description='Package IDs the user is eligible for. Packages not listed are ineligible.'
        ),
    ]
    ttl_sec: Annotated[
        int,
        Field(
            description='How long the router should cache this response, in seconds. The router returns cached eligibility without re-querying the buyer during this window. A value of 0 means do not cache.',
            ge=0,
            le=86400,
        ),
    ]
    tmpx: Annotated[
        str | None,
        Field(
            description="HPKE-encrypted exposure token containing the resolved user identity tokens. The publisher substitutes this into creative tracking URLs as {TMPX}. The buyer's impression pixel receives the token at serve time, enabling real-time per-user frequency state updates. Wire format: kid.base64url_nopad(ciphertext) — unpadded base64url per RFC 4648 section 5 (no = characters). Publishers MUST treat this value as opaque pass-through data."
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var eligible_package_ids : list[str]
var model_config
var request_id : str
var tmpx : str | None
var ttl_sec : int
var type : Literal['identity_match_response']

Inherited members

class KellerType (*args, **kwds)
Expand source code
class KellerType(Enum):
    master = 'master'
    sub_brand = 'sub_brand'
    endorsed = 'endorsed'
    independent = 'independent'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var endorsed
var independent
var master
var sub_brand
class LegacyHmacFallback (options_for: Callable[[Mapping[str, str]], LegacyWebhookHmacOptions | None],
only_when_9421_absent: bool = True)
Expand source code
@dataclass(frozen=True)
class LegacyHmacFallback:
    """Opt-in policy for accepting HMAC-SHA256 senders during 3.x migration.

    The default behavior of the receiver is to reject any request that fails
    9421 verification. Pass an instance of this class to ``WebhookReceiverConfig``
    to accept HMAC-signed webhooks as a fallback.

    :param options_for: callback that returns a populated
        :class:`LegacyWebhookHmacOptions` given the incoming request headers.
        Your implementation resolves the sender (from Bearer, hostname, or
        legacy shared-secret tag) and returns the secret + sender_identity
        tuple the verifier needs. Return ``None`` to decline the fallback
        for this request (rejection follows the 9421-only failure path).
    :param only_when_9421_absent: when ``True`` (default), HMAC fallback only
        fires when no 9421 headers are present at all. When a request carries
        9421 headers that FAIL verification, it still rejects — preventing a
        downgrade attack where a MITM strips the 9421 signature and replaces
        it with a forged HMAC one it knows the secret for. When ``False``,
        HMAC is tried on any 9421 failure; only set this for testing or known
        homogenous sender cohorts.
    """

    options_for: Callable[[Mapping[str, str]], LegacyWebhookHmacOptions | None]
    only_when_9421_absent: bool = True

    @classmethod
    def from_shared_secret(
        cls,
        *,
        secret: bytes,
        sender_identity: str,
        only_when_9421_absent: bool = True,
        window_seconds: int = 300,
    ) -> LegacyHmacFallback:
        """Convenience constructor for the "one secret, one sender" case.

        Covers the common 3.x migration setup where the receiver has exactly
        one publisher on the legacy scheme and binds them to a known
        ``sender_identity`` (typically a buyer-defined string). For multi-
        sender or header-derived-identity setups, construct with an
        ``options_for`` callback directly.
        """
        import time as _time

        def _options_for(_headers: Mapping[str, str]) -> LegacyWebhookHmacOptions:
            return LegacyWebhookHmacOptions(
                secret=secret,
                sender_identity=sender_identity,
                now=_time.time(),
                window_seconds=window_seconds,
            )

        return cls(
            options_for=_options_for,
            only_when_9421_absent=only_when_9421_absent,
        )

Opt-in policy for accepting HMAC-SHA256 senders during 3.x migration.

The default behavior of the receiver is to reject any request that fails 9421 verification. Pass an instance of this class to WebhookReceiverConfig to accept HMAC-signed webhooks as a fallback.

:param options_for: callback that returns a populated :class:LegacyWebhookHmacOptions given the incoming request headers. Your implementation resolves the sender (from Bearer, hostname, or legacy shared-secret tag) and returns the secret + sender_identity tuple the verifier needs. Return None to decline the fallback for this request (rejection follows the 9421-only failure path). :param only_when_9421_absent: when True (default), HMAC fallback only fires when no 9421 headers are present at all. When a request carries 9421 headers that FAIL verification, it still rejects — preventing a downgrade attack where a MITM strips the 9421 signature and replaces it with a forged HMAC one it knows the secret for. When False, HMAC is tried on any 9421 failure; only set this for testing or known homogenous sender cohorts.

Static methods

def from_shared_secret(*,
secret: bytes,
sender_identity: str,
only_when_9421_absent: bool = True,
window_seconds: int = 300) ‑> LegacyHmacFallback

Convenience constructor for the "one secret, one sender" case.

Covers the common 3.x migration setup where the receiver has exactly one publisher on the legacy scheme and binds them to a known sender_identity (typically a buyer-defined string). For multi- sender or header-derived-identity setups, construct with an options_for callback directly.

Instance variables

var only_when_9421_absent : bool
var options_for : Callable[[Mapping[str, str]], LegacyWebhookHmacOptions | None]
class ListAccountsRequest (**data: Any)
Expand source code
class ListAccountsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    status: Annotated[
        Status | None,
        Field(description='Filter accounts by status. Omit to return accounts in all statuses.'),
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    sandbox: Annotated[
        bool | None,
        Field(
            description='Filter by sandbox status. true returns only sandbox accounts, false returns only production accounts. Omit to return all accounts. Primarily used with explicit accounts (require_operator_auth: true) where sandbox accounts are pre-existing test accounts on the platform.'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var model_config
var paginationPaginationRequest | None
var sandbox : bool | None
var statusStatus | None

Inherited members

class ListAccountsResponse (**data: Any)
Expand source code
class ListAccountsResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    accounts: Annotated[
        list[account.Account],
        Field(description='Array of accounts accessible to the authenticated agent'),
    ]
    errors: Annotated[
        list[error.Error] | None, Field(description='Task-specific errors and warnings')
    ] = None
    pagination: pagination_response.PaginationResponse | None = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accounts : list[Account]
var contextContextObject | None
var errors : list[Error] | None
var extExtensionObject | None
var model_config
var paginationPaginationResponse | None

Inherited members

class ListContentStandardsSuccessResponse (**data: Any)
Expand source code
class ListContentStandardsResponse1(AdCPBaseModel):
    standards: Annotated[
        list[content_standards.ContentStandards],
        Field(description='Array of content standards configurations matching the filter criteria'),
    ]
    pagination: pagination_response.PaginationResponse | None = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var paginationPaginationResponse | None
var standards : list[ContentStandards]

Inherited members

class ListContentStandardsErrorResponse (**data: Any)
Expand source code
class ListContentStandardsResponse2(AdCPBaseModel):
    errors: list[error.Error]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class ListCreativeFormatsRequest (**data: Any)
Expand source code
class ListCreativeFormatsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description='Return only these specific format IDs (e.g., from get_products response)',
            min_length=1,
        ),
    ] = None
    asset_types: Annotated[
        list[asset_content_type.AssetContentType] | None,
        Field(
            description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.",
            min_length=1,
        ),
    ] = None
    max_width: Annotated[
        int | None,
        Field(
            description='Maximum width in pixels (inclusive). Returns formats where ANY render has width <= this value. For multi-render formats, matches if at least one render fits.'
        ),
    ] = None
    max_height: Annotated[
        int | None,
        Field(
            description='Maximum height in pixels (inclusive). Returns formats where ANY render has height <= this value. For multi-render formats, matches if at least one render fits.'
        ),
    ] = None
    min_width: Annotated[
        int | None,
        Field(
            description='Minimum width in pixels (inclusive). Returns formats where ANY render has width >= this value.'
        ),
    ] = None
    min_height: Annotated[
        int | None,
        Field(
            description='Minimum height in pixels (inclusive). Returns formats where ANY render has height >= this value.'
        ),
    ] = None
    is_responsive: Annotated[
        bool | None,
        Field(
            description='Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.'
        ),
    ] = None
    name_search: Annotated[
        str | None, Field(description='Search for formats by name (case-insensitive partial match)')
    ] = None
    wcag_level: Annotated[
        wcag_level_1.WcagLevel | None,
        Field(
            description='Filter to formats that meet at least this WCAG conformance level (A < AA < AAA)'
        ),
    ] = None
    disclosure_positions: Annotated[
        list[disclosure_position.DisclosurePosition] | None,
        Field(
            description="Filter to formats that support all of these disclosure positions. When a format has disclosure_capabilities, match against those positions. Otherwise fall back to supported_disclosure_positions. Use to find formats compatible with a brief's compliance requirements.",
            min_length=1,
        ),
    ] = None
    disclosure_persistence: Annotated[
        list[disclosure_persistence_1.DisclosurePersistence] | None,
        Field(
            description='Filter to formats where each requested persistence mode is supported by at least one position in disclosure_capabilities. Different positions may satisfy different modes. Use to find formats compatible with jurisdiction-specific persistence requirements (e.g., continuous for EU AI Act).',
            min_length=1,
        ),
    ] = None
    output_format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description="Filter to formats whose output_format_ids includes any of these format IDs. Returns formats that can produce these outputs — inspect each result's input_format_ids to see what inputs they accept.",
            min_length=1,
        ),
    ] = None
    input_format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description="Filter to formats whose input_format_ids includes any of these format IDs. Returns formats that accept these creatives as input — inspect each result's output_format_ids to see what they can produce.",
            min_length=1,
        ),
    ] = None
    pagination: pagination_request.PaginationRequest | None = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var asset_types : list[AssetContentType] | None
var contextContextObject | None
var disclosure_persistence : list[DisclosurePersistence] | None
var disclosure_positions : list[DisclosurePosition] | None
var extExtensionObject | None
var format_ids : list[FormatId] | None
var input_format_ids : list[FormatId] | None
var is_responsive : bool | None
var max_height : int | None
var max_width : int | None
var min_height : int | None
var min_width : int | None
var model_config
var output_format_ids : list[FormatId] | None
var paginationPaginationRequest | None
var wcag_levelWcagLevel | None

Inherited members

class ListCreativeFormatsResponse (**data: Any)
Expand source code
class ListCreativeFormatsResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    formats: Annotated[
        list[format.Format],
        Field(
            description="Full format definitions for all formats this agent supports. Each format's authoritative source is indicated by its agent_url field."
        ),
    ]
    creative_agents: Annotated[
        list[CreativeAgent] | None,
        Field(
            description='Optional: Creative agents that provide additional formats. Buyers can recursively query these agents to discover more formats. No authentication required for list_creative_formats.'
        ),
    ] = None
    errors: Annotated[
        list[error.Error] | None,
        Field(description='Task-specific errors and warnings (e.g., format availability issues)'),
    ] = None
    pagination: pagination_response.PaginationResponse | None = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var creative_agents : list[CreativeAgent] | None
var errors : list[Error] | None
var extExtensionObject | None
var formats : list[Format]
var model_config
var paginationPaginationResponse | None
var sandbox : bool | None

Inherited members

class ListCreativesRequest (**data: Any)
Expand source code
class ListCreativesRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    filters: creative_filters.CreativeFilters | None = None
    sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None
    pagination: pagination_request.PaginationRequest | None = None
    include_assignments: Annotated[
        bool | None, Field(description='Include package assignment information in response')
    ] = True
    include_snapshot: Annotated[
        bool | None,
        Field(
            description='Include a lightweight delivery snapshot per creative (lifetime impressions and last-served date). For detailed performance analytics, use get_creative_delivery.'
        ),
    ] = False
    include_items: Annotated[
        bool | None,
        Field(description='Include items for multi-asset formats like carousels and native ads'),
    ] = False
    include_variables: Annotated[
        bool | None,
        Field(
            description='Include dynamic content variable definitions (DCO slots) for each creative'
        ),
    ] = False
    include_pricing: Annotated[
        bool | None,
        Field(
            description='Include pricing_options on each creative. Requires account to be provided. When false or omitted, pricing is not computed.'
        ),
    ] = False
    account: Annotated[
        account_ref.AccountReference | None,
        Field(
            description="Account reference for pricing and access. When provided with include_pricing, the agent returns pricing_options from this account's rate card on each creative."
        ),
    ] = None
    fields: Annotated[
        list[Field1] | None,
        Field(
            description="Specific fields to include in response (omit for all fields). The 'concept' value returns both concept_id and concept_name.",
            min_length=1,
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference | None
var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var fields : list[Field1] | None
var filtersCreativeFilters | None
var include_assignments : bool | None
var include_items : bool | None
var include_pricing : bool | None
var include_snapshot : bool | None
var include_variables : bool | None
var model_config
var paginationPaginationRequest | None
var sortSort | None

Inherited members

class ListCreativesResponse (**data: Any)
Expand source code
class ListCreativesResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    query_summary: Annotated[
        QuerySummary, Field(description='Summary of the query that was executed')
    ]
    pagination: pagination_response.PaginationResponse
    creatives: Annotated[
        list[Creative], Field(description='Array of creative assets matching the query')
    ]
    format_summary: Annotated[
        dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z0-9_-]+$')], int] | None,
        Field(
            description="Breakdown of creatives by format. Keys are agent-defined format identifiers, optionally including dimensions (e.g., 'display_static_300x250', 'video_30s_vast'). Key construction is platform-specific — there is no required format."
        ),
    ] = None
    status_summary: Annotated[
        StatusSummary | None, Field(description='Breakdown of creatives by status')
    ] = None
    errors: Annotated[
        list[error.Error] | None,
        Field(description='Task-specific errors (e.g., invalid filters, account not found)'),
    ] = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var creatives : list[Creative]
var errors : list[Error] | None
var extExtensionObject | None
var format_summary : dict[str, int] | None
var model_config
var paginationPaginationResponse
var query_summaryQuerySummary
var sandbox : bool | None
var status_summaryStatusSummary | None

Inherited members

class LogEventRequest (**data: Any)
Expand source code
class LogEventRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    event_source_id: Annotated[
        str, Field(description='Event source configured on the account via sync_event_sources')
    ]
    test_event_code: Annotated[
        str | None,
        Field(
            description="Test event code for validation without affecting production data. Events with this code appear in the platform's test events UI."
        ),
    ] = None
    events: Annotated[
        list[event.Event], Field(description='Events to log', max_length=10000, min_length=1)
    ]
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for this request. Prevents duplicate event logging on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var event_source_id : str
var events : list[Event]
var extExtensionObject | None
var idempotency_key : str
var model_config
var test_event_code : str | None

Inherited members

class LogEventSuccessResponse (**data: Any)
Expand source code
class LogEventResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    events_received: Annotated[int, Field(description='Number of events received', ge=0)]
    events_processed: Annotated[
        int, Field(description='Number of events successfully queued for processing', ge=0)
    ]
    partial_failures: Annotated[
        list[PartialFailure] | None, Field(description='Events that failed validation')
    ] = None
    warnings: Annotated[
        list[str] | None,
        Field(
            description='Non-fatal issues (low match quality, missing recommended fields, deprecation notices)'
        ),
    ] = None
    match_quality: Annotated[
        float | None,
        Field(
            description='Overall match quality score for the batch (0.0 = no matches, 1.0 = all matched)',
            ge=0.0,
            le=1.0,
        ),
    ] = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var events_processed : int
var events_received : int
var extExtensionObject | None
var match_quality : float | None
var model_config
var partial_failures : list[PartialFailure] | None
var sandbox : bool | None
var warnings : list[str] | None

Inherited members

class LogEventErrorResponse (**data: Any)
Expand source code
class LogEventResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[list[error.Error], Field(description='Operation-level errors', min_length=1)]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class McpWebhookPayload (**data: Any)
Expand source code
class McpWebhookPayload(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    idempotency_key: Annotated[
        str,
        Field(
            description="Sender-generated key stable across retries of the same webhook event. Publishers MUST generate a cryptographically random value (UUID v4 recommended) per distinct event and reuse the same key on every retry of that event. Receivers MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential) — keys from different publishers are independent. This is the canonical dedup field — the (task_id, status, timestamp) tuple is insufficient when a single transition is retried with unchanged timestamp or when two transitions share a timestamp.",
            max_length=255,
            min_length=16,
            pattern="^[A-Za-z0-9_.:-]{16,255}$",
        ),
    ]
    operation_id: Annotated[
        str | None,
        Field(
            description="Client-generated identifier that was embedded in the webhook URL by the buyer. Publishers echo this back in webhook payloads so clients can correlate notifications without parsing URL paths. Typically generated as a unique ID per task invocation."
        ),
    ] = None
    task_id: Annotated[
        str,
        Field(
            description="Unique identifier for this task. Use this to correlate webhook notifications with the original task submission."
        ),
    ]
    task_type: Annotated[
        task_type_1.TaskType,
        Field(
            description="Type of AdCP operation that triggered this webhook. Enables webhook handlers to route to appropriate processing logic."
        ),
    ]
    protocol: Annotated[
        adcp_protocol.AdcpProtocol | None,
        Field(
            description="AdCP protocol this task belongs to. Helps classify the operation type at a high level."
        ),
    ] = None
    status: Annotated[
        task_status.TaskStatus,
        Field(
            description="Current task status. Webhooks are triggered for status changes after initial submission."
        ),
    ]
    timestamp: Annotated[
        AwareDatetime, Field(description="ISO 8601 timestamp when this webhook was generated.")
    ]
    message: Annotated[
        str | None,
        Field(
            description="Human-readable summary of the current task state. Provides context about what happened and what action may be needed."
        ),
    ] = None
    context_id: Annotated[
        str | None,
        Field(
            description="Session/conversation identifier. Use this to continue the conversation if input-required status needs clarification or additional parameters."
        ),
    ] = None
    result: Annotated[
        async_response_data.AdcpAsyncResponseData | None,
        Field(
            description="Task-specific payload matching the status. For completed/failed, contains the full task response. For working/input-required/submitted, contains status-specific data. This is the data layer that AdCP specs - same structure used in A2A status.message.parts[].data."
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var context_id : str | None
var idempotency_key : str
var message : str | None
var model_config
var operation_id : str | None
var protocolAdcpProtocol | None
var resultAdcpAsyncResponseData | None
var statusTaskStatus
var task_id : str
var task_typeTaskType
var timestamp : pydantic.types.AwareDatetime

Inherited members

class MediaBuy (**data: Any)
Expand source code
class MediaBuy(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    media_buy_id: Annotated[str, Field(description="Seller's unique identifier for the media buy")]
    account: Annotated[
        account_1.Account | None, Field(description='Account billed for this media buy')
    ] = None
    status: media_buy_status.MediaBuyStatus
    rejection_reason: Annotated[
        str | None,
        Field(
            description="Reason provided by the seller when status is 'rejected'. Present only when status is 'rejected'."
        ),
    ] = None
    confirmed_at: Annotated[
        AwareDatetime | None,
        Field(
            description='ISO 8601 timestamp when the seller confirmed this media buy. A successful create_media_buy response constitutes order confirmation.'
        ),
    ] = None
    cancellation: Annotated[
        Cancellation | None,
        Field(description="Cancellation metadata. Present only when status is 'canceled'."),
    ] = None
    total_budget: Annotated[float, Field(description='Total budget amount', ge=0.0)]
    packages: Annotated[
        list[package.Package], Field(description='Array of packages within this media buy')
    ]
    invoice_recipient: Annotated[
        business_entity.BusinessEntity | None,
        Field(
            description="Per-buy override for who receives the invoice. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request."
        ),
    ] = None
    creative_deadline: Annotated[
        AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline')
    ] = None
    revision: Annotated[
        int | None,
        Field(
            description='Monotonically increasing revision number. Incremented on every state change or update. Callers MAY include this in update_media_buy requests for optimistic concurrency — sellers MUST reject with CONFLICT if the provided revision does not match the current value.',
            ge=1,
        ),
    ] = None
    created_at: Annotated[AwareDatetime | None, Field(description='Creation timestamp')] = None
    updated_at: Annotated[AwareDatetime | None, Field(description='Last update timestamp')] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccount | None
var cancellationCancellation | None
var confirmed_at : pydantic.types.AwareDatetime | None
var created_at : pydantic.types.AwareDatetime | None
var creative_deadline : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var invoice_recipientBusinessEntity | None
var media_buy_id : str
var model_config
var packages : list[Package]
var rejection_reason : str | None
var revision : int | None
var statusMediaBuyStatus
var total_budget : float
var updated_at : pydantic.types.AwareDatetime | None

Inherited members

class MediaBuyStatus (*args, **kwds)
Expand source code
class MediaBuyStatus(Enum):
    pending_creatives = 'pending_creatives'
    pending_start = 'pending_start'
    active = 'active'
    paused = 'paused'
    completed = 'completed'
    rejected = 'rejected'
    canceled = 'canceled'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var active
var canceled
var completed
var paused
var pending_creatives
var pending_start
var rejected
class MediaChannel (*args, **kwds)
Expand source code
class MediaChannel(Enum):
    display = 'display'
    olv = 'olv'
    social = 'social'
    search = 'search'
    ctv = 'ctv'
    linear_tv = 'linear_tv'
    radio = 'radio'
    streaming_audio = 'streaming_audio'
    podcast = 'podcast'
    dooh = 'dooh'
    ooh = 'ooh'
    print = 'print'
    cinema = 'cinema'
    email = 'email'
    gaming = 'gaming'
    retail_media = 'retail_media'
    influencer = 'influencer'
    affiliate = 'affiliate'
    product_placement = 'product_placement'
    sponsored_intelligence = 'sponsored_intelligence'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var affiliate
var cinema
var ctv
var display
var dooh
var email
var gaming
var influencer
var linear_tv
var olv
var ooh
var podcast
var print
var product_placement
var radio
var retail_media
var search
var social
var sponsored_intelligence
var streaming_audio
class Member (**data: Any)
Expand source code
class Member(BaseModel):
    """An organization registered in the AAO member directory."""

    model_config = ConfigDict(extra="allow")

    id: str
    slug: str
    display_name: str
    description: str | None = None
    tagline: str | None = None
    logo_url: str | None = None
    logo_light_url: str | None = None
    logo_dark_url: str | None = None
    contact_email: str | None = None
    contact_website: str | None = None
    offerings: list[str] = Field(default_factory=list)
    markets: list[str] = Field(default_factory=list)
    agents: list[dict[str, Any]] = Field(default_factory=list)
    brands: list[dict[str, Any]] = Field(default_factory=list)
    is_public: bool = True
    is_founding_member: bool = False
    featured: bool = False
    si_enabled: bool = False

An organization registered in the AAO member directory.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var agents : list[dict[str, typing.Any]]
var brands : list[dict[str, typing.Any]]
var contact_email : str | None
var contact_website : str | None
var description : str | None
var display_name : str
var featured : bool
var id : str
var is_founding_member : bool
var is_public : bool
var logo_dark_url : str | None
var logo_light_url : str | None
var logo_url : str | None
var markets : list[str]
var model_config
var offerings : list[str]
var si_enabled : bool
var slug : str
var tagline : str | None
class MemoryBackend (*, clock: Callable[[], float] = <built-in function time>)
Expand source code
class MemoryBackend(IdempotencyBackend):
    """In-process dict-backed store.

    Suitable for tests, single-process reference implementations, and local
    development. **Not suitable for multi-process deployments** — each worker
    has its own cache, so a retry that lands on a different worker is treated
    as a fresh request.

    Thread safety: the backend uses an :class:`asyncio.Lock` to serialize
    mutations of the shared dict. Reads go through the lock too; for a pure
    in-process backend this is cheap and prevents torn reads across concurrent
    ``get``/``put`` interleaving.

    :param clock: Callable returning the current epoch seconds. Override for
        tests that need to advance time deterministically without monkeypatching
        :mod:`time`. Defaults to :func:`time.time`.
    """

    def __init__(self, *, clock: Callable[[], float] = time.time) -> None:
        self._store: dict[tuple[str, str], CachedResponse] = {}
        self._lock = asyncio.Lock()
        self._clock = clock

    async def get(
        self, principal_id: str, key: str
    ) -> CachedResponse | None:
        async with self._lock:
            entry = self._store.get((principal_id, key))
            if entry is None:
                return None
            if entry.expires_at_epoch <= self._clock():
                # Lazy expiry — drop the stale entry so the next request
                # treats the slot as fresh and races to repopulate.
                del self._store[(principal_id, key)]
                return None
            return entry

    async def put(
        self,
        principal_id: str,
        key: str,
        entry: CachedResponse,
    ) -> None:
        async with self._lock:
            self._store[(principal_id, key)] = entry

    async def delete_expired(self, now_epoch: float | None = None) -> int:
        cutoff = now_epoch if now_epoch is not None else self._clock()
        async with self._lock:
            stale = [k for k, v in self._store.items() if v.expires_at_epoch <= cutoff]
            for k in stale:
                del self._store[k]
            return len(stale)

    async def clear(self) -> None:
        """Remove all cached entries.

        Test-suite hook — handy for resetting state between fixtures when a
        single :class:`MemoryBackend` is shared across multiple tests.
        """
        async with self._lock:
            self._store.clear()

    async def _size(self) -> int:
        """Test-only: return the current entry count."""
        async with self._lock:
            return len(self._store)

In-process dict-backed store.

Suitable for tests, single-process reference implementations, and local development. Not suitable for multi-process deployments — each worker has its own cache, so a retry that lands on a different worker is treated as a fresh request.

Thread safety: the backend uses an :class:asyncio.Lock to serialize mutations of the shared dict. Reads go through the lock too; for a pure in-process backend this is cheap and prevents torn reads across concurrent get/put interleaving.

:param clock: Callable returning the current epoch seconds. Override for tests that need to advance time deterministically without monkeypatching :mod:time. Defaults to :func:time.time.

Ancestors

Methods

async def clear(self) ‑> None
Expand source code
async def clear(self) -> None:
    """Remove all cached entries.

    Test-suite hook — handy for resetting state between fixtures when a
    single :class:`MemoryBackend` is shared across multiple tests.
    """
    async with self._lock:
        self._store.clear()

Remove all cached entries.

Test-suite hook — handy for resetting state between fixtures when a single :class:MemoryBackend is shared across multiple tests.

Inherited members

class OfferingAssetConstraint (**data: Any)
Expand source code
class OfferingAssetConstraint(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    asset_group_id: Annotated[
        str,
        Field(
            description="The asset group this constraint applies to. Values are format-defined vocabulary — each format chooses its own group IDs (e.g., 'headlines', 'images', 'videos'). Buyers discover them via list_creative_formats."
        ),
    ]
    asset_type: Annotated[
        asset_content_type.AssetContentType,
        Field(description='The expected content type for this group.'),
    ]
    required: Annotated[
        bool | None,
        Field(
            description='Whether this asset group must be present in each offering. Defaults to true.'
        ),
    ] = True
    min_count: Annotated[
        int | None, Field(description='Minimum number of items required in this group.', ge=1)
    ] = None
    max_count: Annotated[
        int | None, Field(description='Maximum number of items allowed in this group.', ge=1)
    ] = None
    asset_requirements: Annotated[
        asset_requirements_1.AssetRequirements | None,
        Field(
            description='Technical requirements for each item in this group (e.g., max_length for text, min_width/aspect_ratio for images). Applies uniformly to all items in the group.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var asset_group_id : str
var asset_requirementsAssetRequirements | None
var asset_typeAssetContentType
var extExtensionObject | None
var max_count : int | None
var min_count : int | None
var model_config
var required : bool | None

Inherited members

class OfferingAssetGroup (**data: Any)
Expand source code
class OfferingAssetGroup(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    asset_group_id: Annotated[
        str,
        Field(
            description="Identifies the creative role this group fills. Values are defined by each format's offering_asset_constraints — not protocol constants. Discover them via list_creative_formats (e.g., a format might declare 'headlines', 'images', or 'videos')."
        ),
    ]
    asset_type: Annotated[
        asset_content_type.AssetContentType,
        Field(description='The content type of all items in this group.'),
    ]
    items: Annotated[
        list[
            text_asset.TextAsset
            | image_asset.ImageAsset
            | video_asset.VideoAsset
            | audio_asset.AudioAsset
            | url_asset.UrlAsset
            | html_asset.HtmlAsset
            | markdown_asset.MarkdownAsset
            | vast_asset.VastAsset
            | daast_asset.DaastAsset
            | css_asset.CssAsset
            | javascript_asset.JavascriptAsset
            | webhook_asset.WebhookAsset
        ],
        Field(
            description='The assets in this group. Each item should match the structure for the declared asset_type. Note: JSON Schema validation accepts any valid asset structure here; enforcement that items match asset_type is the responsibility of the consuming agent.',
            min_length=1,
        ),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var asset_group_id : str
var asset_typeAssetContentType
var extExtensionObject | None
var items : list[TextAsset | ImageAsset | VideoAsset | AudioAsset | UrlAsset | HtmlAsset | MarkdownAsset | VastAsset | DaastAsset | CssAsset | JavascriptAsset | WebhookAsset]
var model_config

Inherited members

class OptimizationGoal (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class OptimizationGoal(RootModel[OptimizationGoal1 | OptimizationGoal2]):
    root: Annotated[
        OptimizationGoal1 | OptimizationGoal2,
        Field(
            description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. Duplicate priority values result in undefined seller behavior.',
            title='Optimization Goal',
        ),
    ]

    def __getattr__(self, name: str) -> Any:
        """Proxy attribute access to the wrapped type."""
        if name.startswith('_'):
            raise AttributeError(name)
        return getattr(self.root, name)

Usage Documentation

RootModel and Custom Root Types

A Pydantic BaseModel for the root object of the model.

Attributes

root
The root object of the model.
__pydantic_root_model__
Whether the model is a RootModel.
__pydantic_private__
Private fields in the model.
__pydantic_extra__
Extra fields in the model.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.root_model.RootModel[Union[OptimizationGoal1, OptimizationGoal2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootOptimizationGoal1 | OptimizationGoal2
class Overlay (**data: Any)
Expand source code
class Overlay(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    id: Annotated[
        str,
        Field(
            description="Identifier for this overlay (e.g., 'play_pause', 'volume', 'publisher_logo', 'carousel_prev', 'carousel_next')"
        ),
    ]
    description: Annotated[
        str | None,
        Field(
            description='Human-readable explanation of what this overlay is and how buyers should account for it'
        ),
    ] = None
    visual: Annotated[
        Visual | None,
        Field(
            description='Optional visual reference for this overlay element. Useful for creative agents compositing previews and for buyers understanding what will appear over their content. Must include at least one of: url, light, or dark.'
        ),
    ] = None
    bounds: Annotated[
        Bounds,
        Field(
            description="Position and size of the overlay relative to the asset's own top-left corner. See 'unit' for coordinate interpretation."
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var boundsBounds
var description : str | None
var id : str
var model_config
var visualVisual | None

Inherited members

class MediaBuyPackage (**data: Any)
Expand source code
class Package(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    package_id: Annotated[str, Field(description="Seller's package identifier")]
    product_id: Annotated[
        str | None, Field(description='Product identifier this package is purchased from')
    ] = None
    budget: Annotated[
        float | None,
        Field(
            description='Package budget amount, denominated in package.currency when present, otherwise media_buy.currency',
            ge=0.0,
        ),
    ] = None
    currency: Annotated[
        str | None,
        Field(
            description='ISO 4217 currency code for monetary values at this package level (budget, bid_price, snapshot.spend). When absent, inherit media_buy.currency.',
            pattern='^[A-Z]{3}$',
        ),
    ] = None
    bid_price: Annotated[
        float | None,
        Field(
            description='Current bid price for auction-based packages. Denominated in package.currency when present, otherwise media_buy.currency. Relevant for automated price optimization loops.',
            ge=0.0,
        ),
    ] = None
    impressions: Annotated[
        float | None,
        Field(description='Goal impression count for impression-based packages', ge=0.0),
    ] = None
    start_time: Annotated[
        AwareDatetime | None,
        Field(
            description='ISO 8601 flight start time for this package. Use to determine whether the package is within its scheduled flight before interpreting delivery status.'
        ),
    ] = None
    end_time: Annotated[
        AwareDatetime | None, Field(description='ISO 8601 flight end time for this package')
    ] = None
    paused: Annotated[
        bool | None, Field(description='Whether this package is currently paused by the buyer')
    ] = None
    canceled: Annotated[
        bool | None,
        Field(
            description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated.'
        ),
    ] = None
    cancellation: Annotated[
        Cancellation2 | None,
        Field(description='Cancellation metadata. Present only when canceled is true.'),
    ] = None
    creative_deadline: Annotated[
        AwareDatetime | None,
        Field(
            description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies."
        ),
    ] = None
    creative_approvals: Annotated[
        list[CreativeApproval] | None,
        Field(
            description='Approval status for each creative assigned to this package. Absent when no creatives have been assigned.'
        ),
    ] = None
    format_ids_pending: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description='Format IDs from the original create_media_buy format_ids_to_provide that have not yet been uploaded via sync_creatives. When empty or absent, all required formats have been provided.'
        ),
    ] = None
    snapshot_unavailable_reason: Annotated[
        SnapshotUnavailableReason | None,
        Field(
            description='Machine-readable reason the snapshot is omitted. Present only when include_snapshot was true and snapshot is unavailable for this package.'
        ),
    ] = None
    snapshot: Annotated[
        Snapshot | None,
        Field(
            description='Near-real-time delivery snapshot for this package. Only present when include_snapshot was true in the request. Represents the latest available entity-level stats from the platform — not billing-grade data.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var bid_price : float | None
var budget : float | None
var canceled : bool | None
var cancellationCancellation2 | None
var creative_approvals : list[CreativeApproval] | None
var creative_deadline : pydantic.types.AwareDatetime | None
var currency : str | None
var end_time : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var format_ids_pending : list[FormatId] | None
var impressions : float | None
var model_config
var package_id : str
var paused : bool | None
var product_id : str | None
var snapshotSnapshot | None
var snapshot_unavailable_reasonSnapshotUnavailableReason | None
var start_time : pydantic.types.AwareDatetime | None
class Package (**data: Any)
Expand source code
class Package(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    package_id: Annotated[str, Field(description="Seller's unique identifier for the package")]
    product_id: Annotated[
        str | None, Field(description='ID of the product this package is based on')
    ] = None
    budget: Annotated[
        float | None,
        Field(
            description='Budget allocation for this package in the currency specified by the pricing option',
            ge=0.0,
        ),
    ] = None
    pacing: pacing_1.Pacing | None = None
    pricing_option_id: Annotated[
        str | None,
        Field(
            description="ID of the selected pricing option from the product's pricing_options array"
        ),
    ] = None
    bid_price: Annotated[
        float | None,
        Field(
            description="Bid price for auction-based pricing. This is the exact bid/price to honor unless the selected pricing option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).",
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description="Breakdown of the effective price for this package. On fixed-price packages, echoes the pricing option's breakdown. On auction packages, shows the clearing price breakdown including any commission or settlement terms."
        ),
    ] = None
    impressions: Annotated[
        float | None, Field(description='Impression goal for this package', ge=0.0)
    ] = None
    catalogs: Annotated[
        list[catalog.Catalog] | None,
        Field(
            description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Echoed from the create_media_buy request.'
        ),
    ] = None
    format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description='Format IDs active for this package. Echoed from the create_media_buy request; omitted means all formats for the product are active.'
        ),
    ] = None
    targeting_overlay: targeting.TargetingOverlay | None = None
    measurement_terms: Annotated[
        measurement_terms_1.MeasurementTerms | None,
        Field(
            description="Agreed billing measurement and makegood terms for this package. Reflects what was negotiated — may differ from the buyer's proposal or the product's defaults. When present, these terms are binding for the package's duration."
        ),
    ] = None
    performance_standards: Annotated[
        list[performance_standard.PerformanceStandard] | None,
        Field(
            description='Agreed performance standards for this package. When any entry specifies a vendor, creatives assigned to this package MUST include corresponding tracker_script or tracker_pixel assets from that vendor.',
            min_length=1,
        ),
    ] = None
    creative_assignments: Annotated[
        list[creative_assignment.CreativeAssignment] | None,
        Field(description='Creative assets assigned to this package'),
    ] = None
    format_ids_to_provide: Annotated[
        list[format_id.FormatId] | None,
        Field(description='Format IDs that creative assets will be provided for this package'),
    ] = None
    optimization_goals: Annotated[
        list[optimization_goal.OptimizationGoal] | None,
        Field(
            description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.',
            min_length=1,
        ),
    ] = None
    start_time: Annotated[
        AwareDatetime | None,
        Field(
            description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Sellers SHOULD always include the resolved value in responses, even when inherited."
        ),
    ] = None
    end_time: Annotated[
        AwareDatetime | None,
        Field(
            description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Sellers SHOULD always include the resolved value in responses, even when inherited."
        ),
    ] = None
    paused: Annotated[
        bool | None,
        Field(
            description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.'
        ),
    ] = False
    canceled: Annotated[
        bool | None,
        Field(
            description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.'
        ),
    ] = False
    cancellation: Annotated[
        Cancellation | None,
        Field(description='Cancellation metadata. Present only when canceled is true.'),
    ] = None
    agency_estimate_number: Annotated[
        str | None,
        Field(
            description="Agency estimate or authorization number for this package. Echoed from the buyer's request. When present on the package, takes precedence over the media buy-level estimate number.",
            max_length=100,
        ),
    ] = None
    creative_deadline: Annotated[
        AwareDatetime | None,
        Field(
            description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies."
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var agency_estimate_number : str | None
var bid_price : float | None
var budget : float | None
var canceled : bool | None
var cancellationCancellation | None
var catalogs : list[Catalog] | None
var contextContextObject | None
var creative_assignments : list[CreativeAssignment] | None
var creative_deadline : pydantic.types.AwareDatetime | None
var end_time : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var format_ids : list[FormatId] | None
var format_ids_to_provide : list[FormatId] | None
var impressions : float | None
var measurement_termsMeasurementTerms | None
var model_config
var optimization_goals : list[OptimizationGoal] | None
var pacingPacing | None
var package_id : str
var paused : bool | None
var performance_standards : list[PerformanceStandard] | None
var price_breakdownPriceBreakdown | None
var pricing_option_id : str | None
var product_id : str | None
var start_time : pydantic.types.AwareDatetime | None
var targeting_overlayTargetingOverlay | None

Inherited members

class PackageRequest (**data: Any)
Expand source code
class PackageRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    product_id: Annotated[str, Field(description='Product ID for this package')]
    format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description='Array of format IDs that will be used for this package - must be supported by the product. If omitted, defaults to all formats supported by the product.',
            min_length=1,
        ),
    ] = None
    budget: Annotated[
        float,
        Field(description="Budget allocation for this package in the media buy's currency", ge=0.0),
    ]
    pacing: pacing_1.Pacing | None = None
    pricing_option_id: Annotated[
        str,
        Field(
            description="ID of the selected pricing option from the product's pricing_options array"
        ),
    ]
    bid_price: Annotated[
        float | None,
        Field(
            description="Bid price for auction-based pricing options. This is the exact bid/price to honor unless selected pricing_option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).",
            ge=0.0,
        ),
    ] = None
    impressions: Annotated[
        float | None, Field(description='Impression goal for this package', ge=0.0)
    ] = None
    start_time: Annotated[
        AwareDatetime | None,
        Field(
            description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Must fall within the media buy's date range."
        ),
    ] = None
    end_time: Annotated[
        AwareDatetime | None,
        Field(
            description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Must fall within the media buy's date range."
        ),
    ] = None
    paused: Annotated[
        bool | None,
        Field(
            description='Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.'
        ),
    ] = False
    catalogs: Annotated[
        list[catalog.Catalog] | None,
        Field(
            description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Makes the package catalog-driven: one budget envelope, platform optimizes across items.'
        ),
    ] = None
    optimization_goals: Annotated[
        list[optimization_goal.OptimizationGoal] | None,
        Field(
            description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.',
            min_length=1,
        ),
    ] = None
    targeting_overlay: targeting.TargetingOverlay | None = None
    measurement_terms: Annotated[
        measurement_terms_1.MeasurementTerms | None,
        Field(
            description="Buyer's proposed billing measurement and makegood terms. Overrides product defaults. Seller accepts (echoed on confirmed package), rejects with TERMS_REJECTED, or adjusts. When absent, product's measurement_terms apply."
        ),
    ] = None
    performance_standards: Annotated[
        list[performance_standard.PerformanceStandard] | None,
        Field(
            description="Buyer's proposed performance standards for this package. Overrides product defaults. Seller accepts, rejects with TERMS_REJECTED, or adjusts. When absent, product's performance_standards apply.",
            min_length=1,
        ),
    ] = None
    creative_assignments: Annotated[
        list[creative_assignment.CreativeAssignment] | None,
        Field(
            description='Assign existing library creatives to this package with optional weights and placement targeting',
            min_length=1,
        ),
    ] = None
    creatives: Annotated[
        list[creative_asset.CreativeAsset] | None,
        Field(
            description='Upload new creative assets and assign to this package (creatives will be added to library). Use creative_assignments instead for existing library creatives.',
            max_length=100,
            min_length=1,
        ),
    ] = None
    agency_estimate_number: Annotated[
        str | None,
        Field(
            description='Agency estimate or authorization number for this package. Overrides the media buy-level estimate number when different packages correspond to different agency estimates (e.g., different stations or flights within the same buy).',
            max_length=100,
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var agency_estimate_number : str | None
var bid_price : float | None
var budget : float
var catalogs : list[Catalog] | None
var contextContextObject | None
var creative_assignments : list[CreativeAssignment] | None
var creatives : list[CreativeAsset] | None
var end_time : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var format_ids : list[FormatId] | None
var impressions : float | None
var measurement_termsMeasurementTerms | None
var model_config
var optimization_goals : list[OptimizationGoal] | None
var pacingPacing | None
var paused : bool | None
var performance_standards : list[PerformanceStandard] | None
var pricing_option_id : str
var product_id : str
var start_time : pydantic.types.AwareDatetime | None
var targeting_overlayTargetingOverlay | None

Inherited members

class PaginationRequest (**data: Any)
Expand source code
class PaginationRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    max_results: Annotated[
        int | None, Field(description='Maximum number of items to return per page', ge=1, le=100)
    ] = 50
    cursor: Annotated[
        str | None,
        Field(description='Opaque cursor from a previous response to fetch the next page'),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var cursor : str | None
var max_results : int | None
var model_config

Inherited members

class Policy (**data: Any)
Expand source code
class Policy(PolicySummary):
    """Full governance policy including policy text and calibration exemplars."""

    policy: str
    guidance: str | None = None
    exemplars: PolicyExemplars | None = None
    ext: dict[str, Any] | None = None

Full governance policy including policy text and calibration exemplars.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var exemplarsPolicyExemplars | None
var ext : dict[str, typing.Any] | None
var guidance : str | None
var model_config
var policy : str
class PolicyExemplar (**data: Any)
Expand source code
class PolicyExemplar(BaseModel):
    """A pass/fail scenario used to calibrate governance agent interpretation."""

    model_config = ConfigDict(extra="allow")

    scenario: str
    explanation: str

A pass/fail scenario used to calibrate governance agent interpretation.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var explanation : str
var model_config
var scenario : str
class PolicyExemplars (**data: Any)
Expand source code
class PolicyExemplars(BaseModel):
    """Collection of pass/fail exemplars for a policy."""

    model_config = ConfigDict(extra="allow")

    pass_: list[PolicyExemplar] = Field(default_factory=list, alias="pass")
    fail: list[PolicyExemplar] = Field(default_factory=list)

Collection of pass/fail exemplars for a policy.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var fail : list[PolicyExemplar]
var model_config
var pass_ : list[PolicyExemplar]
class PolicyHistory (**data: Any)
Expand source code
class PolicyHistory(BaseModel):
    """Edit history for a policy."""

    model_config = ConfigDict(extra="allow")

    policy_id: str
    total: int
    revisions: list[PolicyRevision] = Field(default_factory=list)

Edit history for a policy.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var model_config
var policy_id : str
var revisions : list[PolicyRevision]
var total : int
class PolicyRevision (**data: Any)
Expand source code
class PolicyRevision(BaseModel):
    """A single revision in a policy's edit history."""

    model_config = ConfigDict(extra="allow")

    revision_number: int
    editor_name: str
    edit_summary: str
    is_rollback: bool
    rolled_back_to: int | None = None
    created_at: str

A single revision in a policy's edit history.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var created_at : str
var edit_summary : str
var editor_name : str
var is_rollback : bool
var model_config
var revision_number : int
var rolled_back_to : int | None
class PolicySummary (**data: Any)
Expand source code
class PolicySummary(BaseModel):
    """Summary of a governance policy from the registry."""

    model_config = ConfigDict(extra="allow", populate_by_name=True)

    policy_id: str
    version: str
    name: str
    description: str | None = None
    category: str
    enforcement: str
    jurisdictions: list[str] = Field(default_factory=list)
    region_aliases: dict[str, list[str]] = Field(default_factory=dict)
    verticals: list[str] = Field(default_factory=list)
    channels: list[str] | None = None
    governance_domains: list[str] = Field(default_factory=list)
    effective_date: str | None = None
    sunset_date: str | None = None
    source_url: str | None = None
    source_name: str | None = None
    source_type: str | None = None
    review_status: str | None = None
    created_at: str | None = None
    updated_at: str | None = None

Summary of a governance policy from the registry.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Subclasses

Class variables

var category : str
var channels : list[str] | None
var created_at : str | None
var description : str | None
var effective_date : str | None
var enforcement : str
var governance_domains : list[str]
var jurisdictions : list[str]
var model_config
var name : str
var policy_id : str
var region_aliases : dict[str, list[str]]
var review_status : str | None
var source_name : str | None
var source_type : str | None
var source_url : str | None
var sunset_date : str | None
var updated_at : str | None
var version : str
var verticals : list[str]
class PreviewCreativeRequest (**data: Any)
Expand source code
class PreviewCreativeRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    request_type: Annotated[
        RequestType,
        Field(
            description="Preview mode. 'single' previews one creative manifest. 'batch' previews multiple creatives in one call. 'variant' replays a post-flight variant by ID."
        ),
    ]
    creative_manifest: Annotated[
        creative_manifest_1.CreativeManifest | None,
        Field(
            description="Complete creative manifest with all required assets for the format. Required when request_type is 'single'. Also accepted per item in batch mode."
        ),
    ] = None
    format_id: Annotated[
        format_id_1.FormatId | None,
        Field(
            description='Format identifier for rendering the preview. Defaults to creative_manifest_1.format_id if omitted. Used in single mode.'
        ),
    ] = None
    inputs: Annotated[
        list[Input] | None,
        Field(
            description='Array of input sets for generating multiple preview variants. Each input set defines macros and context values for one preview rendering. Used in single mode.',
            min_length=1,
        ),
    ] = None
    template_id: Annotated[
        str | None,
        Field(description='Specific template ID for custom format rendering. Used in single mode.'),
    ] = None
    quality: Annotated[
        creative_quality.CreativeQuality | None,
        Field(
            description="Render quality. 'draft' produces fast, lower-fidelity renderings. 'production' produces full-quality renderings. In batch mode, sets the default for all requests (individual items can override)."
        ),
    ] = None
    output_format: Annotated[
        preview_output_format.PreviewOutputFormat | None,
        Field(
            description="Output format. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). In batch mode, sets the default for all requests (individual items can override). Default: 'url'."
        ),
    ] = preview_output_format.PreviewOutputFormat.url
    item_limit: Annotated[
        int | None,
        Field(
            description='Maximum number of catalog items to render per preview variant. Used in single mode. Creative agents SHOULD default to a reasonable sample when omitted and the catalog is large.',
            ge=1,
        ),
    ] = None
    requests: Annotated[
        list[Request] | None,
        Field(
            description="Array of preview requests (1-50 items). Required when request_type is 'batch'. Each item follows the single request structure.",
            max_length=50,
            min_length=1,
        ),
    ] = None
    variant_id: Annotated[
        str | None,
        Field(
            description="Platform-assigned variant identifier from get_creative_delivery response. Required when request_type is 'variant'."
        ),
    ] = None
    creative_id: Annotated[
        str | None, Field(description='Creative identifier for context. Used in variant mode.')
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var creative_id : str | None
var creative_manifestCreativeManifest | None
var extExtensionObject | None
var format_idFormatId | None
var inputs : list[Input] | None
var item_limit : int | None
var model_config
var output_formatPreviewOutputFormat | None
var qualityCreativeQuality | None
var request_typeRequestType
var requests : list[Request] | None
var template_id : str | None
var variant_id : str | None

Inherited members

class PreviewCreativeSingleResponse (**data: Any)
Expand source code
class PreviewCreativeResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    response_type: Annotated[
        Literal['single'],
        Field(description='Discriminator indicating this is a single preview response'),
    ]
    previews: Annotated[
        list[Preview],
        Field(
            description='Array of preview variants. Each preview corresponds to an input set from the request. If no inputs were provided, returns a single default preview.',
            min_length=1,
        ),
    ]
    interactive_url: Annotated[
        AnyUrl | None,
        Field(
            description='Optional URL to an interactive testing page that shows all preview variants with controls to switch between them, modify macro values, and test different scenarios.'
        ),
    ] = None
    expires_at: Annotated[
        AwareDatetime, Field(description='ISO 8601 timestamp when preview links expire')
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var expires_at : pydantic.types.AwareDatetime
var extExtensionObject | None
var interactive_url : pydantic.networks.AnyUrl | None
var model_config
var previews : list[Preview]
var response_type : Literal['single']
class PreviewCreativeStaticResponse (**data: Any)
Expand source code
class PreviewCreativeResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    response_type: Annotated[
        Literal['single'],
        Field(description='Discriminator indicating this is a single preview response'),
    ]
    previews: Annotated[
        list[Preview],
        Field(
            description='Array of preview variants. Each preview corresponds to an input set from the request. If no inputs were provided, returns a single default preview.',
            min_length=1,
        ),
    ]
    interactive_url: Annotated[
        AnyUrl | None,
        Field(
            description='Optional URL to an interactive testing page that shows all preview variants with controls to switch between them, modify macro values, and test different scenarios.'
        ),
    ] = None
    expires_at: Annotated[
        AwareDatetime, Field(description='ISO 8601 timestamp when preview links expire')
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var expires_at : pydantic.types.AwareDatetime
var extExtensionObject | None
var interactive_url : pydantic.networks.AnyUrl | None
var model_config
var previews : list[Preview]
var response_type : Literal['single']

Inherited members

class PreviewCreativeBatchResponse (**data: Any)
Expand source code
class PreviewCreativeResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    response_type: Annotated[
        Literal['batch'],
        Field(description='Discriminator indicating this is a batch preview response'),
    ]
    results: Annotated[
        list[Results | Results1],
        Field(
            description='Array of preview results corresponding to each request in the same order. results[0] is the result for requests[0], results[1] for requests[1], etc. Order is guaranteed even when some requests fail. Each result contains either a successful preview response or an error.',
            min_length=1,
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var response_type : Literal['batch']
var results : list[Results | Results1]
class PreviewCreativeInteractiveResponse (**data: Any)
Expand source code
class PreviewCreativeResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    response_type: Annotated[
        Literal['batch'],
        Field(description='Discriminator indicating this is a batch preview response'),
    ]
    results: Annotated[
        list[Results | Results1],
        Field(
            description='Array of preview results corresponding to each request in the same order. results[0] is the result for requests[0], results[1] for requests[1], etc. Order is guaranteed even when some requests fail. Each result contains either a successful preview response or an error.',
            min_length=1,
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var response_type : Literal['batch']
var results : list[Results | Results1]

Inherited members

class PreviewCreativeVariantResponse (**data: Any)
Expand source code
class PreviewCreativeResponse3(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    response_type: Annotated[
        Literal['variant'],
        Field(description='Discriminator indicating this is a variant preview response'),
    ]
    variant_id: Annotated[str, Field(description='Platform-assigned variant identifier')]
    creative_id: Annotated[
        str | None, Field(description='Creative identifier this variant belongs to')
    ] = None
    previews: Annotated[
        list[Preview11],
        Field(
            description='Array of rendered pieces for this variant. Most formats render as a single piece.',
            min_length=1,
        ),
    ]
    manifest: Annotated[
        creative_manifest.CreativeManifest | None,
        Field(
            description='The rendered creative manifest for this variant — the actual output that was served, not the input assets'
        ),
    ] = None
    expires_at: Annotated[
        AwareDatetime | None, Field(description='ISO 8601 timestamp when preview links expire')
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var creative_id : str | None
var expires_at : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var manifestCreativeManifest | None
var model_config
var previews : list[Preview11]
var response_type : Literal['variant']
var variant_id : str

Inherited members

class UrlPreviewRender (**data: Any)
Expand source code
class PreviewRender1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    render_id: Annotated[
        str, Field(description='Unique identifier for this rendered piece within the variant')
    ]
    output_format: Annotated[
        Literal['url'], Field(description='Discriminator indicating preview_url is provided')
    ]
    preview_url: Annotated[
        AnyUrl,
        Field(
            description='URL to an HTML page that renders this piece. Can be embedded in an iframe.'
        ),
    ]
    role: Annotated[
        str,
        Field(
            description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles."
        ),
    ]
    dimensions: Annotated[
        Dimensions | None, Field(description='Dimensions for this rendered piece')
    ] = None
    embedding: Annotated[
        Embedding | None,
        Field(description='Optional security and embedding metadata for safe iframe integration'),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var dimensionsDimensions | None
var embeddingEmbedding | None
var model_config
var output_format : Literal['url']
var preview_url : pydantic.networks.AnyUrl
var render_id : str
var role : str

Inherited members

class HtmlPreviewRender (**data: Any)
Expand source code
class PreviewRender2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    render_id: Annotated[
        str, Field(description='Unique identifier for this rendered piece within the variant')
    ]
    output_format: Annotated[
        Literal['html'], Field(description='Discriminator indicating preview_html is provided')
    ]
    preview_html: Annotated[
        str,
        Field(
            description='Raw HTML for this rendered piece. Can be embedded directly in the page without iframe. Security warning: Only use with trusted creative agents as this bypasses iframe sandboxing.'
        ),
    ]
    role: Annotated[
        str,
        Field(
            description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles."
        ),
    ]
    dimensions: Annotated[
        Dimensions | None, Field(description='Dimensions for this rendered piece')
    ] = None
    embedding: Annotated[
        Embedding | None, Field(description='Optional security and embedding metadata')
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var dimensionsDimensions | None
var embeddingEmbedding | None
var model_config
var output_format : Literal['html']
var preview_html : str
var render_id : str
var role : str

Inherited members

class BothPreviewRender (**data: Any)
Expand source code
class PreviewRender3(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    render_id: Annotated[
        str, Field(description='Unique identifier for this rendered piece within the variant')
    ]
    output_format: Annotated[
        Literal['both'],
        Field(
            description='Discriminator indicating both preview_url and preview_html are provided'
        ),
    ]
    preview_url: Annotated[
        AnyUrl,
        Field(
            description='URL to an HTML page that renders this piece. Can be embedded in an iframe.'
        ),
    ]
    preview_html: Annotated[
        str,
        Field(
            description='Raw HTML for this rendered piece. Can be embedded directly in the page without iframe. Security warning: Only use with trusted creative agents as this bypasses iframe sandboxing.'
        ),
    ]
    role: Annotated[
        str,
        Field(
            description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles."
        ),
    ]
    dimensions: Annotated[
        Dimensions | None, Field(description='Dimensions for this rendered piece')
    ] = None
    embedding: Annotated[
        Embedding | None,
        Field(description='Optional security and embedding metadata for safe iframe integration'),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var dimensionsDimensions | None
var embeddingEmbedding | None
var model_config
var output_format : Literal['both']
var preview_html : str
var preview_url : pydantic.networks.AnyUrl
var render_id : str
var role : str

Inherited members

class PriceGuidance (**data: Any)
Expand source code
class PriceGuidance(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    p25: Annotated[
        float | None, Field(description='25th percentile of recent winning bids', ge=0.0)
    ] = None
    p50: Annotated[float | None, Field(description='Median of recent winning bids', ge=0.0)] = None
    p75: Annotated[
        float | None, Field(description='75th percentile of recent winning bids', ge=0.0)
    ] = None
    p90: Annotated[
        float | None, Field(description='90th percentile of recent winning bids', ge=0.0)
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config
var p25 : float | None
var p50 : float | None
var p75 : float | None
var p90 : float | None

Inherited members

class PricingModel (*args, **kwds)
Expand source code
class PricingModel(Enum):
    cpm = 'cpm'
    vcpm = 'vcpm'
    cpc = 'cpc'
    cpcv = 'cpcv'
    cpv = 'cpv'
    cpp = 'cpp'
    cpa = 'cpa'
    flat_rate = 'flat_rate'
    time = 'time'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var cpa
var cpc
var cpcv
var cpm
var cpp
var cpv
var flat_rate
var time
var vcpm
class Product (**data: Any)
Expand source code
class Product(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    product_id: Annotated[str, Field(description='Unique identifier for the product')]
    name: Annotated[str, Field(description='Human-readable product name')]
    description: Annotated[
        str, Field(description='Detailed description of the product and its inventory')
    ]
    publisher_properties: Annotated[
        list[publisher_property_selector.PublisherPropertySelector],
        Field(
            description="Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency.",
            min_length=1,
        ),
    ]
    channels: Annotated[
        list[channels_1.MediaChannel] | None,
        Field(
            description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']."
        ),
    ] = None
    format_ids: Annotated[
        list[format_id_1.FormatId],
        Field(
            description='Array of supported creative format IDs - structured format_id objects with agent_url and id'
        ),
    ]
    placements: Annotated[
        list[placement.Placement] | None,
        Field(
            description='Optional array of specific placements within this product. When provided, buyers can target specific placements when assigning creatives.',
            min_length=1,
        ),
    ] = None
    delivery_type: delivery_type_1.DeliveryType
    exclusivity: Annotated[
        exclusivity_1.Exclusivity | None,
        Field(
            description="Whether this product offers exclusive access to its inventory. Defaults to 'none' when absent. Most relevant for guaranteed products tied to specific collections or placements."
        ),
    ] = None
    pricing_options: Annotated[
        list[pricing_option.PricingOption],
        Field(description='Available pricing models for this product', min_length=1),
    ]
    forecast: Annotated[
        delivery_forecast.DeliveryForecast | None,
        Field(
            description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.'
        ),
    ] = None
    outcome_measurement: outcome_measurement_1.OutcomeMeasurement | None = None
    delivery_measurement: Annotated[
        DeliveryMeasurement | None,
        Field(
            description='Measurement provider and methodology for delivery metrics. The buyer accepts the declared provider as the source of truth for the buy. When absent, buyers should apply their own measurement defaults.'
        ),
    ] = None
    measurement_terms: Annotated[
        measurement_terms_1.MeasurementTerms | None,
        Field(
            description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy."
        ),
    ] = None
    performance_standards: Annotated[
        list[performance_standard.PerformanceStandard] | None,
        Field(
            description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.",
            min_length=1,
        ),
    ] = None
    cancellation_policy: Annotated[
        cancellation_policy_1.CancellationPolicy | None,
        Field(
            description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.'
        ),
    ] = None
    reporting_capabilities: reporting_capabilities_1.ReportingCapabilities
    creative_policy: creative_policy_1.CreativePolicy | None = None
    is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None
    property_targeting_allowed: Annotated[
        bool | None,
        Field(
            description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results."
        ),
    ] = False
    data_provider_signals: Annotated[
        list[data_provider_signal_selector.DataProviderSignalSelector] | None,
        Field(
            description="Data provider signals available for this product. Buyers fetch signal definitions from each data provider's adagents.json and can verify agent authorization."
        ),
    ] = None
    signal_targeting_allowed: Annotated[
        bool | None,
        Field(
            description='Whether buyers can filter this product to a subset of its data_provider_signals. When false (default), the product includes all listed signals as a bundle. When true, buyers can target specific signals.'
        ),
    ] = False
    catalog_types: Annotated[
        list[catalog_type.CatalogType] | None,
        Field(
            description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.',
            min_length=1,
        ),
    ] = None
    metric_optimization: Annotated[
        MetricOptimization | None,
        Field(
            description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively."
        ),
    ] = None
    max_optimization_goals: Annotated[
        int | None,
        Field(
            description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.',
            ge=1,
        ),
    ] = None
    measurement_readiness: Annotated[
        measurement_readiness_1.MeasurementReadiness | None,
        Field(
            description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals."
        ),
    ] = None
    conversion_tracking: Annotated[
        ConversionTracking | None,
        Field(
            description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities."
        ),
    ] = None
    catalog_match: Annotated[
        CatalogMatch | None,
        Field(
            description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).'
        ),
    ] = None
    brief_relevance: Annotated[
        str | None,
        Field(
            description='Explanation of why this product matches the brief (only included when brief is provided)'
        ),
    ] = None
    expires_at: Annotated[
        AwareDatetime | None,
        Field(
            description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.'
        ),
    ] = None
    product_card: Annotated[
        ProductCard | None,
        Field(
            description='Optional standard visual card (300x400px) for displaying this product in user interfaces. Can be rendered via preview_creative or pre-generated.'
        ),
    ] = None
    product_card_detailed: Annotated[
        ProductCardDetailed | None,
        Field(
            description='Optional detailed card with carousel and full specifications. Provides rich product presentation similar to media kit pages.'
        ),
    ] = None
    collections: Annotated[
        list[collection_selector.CollectionSelector] | None,
        Field(
            description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.',
            min_length=1,
        ),
    ] = None
    collection_targeting_allowed: Annotated[
        bool | None,
        Field(
            description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy."
        ),
    ] = False
    installments: Annotated[
        list[installment.Installment] | None,
        Field(
            description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).'
        ),
    ] = None
    enforced_policies: Annotated[
        list[str] | None,
        Field(
            description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.'
        ),
    ] = None
    trusted_match: Annotated[
        TrustedMatch | None,
        Field(
            description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.'
        ),
    ] = None
    material_submission: Annotated[
        MaterialSubmission | None,
        Field(
            description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation."
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var brief_relevance : str | None
var cancellation_policyCancellationPolicy | None
var catalog_matchCatalogMatch | None
var catalog_types : list[CatalogType] | None
var channels : list[MediaChannel] | None
var collection_targeting_allowed : bool | None
var collections : list[CollectionSelector] | None
var conversion_trackingConversionTracking | None
var creative_policyCreativePolicy | None
var data_provider_signals : list[DataProviderSignalSelector] | None
var delivery_measurementDeliveryMeasurement | None
var delivery_typeDeliveryType
var description : str
var enforced_policies : list[str] | None
var exclusivityExclusivity | None
var expires_at : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var forecastDeliveryForecast | None
var format_ids : list[FormatId]
var installments : list[Installment] | None
var is_custom : bool | None
var material_submissionMaterialSubmission | None
var max_optimization_goals : int | None
var measurement_readinessMeasurementReadiness | None
var measurement_termsMeasurementTerms | None
var metric_optimizationMetricOptimization | None
var model_config
var name : str
var outcome_measurementOutcomeMeasurement | None
var performance_standards : list[PerformanceStandard] | None
var placements : list[Placement] | None
var pricing_options : list[PricingOption]
var product_cardProductCard | None
var product_card_detailedProductCardDetailed | None
var product_id : str
var property_targeting_allowed : bool | None
var publisher_properties : list[PublisherPropertySelector]
var reporting_capabilitiesReportingCapabilities
var signal_targeting_allowed : bool | None
var trusted_matchTrustedMatch | None

Inherited members

class ProductFilters (**data: Any)
Expand source code
class ProductFilters(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    delivery_type: delivery_type_1.DeliveryType | None = None
    exclusivity: Annotated[
        exclusivity_1.Exclusivity | None,
        Field(
            description="Filter by exclusivity level. Returns products matching the specified exclusivity (e.g., 'exclusive' returns only sole-sponsorship products)."
        ),
    ] = None
    is_fixed_price: Annotated[
        bool | None,
        Field(
            description='Filter by pricing availability: true = products offering fixed pricing (at least one option with fixed_price), false = products offering auction pricing (at least one option without fixed_price). Products with both fixed and auction options match both true and false.'
        ),
    ] = None
    format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(description='Filter by specific format IDs', min_length=1),
    ] = None
    standard_formats_only: Annotated[
        bool | None, Field(description='Only return products accepting IAB standard formats')
    ] = None
    min_exposures: Annotated[
        int | None,
        Field(description='Minimum exposures/impressions needed for measurement validity', ge=1),
    ] = None
    start_date: Annotated[
        date_aliased | None,
        Field(
            description='Campaign start date (ISO 8601 date format: YYYY-MM-DD) for availability checks'
        ),
    ] = None
    end_date: Annotated[
        date_aliased | None,
        Field(
            description='Campaign end date (ISO 8601 date format: YYYY-MM-DD) for availability checks'
        ),
    ] = None
    budget_range: Annotated[
        BudgetRange | BudgetRange3 | None,
        Field(description='Budget range to filter appropriate products'),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description="Filter by country coverage using ISO 3166-1 alpha-2 codes (e.g., ['US', 'CA', 'GB']). Works for all inventory types.",
            min_length=1,
        ),
    ] = None
    regions: Annotated[
        list[Region] | None,
        Field(
            description="Filter by region coverage using ISO 3166-2 codes (e.g., ['US-NY', 'US-CA', 'GB-SCT']). Use for locally-bound inventory (regional OOH, local TV) where products have region-specific coverage.",
            min_length=1,
        ),
    ] = None
    metros: Annotated[
        list[Metro] | None,
        Field(
            description='Filter by metro coverage for locally-bound inventory (radio, DOOH, local TV). Use when products have DMA/metro-specific coverage. For digital inventory where products have broad coverage, use required_geo_targeting instead to filter by seller capability.',
            min_length=1,
        ),
    ] = None
    channels: Annotated[
        list[channels_1.MediaChannel] | None,
        Field(
            description="Filter by advertising channels (e.g., ['display', 'ctv', 'dooh'])",
            min_length=1,
        ),
    ] = None
    required_axe_integrations: Annotated[
        list[AnyUrl] | None,
        Field(
            deprecated=True,
            description='Deprecated: Use trusted_match filter instead. Filter to products executable through specific agentic ad exchanges. URLs are canonical identifiers.',
        ),
    ] = None
    trusted_match: Annotated[
        TrustedMatch | None,
        Field(
            description='Filter products by Trusted Match Protocol capabilities. Only products with matching TMP support are returned.'
        ),
    ] = None
    required_features: Annotated[
        media_buy_features.MediaBuyFeatures | None,
        Field(
            description='Filter to products from sellers supporting specific protocol features. Only features set to true are used for filtering.'
        ),
    ] = None
    required_geo_targeting: Annotated[
        list[RequiredGeoTargetingItem] | None,
        Field(
            description='Filter to products from sellers supporting specific geo targeting capabilities. Each entry specifies a targeting level (country, region, metro, postal_area) and optionally a system for levels that have multiple classification systems.',
            min_length=1,
        ),
    ] = None
    signal_targeting: Annotated[
        list[signal_targeting_1.SignalTargeting] | None,
        Field(
            description='Filter to products supporting specific signals from data provider catalogs. Products must have the requested signals in their data_provider_signals and signal_targeting_allowed must be true (or all signals requested).',
            min_length=1,
        ),
    ] = None
    postal_areas: Annotated[
        list[PostalArea] | None,
        Field(
            description='Filter by postal area coverage for locally-bound inventory (direct mail, DOOH, local campaigns). Use when products have postal-area-specific coverage. For digital inventory where products have broad coverage, use required_geo_targeting instead to filter by seller capability.',
            min_length=1,
        ),
    ] = None
    geo_proximity: Annotated[
        list[GeoProximity | GeoProximity15 | GeoProximity16] | None,
        Field(
            description='Filter by proximity to geographic points. Returns products with inventory coverage near these locations. Follows the same format as the targeting overlay — each entry uses exactly one method: travel_time + transport_mode, radius, or geometry. For locally-bound inventory (DOOH, radio), filters to products with coverage in the area. For digital inventory, filters to products from sellers supporting geo_proximity targeting.',
            min_length=1,
        ),
    ] = None
    required_performance_standards: Annotated[
        list[performance_standard.PerformanceStandard] | None,
        Field(
            description="Filter to products that can meet the buyer's performance standard requirements. Each entry specifies a metric, minimum threshold, and optionally a required vendor and standard. Products that cannot meet these thresholds or do not support the specified vendors are excluded. Use this to tell the seller upfront: 'I need DoubleVerify for viewability at 70% MRC.'",
            min_length=1,
        ),
    ] = None
    keywords: Annotated[
        list[Keyword] | None,
        Field(
            description='Filter by keyword relevance for search and retail media platforms. Returns products that support keyword targeting for these terms. Allows the sell-side agent to assess keyword availability and recommend appropriate products. Use match_type to indicate the desired precision.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var budget_rangeBudgetRange | BudgetRange3 | None
var channels : list[MediaChannel] | None
var countries : list[Country] | None
var delivery_typeDeliveryType | None
var end_date : datetime.date | None
var exclusivityExclusivity | None
var format_ids : list[FormatId] | None
var geo_proximity : list[GeoProximity | GeoProximity15 | GeoProximity16] | None
var is_fixed_price : bool | None
var keywords : list[Keyword] | None
var metros : list[Metro] | None
var min_exposures : int | None
var model_config
var postal_areas : list[PostalArea] | None
var regions : list[Region] | None
var required_featuresMediaBuyFeatures | None
var required_geo_targeting : list[RequiredGeoTargetingItem] | None
var required_performance_standards : list[PerformanceStandard] | None
var signal_targeting : list[SignalTargeting] | None
var standard_formats_only : bool | None
var start_date : datetime.date | None
var trusted_matchTrustedMatch | None

Instance variables

var required_axe_integrations : list[pydantic.networks.AnyUrl] | None
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any:
    if obj is None:
        if self.wrapped_property is not None:
            return self.wrapped_property.__get__(None, obj_type)
        raise AttributeError(self.field_name)

    warnings.warn(self.msg, DeprecationWarning, stacklevel=2)

    if self.wrapped_property is not None:
        return self.wrapped_property.__get__(obj, obj_type)
    return obj.__dict__[self.field_name]

Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.

Attributes

msg
The deprecation message to be emitted.
wrapped_property
The property instance if the deprecated field is a computed field, or None.
field_name
The name of the field being deprecated.

Inherited members

class Property (**data: Any)
Expand source code
class Property(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    property_id: Annotated[
        property_id_1.PropertyId | None,
        Field(
            description='Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects.'
        ),
    ] = None
    property_type: Annotated[
        property_type_1.PropertyType, Field(description='Type of advertising property')
    ]
    name: Annotated[str, Field(description='Human-readable property name')]
    identifiers: Annotated[
        list[Identifier], Field(description='Array of identifiers for this property', min_length=1)
    ]
    tags: Annotated[
        list[property_tag.PropertyTag] | None,
        Field(
            description='Tags for categorization and grouping (e.g., network membership, content categories)'
        ),
    ] = None
    supported_channels: Annotated[
        list[channels.MediaChannel] | None,
        Field(
            description="Advertising channels this property supports (e.g., ['display', 'olv', 'social']). Publishers declare which channels their inventory aligns with. Properties may support multiple channels. See the Media Channel Taxonomy for definitions."
        ),
    ] = None
    publisher_domain: Annotated[
        str | None,
        Field(
            description='Domain where adagents.json should be checked for authorization validation. Optional in adagents.json (file location implies domain).'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var identifiers : list[Identifier]
var model_config
var name : str
var property_idPropertyId | None
var property_typePropertyType
var publisher_domain : str | None
var supported_channels : list[MediaChannel] | None
var tags : list[PropertyTag] | None

Inherited members

class PropertyActivity (**data: Any)
Expand source code
class PropertyActivity(RegistryBaseModel):
    domain: Annotated[str, Field(examples=["examplepub.com"])]
    total: Annotated[int, Field(examples=[3])]
    revisions: list[ActivityRevision]

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var domain : str
var model_config
var revisions : list[ActivityRevision]
var total : int
class PropertyId (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class PropertyId(RootModel[str]):
    root: Annotated[
        str,
        Field(
            description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.',
            examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'],
            pattern='^[a-z0-9_]+$',
            title='Property ID',
        ),
    ]

Usage Documentation

RootModel and Custom Root Types

A Pydantic BaseModel for the root object of the model.

Attributes

root
The root object of the model.
__pydantic_root_model__
Whether the model is a RootModel.
__pydantic_private__
Private fields in the model.
__pydantic_extra__
Extra fields in the model.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.root_model.RootModel[str]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var root : str
class PropertyIdentifier (**data: Any)
Expand source code
class PropertyIdentifier(RegistryBaseModel):
    type: Annotated[str, Field(examples=["domain"])]
    value: Annotated[str, Field(examples=["examplepub.com"])]

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config
var type : str
var value : str
class PropertyRegistry (client: RegistryClient,
*,
auth_token: str | None = None,
poll_interval: float = 60.0,
cursor_store: CursorStore | None = None)
Expand source code
class PropertyRegistry:
    """Local cache of property/agent authorization relationships.

    Queries are synchronous dict lookups — no network calls.
    Background sync is opt-in via ``auth_token``.

    Args:
        client: RegistryClient for API calls.
        auth_token: Bearer token for change feed access.
            If omitted, background sync is disabled (load-only mode).
        poll_interval: Seconds between feed polls (default 60).
        cursor_store: Optional CursorStore for feed cursor persistence.
    """

    def __init__(
        self,
        client: RegistryClient,
        *,
        auth_token: str | None = None,
        poll_interval: float = 60.0,
        cursor_store: CursorStore | None = None,
    ) -> None:
        self._client = client
        self._auth_token = auth_token
        self._poll_interval = poll_interval
        self._cursor_store = cursor_store
        self._domain_to_agents: dict[str, set[str]] = {}
        self._agent_to_domains: dict[str, set[str]] = {}
        self._loaded = False
        self._sync: RegistrySync | None = None
        self._task: asyncio.Task[None] | None = None

    # ------------------------------------------------------------------
    # Queries (synchronous, no network)
    # ------------------------------------------------------------------

    def is_authorized(self, agent_url: str, domain: str) -> bool:
        """Check if an agent is authorized for a domain."""
        return agent_url in self._domain_to_agents.get(domain, set())

    def get_domains(self, agent_url: str) -> frozenset[str]:
        """Get all domains authorized for an agent."""
        return frozenset(self._agent_to_domains.get(agent_url, set()))

    def get_agents(self, domain: str) -> frozenset[str]:
        """Get all agents authorized for a domain."""
        return frozenset(self._domain_to_agents.get(domain, set()))

    @property
    def agent_count(self) -> int:
        """Number of agents in the index."""
        return len(self._agent_to_domains)

    @property
    def domain_count(self) -> int:
        """Number of domains in the index."""
        return len(self._domain_to_agents)

    @property
    def loaded(self) -> bool:
        """Whether initial data has been loaded."""
        return self._loaded

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    async def load(self) -> None:
        """Fetch initial state from the registry API.

        Calls ``list_agents()`` and builds the bidirectional
        authorization index from each agent's ``publisher_domains``.
        """
        agents = await self._client.list_agents(properties=True)
        domain_to_agents: dict[str, set[str]] = {}
        agent_to_domains: dict[str, set[str]] = {}

        for agent in agents:
            domains = agent.publisher_domains or []
            if domains:
                agent_to_domains[agent.url] = set(domains)
                for domain in domains:
                    domain_to_agents.setdefault(domain, set()).add(agent.url)

        self._domain_to_agents = domain_to_agents
        self._agent_to_domains = agent_to_domains
        self._loaded = True
        logger.info(
            "PropertyRegistry loaded: %d agents, %d domains",
            len(agent_to_domains),
            len(domain_to_agents),
        )

    async def start(self) -> None:
        """Load initial state and start background sync.

        If ``auth_token`` was not provided, only loads initial state
        without starting the polling loop.
        """
        if not self._loaded:
            await self.load()

        if self._auth_token is None:
            logger.info(
                "PropertyRegistry: no auth_token, background sync disabled"
            )
            return

        self._sync = RegistrySync(
            self._client,
            auth_token=self._auth_token,
            poll_interval=self._poll_interval,
            cursor_store=self._cursor_store,
            types="authorization.*,agent.*,property.*",
        )
        self._sync.on_all(self._handle_event)
        self._task = asyncio.create_task(self._sync.start())

    async def stop(self) -> None:
        """Stop background sync."""
        if self._sync is not None:
            await self._sync.stop()
        if self._task is not None:
            await self._task
            self._task = None
        self._sync = None

    async def __aenter__(self) -> PropertyRegistry:
        await self.start()
        return self

    async def __aexit__(self, *args: object) -> None:
        await self.stop()

    async def refresh(self) -> None:
        """Force a full reload from the API."""
        self._domain_to_agents.clear()
        self._agent_to_domains.clear()
        self._loaded = False
        await self.load()

    # ------------------------------------------------------------------
    # Event handling
    #
    # Trust model: events are fetched over HTTPS from the registry API
    # using a Bearer token. The events are not cryptographically signed.
    # A compromised transport or registry could inject forged events.
    # ------------------------------------------------------------------

    async def _handle_event(self, event: FeedEvent) -> None:
        """Route feed events to the appropriate handler."""
        et = event.event_type
        if et.startswith("authorization."):
            self._apply_authorization(event)
        elif et == "agent.deleted":
            self._remove_agent(event.entity_id)
        elif et in ("agent.created", "agent.updated"):
            await self._refresh_agent(event.payload.get("url", event.entity_id))
        elif et == "property.deleted":
            self._remove_domain(event.entity_id)
        # Unknown event types: ignore silently (forward compatible)

    _ADD_TYPES = {"authorization.created", "authorization.granted"}
    _REMOVE_TYPES = {"authorization.revoked", "authorization.deleted"}

    def _apply_authorization(self, event: FeedEvent) -> None:
        """Add or remove an authorization edge."""
        agent_url = event.payload.get("agent_url", "")
        domain = event.payload.get("domain", "")
        if not agent_url or not domain:
            return

        if event.event_type in self._ADD_TYPES:
            self._domain_to_agents.setdefault(domain, set()).add(agent_url)
            self._agent_to_domains.setdefault(agent_url, set()).add(domain)
        elif event.event_type in self._REMOVE_TYPES:
            self._domain_to_agents.get(domain, set()).discard(agent_url)
            self._agent_to_domains.get(agent_url, set()).discard(domain)

    def _remove_agent(self, agent_url: str) -> None:
        """Remove all authorization edges for an agent."""
        domains = self._agent_to_domains.pop(agent_url, set())
        for domain in domains:
            agents = self._domain_to_agents.get(domain)
            if agents is not None:
                agents.discard(agent_url)
                if not agents:
                    del self._domain_to_agents[domain]

    def _remove_domain(self, domain: str) -> None:
        """Remove all authorization edges for a domain."""
        agents = self._domain_to_agents.pop(domain, set())
        for agent_url in agents:
            domains = self._agent_to_domains.get(agent_url)
            if domains is not None:
                domains.discard(domain)
                if not domains:
                    del self._agent_to_domains[agent_url]

    async def _refresh_agent(self, agent_url: str) -> None:
        """Re-fetch a single agent's domains and update indexes."""
        try:
            data = await self._client.get_agent_domains(agent_url)
            new_domains = {
                p["domain"]
                for p in data.get("properties", [])
                if "domain" in p
            }
        except Exception as exc:
            logger.warning("Failed to refresh agent %s: %s", agent_url, exc)
            return

        # Remove old edges
        old_domains = self._agent_to_domains.get(agent_url, set())
        for d in old_domains:
            s = self._domain_to_agents.get(d)
            if s is not None:
                s.discard(agent_url)
                if not s:
                    del self._domain_to_agents[d]

        # Add new edges
        if new_domains:
            self._agent_to_domains[agent_url] = new_domains
            for d in new_domains:
                self._domain_to_agents.setdefault(d, set()).add(agent_url)
        else:
            self._agent_to_domains.pop(agent_url, None)

Local cache of property/agent authorization relationships.

Queries are synchronous dict lookups — no network calls. Background sync is opt-in via auth_token.

Args

client
RegistryClient for API calls.
auth_token
Bearer token for change feed access. If omitted, background sync is disabled (load-only mode).
poll_interval
Seconds between feed polls (default 60).
cursor_store
Optional CursorStore for feed cursor persistence.

Instance variables

prop agent_count : int
Expand source code
@property
def agent_count(self) -> int:
    """Number of agents in the index."""
    return len(self._agent_to_domains)

Number of agents in the index.

prop domain_count : int
Expand source code
@property
def domain_count(self) -> int:
    """Number of domains in the index."""
    return len(self._domain_to_agents)

Number of domains in the index.

prop loaded : bool
Expand source code
@property
def loaded(self) -> bool:
    """Whether initial data has been loaded."""
    return self._loaded

Whether initial data has been loaded.

Methods

def get_agents(self, domain: str) ‑> frozenset[str]
Expand source code
def get_agents(self, domain: str) -> frozenset[str]:
    """Get all agents authorized for a domain."""
    return frozenset(self._domain_to_agents.get(domain, set()))

Get all agents authorized for a domain.

def get_domains(self, agent_url: str) ‑> frozenset[str]
Expand source code
def get_domains(self, agent_url: str) -> frozenset[str]:
    """Get all domains authorized for an agent."""
    return frozenset(self._agent_to_domains.get(agent_url, set()))

Get all domains authorized for an agent.

def is_authorized(self, agent_url: str, domain: str) ‑> bool
Expand source code
def is_authorized(self, agent_url: str, domain: str) -> bool:
    """Check if an agent is authorized for a domain."""
    return agent_url in self._domain_to_agents.get(domain, set())

Check if an agent is authorized for a domain.

async def load(self) ‑> None
Expand source code
async def load(self) -> None:
    """Fetch initial state from the registry API.

    Calls ``list_agents()`` and builds the bidirectional
    authorization index from each agent's ``publisher_domains``.
    """
    agents = await self._client.list_agents(properties=True)
    domain_to_agents: dict[str, set[str]] = {}
    agent_to_domains: dict[str, set[str]] = {}

    for agent in agents:
        domains = agent.publisher_domains or []
        if domains:
            agent_to_domains[agent.url] = set(domains)
            for domain in domains:
                domain_to_agents.setdefault(domain, set()).add(agent.url)

    self._domain_to_agents = domain_to_agents
    self._agent_to_domains = agent_to_domains
    self._loaded = True
    logger.info(
        "PropertyRegistry loaded: %d agents, %d domains",
        len(agent_to_domains),
        len(domain_to_agents),
    )

Fetch initial state from the registry API.

Calls list_agents() and builds the bidirectional authorization index from each agent's publisher_domains.

async def refresh(self) ‑> None
Expand source code
async def refresh(self) -> None:
    """Force a full reload from the API."""
    self._domain_to_agents.clear()
    self._agent_to_domains.clear()
    self._loaded = False
    await self.load()

Force a full reload from the API.

async def start(self) ‑> None
Expand source code
async def start(self) -> None:
    """Load initial state and start background sync.

    If ``auth_token`` was not provided, only loads initial state
    without starting the polling loop.
    """
    if not self._loaded:
        await self.load()

    if self._auth_token is None:
        logger.info(
            "PropertyRegistry: no auth_token, background sync disabled"
        )
        return

    self._sync = RegistrySync(
        self._client,
        auth_token=self._auth_token,
        poll_interval=self._poll_interval,
        cursor_store=self._cursor_store,
        types="authorization.*,agent.*,property.*",
    )
    self._sync.on_all(self._handle_event)
    self._task = asyncio.create_task(self._sync.start())

Load initial state and start background sync.

If auth_token was not provided, only loads initial state without starting the polling loop.

async def stop(self) ‑> None
Expand source code
async def stop(self) -> None:
    """Stop background sync."""
    if self._sync is not None:
        await self._sync.stop()
    if self._task is not None:
        await self._task
        self._task = None
    self._sync = None

Stop background sync.

class PropertyRegistryItem (**data: Any)
Expand source code
class PropertyRegistryItem(RegistryBaseModel):
    domain: Annotated[str, Field(examples=["examplepub.com"])]
    source: PropertyRegistrySource
    property_count: int
    agent_count: int
    verified: bool

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var agent_count : int
var domain : str
var model_config
var property_count : int
var sourcePropertyRegistrySource
var verified : bool
class PropertySummary (**data: Any)
Expand source code
class PropertySummary(RegistryBaseModel):
    total_count: int
    count_by_type: dict[str, int]
    tags: list[str]
    publisher_count: int

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var count_by_type : dict[str, int]
var model_config
var publisher_count : int
var tags : list[str]
var total_count : int
class PropertyTag (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class PropertyTag(RootModel[str]):
    root: Annotated[
        str,
        Field(
            description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.',
            examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'],
            pattern='^[a-z0-9_]+$',
            title='Property Tag',
        ),
    ]

Usage Documentation

RootModel and Custom Root Types

A Pydantic BaseModel for the root object of the model.

Attributes

root
The root object of the model.
__pydantic_root_model__
Whether the model is a RootModel.
__pydantic_private__
Private fields in the model.
__pydantic_extra__
Extra fields in the model.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.root_model.RootModel[str]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var root : str
class Proposal (**data: Any)
Expand source code
class Proposal(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    proposal_id: Annotated[
        str,
        Field(
            description='Unique identifier for this proposal. Used to execute it via create_media_buy.',
            max_length=255,
        ),
    ]
    name: Annotated[
        str, Field(description='Human-readable name for this media plan proposal', max_length=500)
    ]
    description: Annotated[
        str | None,
        Field(
            description='Explanation of the proposal strategy and what it achieves', max_length=2000
        ),
    ] = None
    allocations: Annotated[
        list[product_allocation.ProductAllocation],
        Field(
            description='Budget allocations across products. Allocation percentages MUST sum to 100. Publishers are responsible for ensuring the sum equals 100; buyers SHOULD validate this before execution.',
            min_length=1,
        ),
    ]
    proposal_status: Annotated[
        proposal_status_1.ProposalStatus | None,
        Field(
            description="Lifecycle status of this proposal. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at."
        ),
    ] = None
    expires_at: Annotated[
        AwareDatetime | None,
        Field(
            description='When this proposal expires and can no longer be executed. For draft proposals, indicates when indicative pricing becomes stale. For committed proposals, indicates when the inventory hold lapses — the buyer must call create_media_buy before this time.'
        ),
    ] = None
    insertion_order: Annotated[
        insertion_order_1.InsertionOrder | None,
        Field(
            description='Formal insertion order attached to a committed proposal. Present when the seller requires a signed agreement before the media buy can proceed. The buyer references the io_id in io_acceptance on create_media_buy.'
        ),
    ] = None
    total_budget_guidance: Annotated[
        TotalBudgetGuidance | None, Field(description='Optional budget guidance for this proposal')
    ] = None
    brief_alignment: Annotated[
        str | None,
        Field(
            description='Explanation of how this proposal aligns with the campaign brief',
            max_length=2000,
        ),
    ] = None
    forecast: Annotated[
        delivery_forecast.DeliveryForecast | None,
        Field(
            description='Aggregate forecasted delivery metrics for the entire proposal. When both proposal-level and allocation-level forecasts are present, the proposal-level forecast is authoritative for total delivery estimation.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var allocations : list[ProductAllocation]
var brief_alignment : str | None
var description : str | None
var expires_at : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var forecastDeliveryForecast | None
var insertion_orderInsertionOrder | None
var model_config
var name : str
var proposal_id : str
var proposal_statusProposalStatus | None
var total_budget_guidanceTotalBudgetGuidance | None

Inherited members

class Protocol (*args, **kwds)
Expand source code
class Protocol(str, Enum):
    """Supported protocols."""

    A2A = "a2a"
    MCP = "mcp"

Supported protocols.

Ancestors

  • builtins.str
  • enum.Enum

Class variables

var A2A
var MCP
class ProvidePerformanceFeedbackRequest (**data: Any)
Expand source code
class ProvidePerformanceFeedbackRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    media_buy_id: Annotated[str, Field(description="Seller's media buy identifier", min_length=1)]
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for this request. Prevents duplicate feedback submissions on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    measurement_period: Annotated[
        datetime_range.DatetimeRange, Field(description='Time period for performance measurement')
    ]
    performance_index: Annotated[
        float,
        Field(
            description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)',
            ge=0.0,
        ),
    ]
    package_id: Annotated[
        str | None,
        Field(
            description='Specific package within the media buy (if feedback is package-specific)',
            min_length=1,
        ),
    ] = None
    creative_id: Annotated[
        str | None,
        Field(
            description='Specific creative asset (if feedback is creative-specific)', min_length=1
        ),
    ] = None
    metric_type: Annotated[
        metric_type_1.MetricType | None, Field(description='The business metric being measured')
    ] = metric_type_1.MetricType.overall_performance
    feedback_source: Annotated[
        feedback_source_1.FeedbackSource | None, Field(description='Source of the performance data')
    ] = feedback_source_1.FeedbackSource.buyer_attribution
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var creative_id : str | None
var extExtensionObject | None
var feedback_sourceFeedbackSource | None
var idempotency_key : str
var measurement_periodDatetimeRange
var media_buy_id : str
var metric_typeMetricType | None
var model_config
var package_id : str | None
var performance_index : float
class ProvidePerformanceFeedbackByBuyerRefRequest (**data: Any)
Expand source code
class ProvidePerformanceFeedbackRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    media_buy_id: Annotated[str, Field(description="Seller's media buy identifier", min_length=1)]
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for this request. Prevents duplicate feedback submissions on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    measurement_period: Annotated[
        datetime_range.DatetimeRange, Field(description='Time period for performance measurement')
    ]
    performance_index: Annotated[
        float,
        Field(
            description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)',
            ge=0.0,
        ),
    ]
    package_id: Annotated[
        str | None,
        Field(
            description='Specific package within the media buy (if feedback is package-specific)',
            min_length=1,
        ),
    ] = None
    creative_id: Annotated[
        str | None,
        Field(
            description='Specific creative asset (if feedback is creative-specific)', min_length=1
        ),
    ] = None
    metric_type: Annotated[
        metric_type_1.MetricType | None, Field(description='The business metric being measured')
    ] = metric_type_1.MetricType.overall_performance
    feedback_source: Annotated[
        feedback_source_1.FeedbackSource | None, Field(description='Source of the performance data')
    ] = feedback_source_1.FeedbackSource.buyer_attribution
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var creative_id : str | None
var extExtensionObject | None
var feedback_sourceFeedbackSource | None
var idempotency_key : str
var measurement_periodDatetimeRange
var media_buy_id : str
var metric_typeMetricType | None
var model_config
var package_id : str | None
var performance_index : float
class ProvidePerformanceFeedbackByMediaBuyRequest (**data: Any)
Expand source code
class ProvidePerformanceFeedbackRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    media_buy_id: Annotated[str, Field(description="Seller's media buy identifier", min_length=1)]
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for this request. Prevents duplicate feedback submissions on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    measurement_period: Annotated[
        datetime_range.DatetimeRange, Field(description='Time period for performance measurement')
    ]
    performance_index: Annotated[
        float,
        Field(
            description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)',
            ge=0.0,
        ),
    ]
    package_id: Annotated[
        str | None,
        Field(
            description='Specific package within the media buy (if feedback is package-specific)',
            min_length=1,
        ),
    ] = None
    creative_id: Annotated[
        str | None,
        Field(
            description='Specific creative asset (if feedback is creative-specific)', min_length=1
        ),
    ] = None
    metric_type: Annotated[
        metric_type_1.MetricType | None, Field(description='The business metric being measured')
    ] = metric_type_1.MetricType.overall_performance
    feedback_source: Annotated[
        feedback_source_1.FeedbackSource | None, Field(description='Source of the performance data')
    ] = feedback_source_1.FeedbackSource.buyer_attribution
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var creative_id : str | None
var extExtensionObject | None
var feedback_sourceFeedbackSource | None
var idempotency_key : str
var measurement_periodDatetimeRange
var media_buy_id : str
var metric_typeMetricType | None
var model_config
var package_id : str | None
var performance_index : float

Inherited members

class ProvidePerformanceFeedbackSuccessResponse (**data: Any)
Expand source code
class ProvidePerformanceFeedbackResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    success: Annotated[
        Literal[True],
        Field(description='Whether the performance feedback was successfully received'),
    ]
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var sandbox : bool | None
var success : Literal[True]

Inherited members

class ProvidePerformanceFeedbackErrorResponse (**data: Any)
Expand source code
class ProvidePerformanceFeedbackResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[
        list[error.Error],
        Field(
            description='Array of errors explaining why feedback was rejected (e.g., invalid measurement period, missing campaign data)',
            min_length=1,
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class PublisherPropertiesAll (**data: Any)
Expand source code
class PublisherPropertySelector1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    publisher_domain: Annotated[
        str,
        Field(
            description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com')",
            pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$',
        ),
    ]
    selection_type: Annotated[
        Literal['all'],
        Field(
            description='Discriminator indicating all properties from this publisher are included'
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config
var publisher_domain : str
var selection_type : Literal['all']

Inherited members

class PublisherPropertiesById (**data: Any)
Expand source code
class PublisherPropertySelector2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    publisher_domain: Annotated[
        str,
        Field(
            description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com')",
            pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$',
        ),
    ]
    selection_type: Annotated[
        Literal['by_id'],
        Field(description='Discriminator indicating selection by specific property IDs'),
    ]
    property_ids: Annotated[
        list[property_id.PropertyId],
        Field(description="Specific property IDs from the publisher's adagents.json", min_length=1),
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config
var property_ids : list[PropertyId]
var publisher_domain : str
var selection_type : Literal['by_id']

Inherited members

class PublisherPropertiesByTag (**data: Any)
Expand source code
class PublisherPropertySelector3(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    publisher_domain: Annotated[
        str,
        Field(
            description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com')",
            pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$',
        ),
    ]
    selection_type: Annotated[
        Literal['by_tag'], Field(description='Discriminator indicating selection by property tags')
    ]
    property_tags: Annotated[
        list[property_tag.PropertyTag],
        Field(
            description="Property tags from the publisher's adagents.json. Selector covers all properties with these tags",
            min_length=1,
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var model_config
var property_tags : list[PropertyTag]
var publisher_domain : str
var selection_type : Literal['by_tag']

Inherited members

class PushNotificationConfig (**data: Any)
Expand source code
class PushNotificationConfig(AdCPBaseModel):
    url: Annotated[AnyUrl, Field(description="Webhook endpoint URL for task status notifications")]
    token: Annotated[
        str | None,
        Field(
            description="Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.",
            min_length=16,
        ),
    ] = None
    authentication: Annotated[
        Authentication | None,
        Field(
            description="Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. When omitted, the seller MUST sign webhooks with the 9421 profile."
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var authenticationAuthentication | None
var model_config
var token : str | None
var url : pydantic.networks.AnyUrl

Inherited members

class Refine (**data: Any)
Expand source code
class Refine(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    scope: Annotated[
        Literal['request'],
        Field(
            description='Change scoped to the overall request — direction for the selection as a whole.'
        ),
    ]
    ask: Annotated[
        str,
        Field(
            description="What the buyer is asking for at the request level (e.g., 'more video options and less display', 'suggest how to combine these products').",
            min_length=1,
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var ask : str
var model_config
var scope : Literal['request']

Inherited members

class RegistryClient (base_url: str = 'https://agenticadvertising.org',
timeout: float = 10.0,
client: httpx.AsyncClient | None = None,
user_agent: str = 'adcp-client-python')
Expand source code
class RegistryClient:
    """Client for the AdCP registry API.

    Provides brand, property, and member lookups against the central AdCP registry.

    Args:
        base_url: Registry API base URL.
        timeout: Request timeout in seconds.
        client: Optional httpx.AsyncClient for connection pooling.
            If provided, caller is responsible for client lifecycle.
        user_agent: User-Agent header for requests.
    """

    def __init__(
        self,
        base_url: str = DEFAULT_REGISTRY_URL,
        timeout: float = 10.0,
        client: httpx.AsyncClient | None = None,
        user_agent: str = "adcp-client-python",
    ):
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._external_client = client
        self._owned_client: httpx.AsyncClient | None = None
        self._user_agent = user_agent

    async def _get_client(self) -> httpx.AsyncClient:
        """Get or create httpx client."""
        if self._external_client is not None:
            return self._external_client
        if self._owned_client is None:
            self._owned_client = httpx.AsyncClient(
                limits=httpx.Limits(
                    max_keepalive_connections=10,
                    max_connections=20,
                ),
            )
        return self._owned_client

    async def close(self) -> None:
        """Close owned HTTP client. No-op if using external client."""
        if self._owned_client is not None:
            await self._owned_client.aclose()
            self._owned_client = None

    async def __aenter__(self) -> RegistryClient:
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()

    async def _request(
        self,
        method: str,
        path: str,
        *,
        params: dict[str, Any] | None = None,
        json_body: dict[str, Any] | None = None,
        auth_token: str | None = None,
        operation: str = "Registry request",
        allow_404: bool = False,
        expected_status: int | set[int] = 200,
    ) -> httpx.Response | None:
        """Execute a registry API request with standard error handling.

        Returns None if allow_404=True and the server returns 404.
        Raises RegistryError for all other non-expected status codes.
        """
        client = await self._get_client()
        headers: dict[str, str] = {"User-Agent": self._user_agent}
        if auth_token is not None:
            headers["Authorization"] = f"Bearer {auth_token}"

        expected = {expected_status} if isinstance(expected_status, int) else expected_status

        try:
            if method == "GET":
                response = await client.get(
                    f"{self._base_url}{path}",
                    params=params,
                    headers=headers,
                    timeout=self._timeout,
                )
            else:
                response = await client.post(
                    f"{self._base_url}{path}",
                    json=json_body,
                    headers=headers,
                    timeout=self._timeout,
                )

            if allow_404 and response.status_code == 404:
                return None
            if response.status_code not in expected:
                raise RegistryError(
                    f"{operation} failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            return response
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(
                f"{operation} timed out after {self._timeout}s"
            ) from e
        except httpx.HTTPError as e:
            raise RegistryError(f"{operation} failed: {e}") from e

    async def _request_ok(
        self,
        method: str,
        path: str,
        **kwargs: Any,
    ) -> httpx.Response:
        """Like _request but guarantees a non-None response.

        Use for endpoints that never return 404-as-None.
        """
        resp = await self._request(method, path, **kwargs)
        if resp is None:
            raise RegistryError(
                f"{kwargs.get('operation', 'Request')} failed: unexpected empty response"
            )
        return resp

    @staticmethod
    def _parse(model_cls: type[_T], data: Any, operation: str) -> _T:
        """Validate data against a Pydantic model, wrapping errors."""
        try:
            return model_cls.model_validate(data)
        except (ValidationError, ValueError) as e:
            raise RegistryError(
                f"{operation} failed: invalid response: {e}"
            ) from e

    async def lookup_brand(self, domain: str) -> ResolvedBrand | None:
        """Resolve a domain to its brand identity.

        Works for any domain — brand houses, sub-brands, and operators
        (agencies, DSPs) are all brands in the registry.

        Args:
            domain: Domain to resolve (e.g., "nike.com", "wpp.com").

        Returns:
            ResolvedBrand if found, None if not in the registry.

        Raises:
            RegistryError: On HTTP or parsing errors.

        Example:
            brand = await registry.lookup_brand(request.brand.domain)
        """
        client = await self._get_client()
        try:
            response = await client.get(
                f"{self._base_url}/api/brands/resolve",
                params={"domain": domain},
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code == 404:
                return None
            if response.status_code != 200:
                raise RegistryError(
                    f"Brand lookup failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            if data is None:
                return None
            return ResolvedBrand.model_validate(data)
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(f"Brand lookup timed out after {self._timeout}s") from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Brand lookup failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(f"Brand lookup failed: invalid response: {e}") from e

    async def lookup_brands(self, domains: list[str]) -> dict[str, ResolvedBrand | None]:
        """Bulk resolve domains to brand identities.

        Automatically chunks requests exceeding 100 domains.

        Args:
            domains: List of domains to resolve.

        Returns:
            Dict mapping each domain to its ResolvedBrand, or None if not found.

        Raises:
            RegistryError: On HTTP or parsing errors.
        """
        if not domains:
            return {}

        chunks = [
            domains[i : i + MAX_BULK_DOMAINS] for i in range(0, len(domains), MAX_BULK_DOMAINS)
        ]

        chunk_results = await asyncio.gather(
            *[self._lookup_brands_chunk(chunk) for chunk in chunks]
        )

        merged: dict[str, ResolvedBrand | None] = {}
        for result in chunk_results:
            merged.update(result)
        return merged

    async def _lookup_brands_chunk(self, domains: list[str]) -> dict[str, ResolvedBrand | None]:
        """Resolve a single chunk of brand domains (max 100)."""
        client = await self._get_client()
        try:
            response = await client.post(
                f"{self._base_url}/api/brands/resolve/bulk",
                json={"domains": domains},
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code != 200:
                raise RegistryError(
                    f"Bulk brand lookup failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            results_raw = data.get("results", {})
            results: dict[str, ResolvedBrand | None] = {d: None for d in domains}
            for domain, brand_data in results_raw.items():
                if brand_data is not None:
                    results[domain] = ResolvedBrand.model_validate(brand_data)
            return results
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(f"Bulk brand lookup timed out after {self._timeout}s") from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Bulk brand lookup failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(f"Bulk brand lookup failed: invalid response: {e}") from e

    async def lookup_property(self, domain: str) -> ResolvedProperty | None:
        """Resolve a publisher domain to its property info.

        Args:
            domain: Publisher domain to resolve (e.g., "nytimes.com").

        Returns:
            ResolvedProperty if found, None if the domain is not in the registry.

        Raises:
            RegistryError: On HTTP or parsing errors.
        """
        client = await self._get_client()
        try:
            response = await client.get(
                f"{self._base_url}/api/properties/resolve",
                params={"domain": domain},
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code == 404:
                return None
            if response.status_code != 200:
                raise RegistryError(
                    f"Property lookup failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            if data is None:
                return None
            return ResolvedProperty.model_validate(data)
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(f"Property lookup timed out after {self._timeout}s") from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Property lookup failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(f"Property lookup failed: invalid response: {e}") from e

    async def lookup_properties(self, domains: list[str]) -> dict[str, ResolvedProperty | None]:
        """Bulk resolve publisher domains to property info.

        Automatically chunks requests exceeding 100 domains.

        Args:
            domains: List of publisher domains to resolve.

        Returns:
            Dict mapping each domain to its ResolvedProperty, or None if not found.

        Raises:
            RegistryError: On HTTP or parsing errors.
        """
        if not domains:
            return {}

        chunks = [
            domains[i : i + MAX_BULK_DOMAINS] for i in range(0, len(domains), MAX_BULK_DOMAINS)
        ]

        chunk_results = await asyncio.gather(
            *[self._lookup_properties_chunk(chunk) for chunk in chunks]
        )

        merged: dict[str, ResolvedProperty | None] = {}
        for result in chunk_results:
            merged.update(result)
        return merged

    async def _lookup_properties_chunk(
        self, domains: list[str]
    ) -> dict[str, ResolvedProperty | None]:
        """Resolve a single chunk of property domains (max 100)."""
        client = await self._get_client()
        try:
            response = await client.post(
                f"{self._base_url}/api/properties/resolve/bulk",
                json={"domains": domains},
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code != 200:
                raise RegistryError(
                    f"Bulk property lookup failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            results_raw = data.get("results", {})
            results: dict[str, ResolvedProperty | None] = {d: None for d in domains}
            for domain, prop_data in results_raw.items():
                if prop_data is not None:
                    results[domain] = ResolvedProperty.model_validate(prop_data)
            return results
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(f"Bulk property lookup timed out after {self._timeout}s") from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Bulk property lookup failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(f"Bulk property lookup failed: invalid response: {e}") from e

    async def list_members(self, limit: int = 100) -> list[Member]:
        """List organizations registered in the AAO member directory.

        Args:
            limit: Maximum number of members to return.

        Returns:
            List of Member objects.

        Raises:
            RegistryError: On HTTP or parsing errors.
        """
        if limit < 1:
            raise ValueError(f"limit must be at least 1, got {limit}")

        client = await self._get_client()
        try:
            response = await client.get(
                f"{self._base_url}/api/members",
                params={"limit": limit},
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code != 200:
                raise RegistryError(
                    f"Member list failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            return [Member.model_validate(m) for m in data.get("members", [])]
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(f"Member list timed out after {self._timeout}s") from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Member list failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(f"Member list failed: invalid response: {e}") from e

    async def get_member(self, slug: str) -> Member | None:
        """Get a single AAO member by their slug.

        Args:
            slug: Member slug (e.g., "adgentek").

        Returns:
            Member if found, None if not in the registry.

        Raises:
            RegistryError: On HTTP or parsing errors.
            ValueError: If slug contains path-traversal characters.
        """
        if not slug or not re.fullmatch(r"[a-zA-Z0-9_-]+", slug):
            raise ValueError(f"Invalid member slug: {slug!r}")
        client = await self._get_client()
        try:
            response = await client.get(
                f"{self._base_url}/api/members/{slug}",
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code == 404:
                return None
            if response.status_code != 200:
                raise RegistryError(
                    f"Member lookup failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            if data is None:
                return None
            return Member.model_validate(data)
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(f"Member lookup timed out after {self._timeout}s") from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Member lookup failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(f"Member lookup failed: invalid response: {e}") from e

    # ========================================================================
    # Policy Registry Operations
    # ========================================================================

    async def list_policies(
        self,
        search: str | None = None,
        category: str | None = None,
        enforcement: str | None = None,
        jurisdiction: str | None = None,
        vertical: str | None = None,
        domain: str | None = None,
        limit: int = 20,
        offset: int = 0,
    ) -> list[PolicySummary]:
        """List governance policies with optional filtering.

        Args:
            search: Full-text search on policy name and description.
            category: Filter by category ("regulation" or "standard").
            enforcement: Filter by enforcement level ("must", "should", "may").
            jurisdiction: Filter by jurisdiction with region alias matching.
            vertical: Filter by industry vertical.
            domain: Filter by governance domain ("campaign", "creative", etc.).
            limit: Results per page (default 20, max 1000).
            offset: Pagination offset.

        Returns:
            List of PolicySummary objects.

        Raises:
            RegistryError: On HTTP or parsing errors.
        """
        client = await self._get_client()
        params: dict[str, str | int] = {"limit": limit, "offset": offset}
        if search is not None:
            params["search"] = search
        if category is not None:
            params["category"] = category
        if enforcement is not None:
            params["enforcement"] = enforcement
        if jurisdiction is not None:
            params["jurisdiction"] = jurisdiction
        if vertical is not None:
            params["vertical"] = vertical
        if domain is not None:
            params["domain"] = domain

        try:
            response = await client.get(
                f"{self._base_url}/api/policies/registry",
                params=params,
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code != 200:
                raise RegistryError(
                    f"Policy list failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            return [PolicySummary.model_validate(p) for p in data.get("policies", [])]
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(f"Policy list timed out after {self._timeout}s") from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Policy list failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(f"Policy list failed: invalid response: {e}") from e

    async def resolve_policy(
        self,
        policy_id: str,
        version: str | None = None,
    ) -> Policy | None:
        """Resolve a single policy by ID.

        Args:
            policy_id: Policy identifier (e.g., "gdpr_consent").
            version: Optional version pin; returns None if current version differs.

        Returns:
            Policy if found, None if not in the registry.

        Raises:
            RegistryError: On HTTP or parsing errors.
        """
        client = await self._get_client()
        params: dict[str, str] = {"policy_id": policy_id}
        if version is not None:
            params["version"] = version

        try:
            response = await client.get(
                f"{self._base_url}/api/policies/resolve",
                params=params,
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code == 404:
                return None
            if response.status_code != 200:
                raise RegistryError(
                    f"Policy resolve failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            if data is None:
                return None
            return Policy.model_validate(data)
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(f"Policy resolve timed out after {self._timeout}s") from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Policy resolve failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(f"Policy resolve failed: invalid response: {e}") from e

    async def resolve_policies(
        self,
        policy_ids: list[str],
    ) -> dict[str, Policy | None]:
        """Bulk resolve policies by ID.

        Automatically chunks requests exceeding 100 policy IDs.

        Args:
            policy_ids: List of policy identifiers to resolve.

        Returns:
            Dict mapping each policy_id to its Policy, or None if not found.

        Raises:
            RegistryError: On HTTP or parsing errors.
        """
        if not policy_ids:
            return {}

        chunks = [
            policy_ids[i : i + MAX_BULK_POLICIES]
            for i in range(0, len(policy_ids), MAX_BULK_POLICIES)
        ]

        chunk_results = await asyncio.gather(
            *[self._resolve_policies_chunk(chunk) for chunk in chunks]
        )

        merged: dict[str, Policy | None] = {}
        for result in chunk_results:
            merged.update(result)
        return merged

    async def _resolve_policies_chunk(
        self, policy_ids: list[str]
    ) -> dict[str, Policy | None]:
        """Resolve a single chunk of policy IDs (max 100)."""
        client = await self._get_client()
        try:
            response = await client.post(
                f"{self._base_url}/api/policies/resolve/bulk",
                json={"policy_ids": policy_ids},
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code != 200:
                raise RegistryError(
                    f"Bulk policy resolve failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            results_raw = data.get("results", {})
            results: dict[str, Policy | None] = {pid: None for pid in policy_ids}
            for pid, policy_data in results_raw.items():
                if policy_data is not None:
                    results[pid] = Policy.model_validate(policy_data)
            return results
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(
                f"Bulk policy resolve timed out after {self._timeout}s"
            ) from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Bulk policy resolve failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(
                f"Bulk policy resolve failed: invalid response: {e}"
            ) from e

    async def policy_history(
        self,
        policy_id: str,
        limit: int = 20,
        offset: int = 0,
    ) -> PolicyHistory | None:
        """Retrieve edit history for a policy.

        Args:
            policy_id: Policy identifier.
            limit: Maximum revisions to return (default 20, max 100).
            offset: Pagination offset.

        Returns:
            PolicyHistory if found, None if the policy doesn't exist.

        Raises:
            RegistryError: On HTTP or parsing errors.
        """
        client = await self._get_client()
        try:
            response = await client.get(
                f"{self._base_url}/api/policies/history",
                params={"policy_id": policy_id, "limit": limit, "offset": offset},
                headers={"User-Agent": self._user_agent},
                timeout=self._timeout,
            )
            if response.status_code == 404:
                return None
            if response.status_code != 200:
                raise RegistryError(
                    f"Policy history failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            data = response.json()
            if data is None:
                return None
            return PolicyHistory.model_validate(data)
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(
                f"Policy history timed out after {self._timeout}s"
            ) from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Policy history failed: {e}") from e
        except (ValidationError, ValueError) as e:
            raise RegistryError(
                f"Policy history failed: invalid response: {e}"
            ) from e

    async def save_policy(
        self,
        policy_id: str,
        version: str,
        name: str,
        category: str,
        enforcement: str,
        policy: str,
        *,
        auth_token: str,
        description: str | None = None,
        jurisdictions: list[str] | None = None,
        region_aliases: dict[str, list[str]] | None = None,
        verticals: list[str] | None = None,
        channels: list[str] | None = None,
        effective_date: str | None = None,
        sunset_date: str | None = None,
        governance_domains: list[str] | None = None,
        source_url: str | None = None,
        source_name: str | None = None,
        guidance: str | None = None,
        exemplars: dict[str, Any] | None = None,
        ext: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Create or update a community-contributed policy.

        Requires authentication. Cannot edit registry-sourced or pending policies.

        Args:
            policy_id: Policy identifier (lowercase alphanumeric with underscores).
            version: Semantic version string.
            name: Human-readable policy name.
            category: "regulation" or "standard".
            enforcement: "must", "should", or "may".
            policy: Natural language policy text.
            auth_token: API key for authentication.
            description: Policy description.
            jurisdictions: ISO jurisdiction codes.
            region_aliases: Region alias mappings (e.g., {"EU": ["DE", "FR"]}).
            verticals: Industry verticals.
            channels: Media channels.
            effective_date: ISO 8601 date when enforcement begins.
            sunset_date: ISO 8601 date when enforcement ends.
            governance_domains: Applicable domains ("campaign", "creative", etc.).
            source_url: URL of the source regulation/standard.
            source_name: Name of the source.
            guidance: Implementation guidance text.
            exemplars: Pass/fail calibration scenarios.
            ext: Extension data.

        Returns:
            Dict with success, message, policy_id, and revision_number.

        Raises:
            RegistryError: On HTTP or parsing errors (400, 401, 409, 429).
        """
        client = await self._get_client()
        body: dict[str, Any] = {
            "policy_id": policy_id,
            "version": version,
            "name": name,
            "category": category,
            "enforcement": enforcement,
            "policy": policy,
        }
        for key, value in [
            ("description", description),
            ("jurisdictions", jurisdictions),
            ("region_aliases", region_aliases),
            ("verticals", verticals),
            ("channels", channels),
            ("effective_date", effective_date),
            ("sunset_date", sunset_date),
            ("governance_domains", governance_domains),
            ("source_url", source_url),
            ("source_name", source_name),
            ("guidance", guidance),
            ("exemplars", exemplars),
            ("ext", ext),
        ]:
            if value is not None:
                body[key] = value

        try:
            response = await client.post(
                f"{self._base_url}/api/policies/save",
                json=body,
                headers={
                    "User-Agent": self._user_agent,
                    "Authorization": f"Bearer {auth_token}",
                },
                timeout=self._timeout,
            )
            if response.status_code != 200:
                raise RegistryError(
                    f"Policy save failed: HTTP {response.status_code}",
                    status_code=response.status_code,
                )
            result: dict[str, Any] = response.json()
            return result
        except RegistryError:
            raise
        except httpx.TimeoutException as e:
            raise RegistryError(
                f"Policy save timed out after {self._timeout}s"
            ) from e
        except httpx.HTTPError as e:
            raise RegistryError(f"Policy save failed: {e}") from e

    # ========================================================================
    # Brand Registry Operations
    # ========================================================================

    async def get_brand_json(
        self, domain: str, *, fresh: bool = False
    ) -> dict[str, Any] | None:
        """Fetch raw brand.json for a domain."""
        params: dict[str, Any] = {"domain": domain}
        if fresh:
            params["fresh"] = "true"
        resp = await self._request(
            "GET", "/api/brands/brand-json",
            params=params, allow_404=True,
            operation="Brand JSON fetch",
        )
        if resp is None:
            return None
        return cast(dict[str, Any], resp.json())

    async def save_brand(
        self,
        domain: str,
        brand_name: str,
        *,
        auth_token: str,
        brand_manifest: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Save or update a brand in the registry (auth required)."""
        body: dict[str, Any] = {"domain": domain, "brand_name": brand_name}
        if brand_manifest is not None:
            body["brand_manifest"] = brand_manifest
        resp = await self._request_ok(
            "POST", "/api/brands/save",
            json_body=body, auth_token=auth_token,
            operation="Brand save",
        )

        return cast(dict[str, Any], resp.json())

    async def list_brands(
        self,
        search: str | None = None,
        limit: int = 100,
        offset: int = 0,
    ) -> list[BrandRegistryItem]:
        """List brands in the registry."""
        params: dict[str, Any] = {"limit": limit, "offset": offset}
        if search is not None:
            params["search"] = search
        resp = await self._request_ok(
            "GET", "/api/brands/registry",
            params=params, operation="Brand list",
        )

        data = resp.json()
        return [self._parse(BrandRegistryItem, b, "Brand list") for b in data.get("brands", [])]

    async def brand_history(
        self,
        domain: str,
        limit: int = 20,
        offset: int = 0,
    ) -> BrandActivity | None:
        """Get edit history for a brand."""
        resp = await self._request(
            "GET", "/api/brands/history",
            params={"domain": domain, "limit": limit, "offset": offset},
            allow_404=True, operation="Brand history",
        )
        if resp is None:
            return None
        return self._parse(BrandActivity, resp.json(), "Brand history")

    async def enrich_brand(self, domain: str) -> dict[str, Any]:
        """Enrich brand data using Brandfetch."""
        resp = await self._request_ok(
            "GET", "/api/brands/enrich",
            params={"domain": domain}, operation="Brand enrich",
        )

        return cast(dict[str, Any], resp.json())

    # ========================================================================
    # Property Registry Operations
    # ========================================================================

    async def list_properties(
        self,
        search: str | None = None,
        limit: int = 100,
        offset: int = 0,
    ) -> list[PropertyRegistryItem]:
        """List properties in the registry."""
        params: dict[str, Any] = {"limit": limit, "offset": offset}
        if search is not None:
            params["search"] = search
        resp = await self._request_ok(
            "GET", "/api/properties/registry",
            params=params, operation="Property list",
        )

        data = resp.json()
        return [
            self._parse(PropertyRegistryItem, p, "Property list")
            for p in data.get("properties", [])
        ]

    async def validate_property(self, domain: str) -> ValidationResult:
        """Validate a domain's adagents.json configuration."""
        resp = await self._request_ok(
            "GET", "/api/properties/validate",
            params={"domain": domain}, operation="Property validate",
        )

        return self._parse(ValidationResult, resp.json(), "Property validate")

    async def save_property(
        self,
        publisher_domain: str,
        authorized_agents: list[dict[str, Any]],
        *,
        auth_token: str,
        properties: list[dict[str, Any]] | None = None,
        contact: dict[str, str] | None = None,
    ) -> dict[str, Any]:
        """Save or update a hosted property (auth required)."""
        body: dict[str, Any] = {
            "publisher_domain": publisher_domain,
            "authorized_agents": authorized_agents,
        }
        if properties is not None:
            body["properties"] = properties
        if contact is not None:
            body["contact"] = contact
        resp = await self._request_ok(
            "POST", "/api/properties/save",
            json_body=body, auth_token=auth_token,
            operation="Property save",
        )

        return cast(dict[str, Any], resp.json())

    async def property_history(
        self,
        domain: str,
        limit: int = 20,
        offset: int = 0,
    ) -> PropertyActivity | None:
        """Get edit history for a property."""
        resp = await self._request(
            "GET", "/api/properties/history",
            params={"domain": domain, "limit": limit, "offset": offset},
            allow_404=True, operation="Property history",
        )
        if resp is None:
            return None
        return self._parse(PropertyActivity, resp.json(), "Property history")

    async def check_property_list(
        self, domains: list[str]
    ) -> dict[str, Any]:
        """Check publisher domains against the registry."""
        resp = await self._request_ok(
            "POST", "/api/properties/check",
            json_body={"domains": domains}, operation="Property check",
        )

        return cast(dict[str, Any], resp.json())

    async def get_property_check_report(
        self, report_id: str
    ) -> dict[str, Any] | None:
        """Retrieve a property check report by ID."""
        resp = await self._request(
            "GET", f"/api/properties/check/{url_quote(report_id, safe='')}",
            allow_404=True, operation="Property check report",
        )
        if resp is None:
            return None
        return cast(dict[str, Any], resp.json())

    # ========================================================================
    # Agent Discovery
    # ========================================================================

    async def list_agents(
        self,
        *,
        type: str | None = None,
        health: bool = False,
        capabilities: bool = False,
        properties: bool = False,
        compliance: bool = False,
    ) -> list[FederatedAgentWithDetails]:
        """List registered and discovered agents."""
        params: dict[str, Any] = {}
        if type is not None:
            params["type"] = type
        if health:
            params["health"] = "true"
        if capabilities:
            params["capabilities"] = "true"
        if properties:
            params["properties"] = "true"
        if compliance:
            params["compliance"] = "true"
        resp = await self._request_ok(
            "GET", "/api/registry/agents",
            params=params, operation="Agent list",
        )

        data = resp.json()
        return [
            self._parse(FederatedAgentWithDetails, a, "Agent list")
            for a in data.get("agents", [])
        ]

    async def list_publishers(self) -> list[FederatedPublisher]:
        """List publishers in the registry."""
        resp = await self._request_ok(
            "GET", "/api/registry/publishers",
            operation="Publisher list",
        )

        data = resp.json()
        return [
            self._parse(FederatedPublisher, p, "Publisher list")
            for p in data.get("publishers", [])
        ]

    async def get_registry_stats(self) -> dict[str, Any]:
        """Get aggregate registry statistics."""
        resp = await self._request_ok(
            "GET", "/api/registry/stats",
            operation="Registry stats",
        )

        return cast(dict[str, Any], resp.json())

    async def search_agents(
        self,
        *,
        auth_token: str,
        channels: str | None = None,
        property_types: str | None = None,
        markets: str | None = None,
        categories: str | None = None,
        tags: str | None = None,
        delivery_types: str | None = None,
        has_tmp: bool | None = None,
        min_properties: int | None = None,
        cursor: str | None = None,
        limit: int = 50,
    ) -> dict[str, Any]:
        """Search agents by inventory profile (auth required)."""
        params: dict[str, Any] = {"limit": limit}
        for key, val in [
            ("channels", channels),
            ("property_types", property_types),
            ("markets", markets),
            ("categories", categories),
            ("tags", tags),
            ("delivery_types", delivery_types),
            ("cursor", cursor),
        ]:
            if val is not None:
                params[key] = val
        if has_tmp is not None:
            params["has_tmp"] = str(has_tmp).lower()
        if min_properties is not None:
            params["min_properties"] = min_properties
        resp = await self._request_ok(
            "GET", "/api/registry/agents/search",
            params=params, auth_token=auth_token,
            operation="Agent search",
        )

        return cast(dict[str, Any], resp.json())

    async def request_crawl(
        self, domain: str, *, auth_token: str
    ) -> dict[str, Any]:
        """Request a domain re-crawl (auth required)."""
        resp = await self._request_ok(
            "POST", "/api/registry/crawl-request",
            json_body={"domain": domain}, auth_token=auth_token,
            operation="Crawl request",
            expected_status={200, 202},
        )

        return cast(dict[str, Any], resp.json())

    # ========================================================================
    # Lookups & Authorization
    # ========================================================================

    async def lookup_domain(self, domain: str) -> DomainLookupResult:
        """Find all agents authorized for a publisher domain."""
        resp = await self._request_ok(
            "GET", f"/api/registry/lookup/domain/{url_quote(domain, safe='')}",
            operation="Domain lookup",
        )

        return self._parse(DomainLookupResult, resp.json(), "Domain lookup")

    async def lookup_property_identifier(
        self, type: str, value: str
    ) -> dict[str, Any]:
        """Find agents holding a specific property identifier."""
        resp = await self._request_ok(
            "GET", "/api/registry/lookup/property",
            params={"type": type, "value": value},
            operation="Property identifier lookup",
        )

        return cast(dict[str, Any], resp.json())

    async def get_agent_domains(self, agent_url: str) -> dict[str, Any]:
        """Get all publisher domains and identifiers for an agent."""
        encoded = url_quote(agent_url, safe="")
        resp = await self._request_ok(
            "GET", f"/api/registry/lookup/agent/{encoded}/domains",
            operation="Agent domains lookup",
        )

        return cast(dict[str, Any], resp.json())

    async def validate_product_authorization(
        self,
        agent_url: str,
        publisher_properties: list[dict[str, Any]],
    ) -> dict[str, Any]:
        """Check whether an agent is authorized to sell products."""
        resp = await self._request_ok(
            "POST", "/api/registry/validate/product-authorization",
            json_body={
                "agent_url": agent_url,
                "publisher_properties": publisher_properties,
            },
            operation="Product authorization",
        )

        return cast(dict[str, Any], resp.json())

    async def expand_product_identifiers(
        self,
        agent_url: str,
        publisher_properties: list[dict[str, Any]],
    ) -> dict[str, Any]:
        """Expand publisher_properties selectors into concrete identifiers."""
        resp = await self._request_ok(
            "POST", "/api/registry/expand/product-identifiers",
            json_body={
                "agent_url": agent_url,
                "publisher_properties": publisher_properties,
            },
            operation="Expand product identifiers",
        )

        return cast(dict[str, Any], resp.json())

    async def validate_property_authorization(
        self,
        agent_url: str,
        identifier_type: str,
        identifier_value: str,
    ) -> dict[str, Any]:
        """Quick check if a property identifier is authorized for an agent."""
        resp = await self._request_ok(
            "GET", "/api/registry/validate/property-authorization",
            params={
                "agent_url": agent_url,
                "identifier_type": identifier_type,
                "identifier_value": identifier_value,
            },
            operation="Property authorization",
        )

        return cast(dict[str, Any], resp.json())

    # ========================================================================
    # Validation Tools
    # ========================================================================

    async def validate_adagents(self, domain: str) -> dict[str, Any]:
        """Validate a domain's adagents.json via the registry API."""
        resp = await self._request_ok(
            "POST", "/api/adagents/validate",
            json_body={"domain": domain}, operation="Adagents validate",
        )

        return cast(dict[str, Any], resp.json())

    async def create_adagents(
        self,
        authorized_agents: list[dict[str, Any]],
        *,
        include_schema: bool = False,
        include_timestamp: bool = False,
        properties: list[Any] | None = None,
    ) -> dict[str, Any]:
        """Generate a valid adagents.json from authorized agents."""
        body: dict[str, Any] = {"authorized_agents": authorized_agents}
        if include_schema:
            body["include_schema"] = True
        if include_timestamp:
            body["include_timestamp"] = True
        if properties is not None:
            body["properties"] = properties
        resp = await self._request_ok(
            "POST", "/api/adagents/create",
            json_body=body, operation="Adagents create",
        )

        return cast(dict[str, Any], resp.json())

    # ========================================================================
    # Search
    # ========================================================================

    async def api_discovery(self) -> dict[str, Any]:
        """Get API discovery info (links to entry points and docs)."""
        resp = await self._request_ok(
            "GET", "/api", operation="API discovery",
        )

        return cast(dict[str, Any], resp.json())

    async def search(self, q: str) -> dict[str, Any]:
        """Search across brands, publishers, and properties."""
        resp = await self._request_ok(
            "GET", "/api/search",
            params={"q": q}, operation="Search",
        )

        return cast(dict[str, Any], resp.json())

    async def lookup_manifest_ref(
        self, domain: str, *, type: str | None = None
    ) -> dict[str, Any]:
        """Find the best manifest reference for a domain."""
        params: dict[str, Any] = {"domain": domain}
        if type is not None:
            params["type"] = type
        resp = await self._request_ok(
            "GET", "/api/manifest-refs/lookup",
            params=params, operation="Manifest ref lookup",
        )

        return cast(dict[str, Any], resp.json())

    # ========================================================================
    # Agent Probing
    # ========================================================================

    async def discover_agent(self, url: str) -> dict[str, Any]:
        """Probe an agent URL to discover its capabilities."""
        resp = await self._request_ok(
            "GET", "/api/public/discover-agent",
            params={"url": url}, operation="Agent discovery",
        )

        return cast(dict[str, Any], resp.json())

    async def get_agent_formats(self, url: str) -> dict[str, Any]:
        """Fetch creative formats from an agent."""
        resp = await self._request_ok(
            "GET", "/api/public/agent-formats",
            params={"url": url}, operation="Agent formats",
        )

        return cast(dict[str, Any], resp.json())

    async def get_agent_products(self, url: str) -> dict[str, Any]:
        """Fetch products from a sales agent."""
        resp = await self._request_ok(
            "GET", "/api/public/agent-products",
            params={"url": url}, operation="Agent products",
        )

        return cast(dict[str, Any], resp.json())

    async def validate_publisher(self, domain: str) -> dict[str, Any]:
        """Validate a publisher domain's adagents.json and return stats."""
        resp = await self._request_ok(
            "GET", "/api/public/validate-publisher",
            params={"domain": domain}, operation="Publisher validation",
        )

        return cast(dict[str, Any], resp.json())

    # ========================================================================
    # Change Feed
    # ========================================================================

    async def get_feed(
        self,
        *,
        auth_token: str,
        cursor: str | None = None,
        types: str | None = None,
        limit: int = 100,
    ) -> FeedPage:
        """Poll the registry change feed (auth required).

        Returns a FeedPage with events, cursor, and has_more.
        Pass cursor from previous response to resume.
        """
        params: dict[str, Any] = {"limit": limit}
        if cursor is not None:
            params["cursor"] = cursor
        if types is not None:
            params["types"] = types
        resp = await self._request_ok(
            "GET", "/api/registry/feed",
            params=params, auth_token=auth_token,
            operation="Feed poll",
        )

        return self._parse(FeedPage, resp.json(), "Feed poll")

Client for the AdCP registry API.

Provides brand, property, and member lookups against the central AdCP registry.

Args

base_url
Registry API base URL.
timeout
Request timeout in seconds.
client
Optional httpx.AsyncClient for connection pooling. If provided, caller is responsible for client lifecycle.
user_agent
User-Agent header for requests.

Methods

async def api_discovery(self) ‑> dict[str, typing.Any]
Expand source code
async def api_discovery(self) -> dict[str, Any]:
    """Get API discovery info (links to entry points and docs)."""
    resp = await self._request_ok(
        "GET", "/api", operation="API discovery",
    )

    return cast(dict[str, Any], resp.json())

Get API discovery info (links to entry points and docs).

async def brand_history(self, domain: str, limit: int = 20, offset: int = 0) ‑> BrandActivity | None
Expand source code
async def brand_history(
    self,
    domain: str,
    limit: int = 20,
    offset: int = 0,
) -> BrandActivity | None:
    """Get edit history for a brand."""
    resp = await self._request(
        "GET", "/api/brands/history",
        params={"domain": domain, "limit": limit, "offset": offset},
        allow_404=True, operation="Brand history",
    )
    if resp is None:
        return None
    return self._parse(BrandActivity, resp.json(), "Brand history")

Get edit history for a brand.

async def check_property_list(self, domains: list[str]) ‑> dict[str, typing.Any]
Expand source code
async def check_property_list(
    self, domains: list[str]
) -> dict[str, Any]:
    """Check publisher domains against the registry."""
    resp = await self._request_ok(
        "POST", "/api/properties/check",
        json_body={"domains": domains}, operation="Property check",
    )

    return cast(dict[str, Any], resp.json())

Check publisher domains against the registry.

async def close(self) ‑> None
Expand source code
async def close(self) -> None:
    """Close owned HTTP client. No-op if using external client."""
    if self._owned_client is not None:
        await self._owned_client.aclose()
        self._owned_client = None

Close owned HTTP client. No-op if using external client.

async def create_adagents(self,
authorized_agents: list[dict[str, Any]],
*,
include_schema: bool = False,
include_timestamp: bool = False,
properties: list[Any] | None = None) ‑> dict[str, typing.Any]
Expand source code
async def create_adagents(
    self,
    authorized_agents: list[dict[str, Any]],
    *,
    include_schema: bool = False,
    include_timestamp: bool = False,
    properties: list[Any] | None = None,
) -> dict[str, Any]:
    """Generate a valid adagents.json from authorized agents."""
    body: dict[str, Any] = {"authorized_agents": authorized_agents}
    if include_schema:
        body["include_schema"] = True
    if include_timestamp:
        body["include_timestamp"] = True
    if properties is not None:
        body["properties"] = properties
    resp = await self._request_ok(
        "POST", "/api/adagents/create",
        json_body=body, operation="Adagents create",
    )

    return cast(dict[str, Any], resp.json())

Generate a valid adagents.json from authorized agents.

async def discover_agent(self, url: str) ‑> dict[str, typing.Any]
Expand source code
async def discover_agent(self, url: str) -> dict[str, Any]:
    """Probe an agent URL to discover its capabilities."""
    resp = await self._request_ok(
        "GET", "/api/public/discover-agent",
        params={"url": url}, operation="Agent discovery",
    )

    return cast(dict[str, Any], resp.json())

Probe an agent URL to discover its capabilities.

async def enrich_brand(self, domain: str) ‑> dict[str, typing.Any]
Expand source code
async def enrich_brand(self, domain: str) -> dict[str, Any]:
    """Enrich brand data using Brandfetch."""
    resp = await self._request_ok(
        "GET", "/api/brands/enrich",
        params={"domain": domain}, operation="Brand enrich",
    )

    return cast(dict[str, Any], resp.json())

Enrich brand data using Brandfetch.

async def expand_product_identifiers(self, agent_url: str, publisher_properties: list[dict[str, Any]]) ‑> dict[str, typing.Any]
Expand source code
async def expand_product_identifiers(
    self,
    agent_url: str,
    publisher_properties: list[dict[str, Any]],
) -> dict[str, Any]:
    """Expand publisher_properties selectors into concrete identifiers."""
    resp = await self._request_ok(
        "POST", "/api/registry/expand/product-identifiers",
        json_body={
            "agent_url": agent_url,
            "publisher_properties": publisher_properties,
        },
        operation="Expand product identifiers",
    )

    return cast(dict[str, Any], resp.json())

Expand publisher_properties selectors into concrete identifiers.

async def get_agent_domains(self, agent_url: str) ‑> dict[str, typing.Any]
Expand source code
async def get_agent_domains(self, agent_url: str) -> dict[str, Any]:
    """Get all publisher domains and identifiers for an agent."""
    encoded = url_quote(agent_url, safe="")
    resp = await self._request_ok(
        "GET", f"/api/registry/lookup/agent/{encoded}/domains",
        operation="Agent domains lookup",
    )

    return cast(dict[str, Any], resp.json())

Get all publisher domains and identifiers for an agent.

async def get_agent_formats(self, url: str) ‑> dict[str, typing.Any]
Expand source code
async def get_agent_formats(self, url: str) -> dict[str, Any]:
    """Fetch creative formats from an agent."""
    resp = await self._request_ok(
        "GET", "/api/public/agent-formats",
        params={"url": url}, operation="Agent formats",
    )

    return cast(dict[str, Any], resp.json())

Fetch creative formats from an agent.

async def get_agent_products(self, url: str) ‑> dict[str, typing.Any]
Expand source code
async def get_agent_products(self, url: str) -> dict[str, Any]:
    """Fetch products from a sales agent."""
    resp = await self._request_ok(
        "GET", "/api/public/agent-products",
        params={"url": url}, operation="Agent products",
    )

    return cast(dict[str, Any], resp.json())

Fetch products from a sales agent.

async def get_brand_json(self, domain: str, *, fresh: bool = False) ‑> dict[str, typing.Any] | None
Expand source code
async def get_brand_json(
    self, domain: str, *, fresh: bool = False
) -> dict[str, Any] | None:
    """Fetch raw brand.json for a domain."""
    params: dict[str, Any] = {"domain": domain}
    if fresh:
        params["fresh"] = "true"
    resp = await self._request(
        "GET", "/api/brands/brand-json",
        params=params, allow_404=True,
        operation="Brand JSON fetch",
    )
    if resp is None:
        return None
    return cast(dict[str, Any], resp.json())

Fetch raw brand.json for a domain.

async def get_feed(self,
*,
auth_token: str,
cursor: str | None = None,
types: str | None = None,
limit: int = 100) ‑> FeedPage
Expand source code
async def get_feed(
    self,
    *,
    auth_token: str,
    cursor: str | None = None,
    types: str | None = None,
    limit: int = 100,
) -> FeedPage:
    """Poll the registry change feed (auth required).

    Returns a FeedPage with events, cursor, and has_more.
    Pass cursor from previous response to resume.
    """
    params: dict[str, Any] = {"limit": limit}
    if cursor is not None:
        params["cursor"] = cursor
    if types is not None:
        params["types"] = types
    resp = await self._request_ok(
        "GET", "/api/registry/feed",
        params=params, auth_token=auth_token,
        operation="Feed poll",
    )

    return self._parse(FeedPage, resp.json(), "Feed poll")

Poll the registry change feed (auth required).

Returns a FeedPage with events, cursor, and has_more. Pass cursor from previous response to resume.

async def get_member(self, slug: str) ‑> Member | None
Expand source code
async def get_member(self, slug: str) -> Member | None:
    """Get a single AAO member by their slug.

    Args:
        slug: Member slug (e.g., "adgentek").

    Returns:
        Member if found, None if not in the registry.

    Raises:
        RegistryError: On HTTP or parsing errors.
        ValueError: If slug contains path-traversal characters.
    """
    if not slug or not re.fullmatch(r"[a-zA-Z0-9_-]+", slug):
        raise ValueError(f"Invalid member slug: {slug!r}")
    client = await self._get_client()
    try:
        response = await client.get(
            f"{self._base_url}/api/members/{slug}",
            headers={"User-Agent": self._user_agent},
            timeout=self._timeout,
        )
        if response.status_code == 404:
            return None
        if response.status_code != 200:
            raise RegistryError(
                f"Member lookup failed: HTTP {response.status_code}",
                status_code=response.status_code,
            )
        data = response.json()
        if data is None:
            return None
        return Member.model_validate(data)
    except RegistryError:
        raise
    except httpx.TimeoutException as e:
        raise RegistryError(f"Member lookup timed out after {self._timeout}s") from e
    except httpx.HTTPError as e:
        raise RegistryError(f"Member lookup failed: {e}") from e
    except (ValidationError, ValueError) as e:
        raise RegistryError(f"Member lookup failed: invalid response: {e}") from e

Get a single AAO member by their slug.

Args

slug
Member slug (e.g., "adgentek").

Returns

Member if found, None if not in the registry.

Raises

RegistryError
On HTTP or parsing errors.
ValueError
If slug contains path-traversal characters.
async def get_property_check_report(self, report_id: str) ‑> dict[str, typing.Any] | None
Expand source code
async def get_property_check_report(
    self, report_id: str
) -> dict[str, Any] | None:
    """Retrieve a property check report by ID."""
    resp = await self._request(
        "GET", f"/api/properties/check/{url_quote(report_id, safe='')}",
        allow_404=True, operation="Property check report",
    )
    if resp is None:
        return None
    return cast(dict[str, Any], resp.json())

Retrieve a property check report by ID.

async def get_registry_stats(self) ‑> dict[str, typing.Any]
Expand source code
async def get_registry_stats(self) -> dict[str, Any]:
    """Get aggregate registry statistics."""
    resp = await self._request_ok(
        "GET", "/api/registry/stats",
        operation="Registry stats",
    )

    return cast(dict[str, Any], resp.json())

Get aggregate registry statistics.

async def list_agents(self,
*,
type: str | None = None,
health: bool = False,
capabilities: bool = False,
properties: bool = False,
compliance: bool = False) ‑> list[FederatedAgentWithDetails]
Expand source code
async def list_agents(
    self,
    *,
    type: str | None = None,
    health: bool = False,
    capabilities: bool = False,
    properties: bool = False,
    compliance: bool = False,
) -> list[FederatedAgentWithDetails]:
    """List registered and discovered agents."""
    params: dict[str, Any] = {}
    if type is not None:
        params["type"] = type
    if health:
        params["health"] = "true"
    if capabilities:
        params["capabilities"] = "true"
    if properties:
        params["properties"] = "true"
    if compliance:
        params["compliance"] = "true"
    resp = await self._request_ok(
        "GET", "/api/registry/agents",
        params=params, operation="Agent list",
    )

    data = resp.json()
    return [
        self._parse(FederatedAgentWithDetails, a, "Agent list")
        for a in data.get("agents", [])
    ]

List registered and discovered agents.

async def list_brands(self, search: str | None = None, limit: int = 100, offset: int = 0) ‑> list[BrandRegistryItem]
Expand source code
async def list_brands(
    self,
    search: str | None = None,
    limit: int = 100,
    offset: int = 0,
) -> list[BrandRegistryItem]:
    """List brands in the registry."""
    params: dict[str, Any] = {"limit": limit, "offset": offset}
    if search is not None:
        params["search"] = search
    resp = await self._request_ok(
        "GET", "/api/brands/registry",
        params=params, operation="Brand list",
    )

    data = resp.json()
    return [self._parse(BrandRegistryItem, b, "Brand list") for b in data.get("brands", [])]

List brands in the registry.

async def list_members(self, limit: int = 100) ‑> list[Member]
Expand source code
async def list_members(self, limit: int = 100) -> list[Member]:
    """List organizations registered in the AAO member directory.

    Args:
        limit: Maximum number of members to return.

    Returns:
        List of Member objects.

    Raises:
        RegistryError: On HTTP or parsing errors.
    """
    if limit < 1:
        raise ValueError(f"limit must be at least 1, got {limit}")

    client = await self._get_client()
    try:
        response = await client.get(
            f"{self._base_url}/api/members",
            params={"limit": limit},
            headers={"User-Agent": self._user_agent},
            timeout=self._timeout,
        )
        if response.status_code != 200:
            raise RegistryError(
                f"Member list failed: HTTP {response.status_code}",
                status_code=response.status_code,
            )
        data = response.json()
        return [Member.model_validate(m) for m in data.get("members", [])]
    except RegistryError:
        raise
    except httpx.TimeoutException as e:
        raise RegistryError(f"Member list timed out after {self._timeout}s") from e
    except httpx.HTTPError as e:
        raise RegistryError(f"Member list failed: {e}") from e
    except (ValidationError, ValueError) as e:
        raise RegistryError(f"Member list failed: invalid response: {e}") from e

List organizations registered in the AAO member directory.

Args

limit
Maximum number of members to return.

Returns

List of Member objects.

Raises

RegistryError
On HTTP or parsing errors.
async def list_policies(self,
search: str | None = None,
category: str | None = None,
enforcement: str | None = None,
jurisdiction: str | None = None,
vertical: str | None = None,
domain: str | None = None,
limit: int = 20,
offset: int = 0) ‑> list[PolicySummary]
Expand source code
async def list_policies(
    self,
    search: str | None = None,
    category: str | None = None,
    enforcement: str | None = None,
    jurisdiction: str | None = None,
    vertical: str | None = None,
    domain: str | None = None,
    limit: int = 20,
    offset: int = 0,
) -> list[PolicySummary]:
    """List governance policies with optional filtering.

    Args:
        search: Full-text search on policy name and description.
        category: Filter by category ("regulation" or "standard").
        enforcement: Filter by enforcement level ("must", "should", "may").
        jurisdiction: Filter by jurisdiction with region alias matching.
        vertical: Filter by industry vertical.
        domain: Filter by governance domain ("campaign", "creative", etc.).
        limit: Results per page (default 20, max 1000).
        offset: Pagination offset.

    Returns:
        List of PolicySummary objects.

    Raises:
        RegistryError: On HTTP or parsing errors.
    """
    client = await self._get_client()
    params: dict[str, str | int] = {"limit": limit, "offset": offset}
    if search is not None:
        params["search"] = search
    if category is not None:
        params["category"] = category
    if enforcement is not None:
        params["enforcement"] = enforcement
    if jurisdiction is not None:
        params["jurisdiction"] = jurisdiction
    if vertical is not None:
        params["vertical"] = vertical
    if domain is not None:
        params["domain"] = domain

    try:
        response = await client.get(
            f"{self._base_url}/api/policies/registry",
            params=params,
            headers={"User-Agent": self._user_agent},
            timeout=self._timeout,
        )
        if response.status_code != 200:
            raise RegistryError(
                f"Policy list failed: HTTP {response.status_code}",
                status_code=response.status_code,
            )
        data = response.json()
        return [PolicySummary.model_validate(p) for p in data.get("policies", [])]
    except RegistryError:
        raise
    except httpx.TimeoutException as e:
        raise RegistryError(f"Policy list timed out after {self._timeout}s") from e
    except httpx.HTTPError as e:
        raise RegistryError(f"Policy list failed: {e}") from e
    except (ValidationError, ValueError) as e:
        raise RegistryError(f"Policy list failed: invalid response: {e}") from e

List governance policies with optional filtering.

Args

search
Full-text search on policy name and description.
category
Filter by category ("regulation" or "standard").
enforcement
Filter by enforcement level ("must", "should", "may").
jurisdiction
Filter by jurisdiction with region alias matching.
vertical
Filter by industry vertical.
domain
Filter by governance domain ("campaign", "creative", etc.).
limit
Results per page (default 20, max 1000).
offset
Pagination offset.

Returns

List of PolicySummary objects.

Raises

RegistryError
On HTTP or parsing errors.
async def list_properties(self, search: str | None = None, limit: int = 100, offset: int = 0) ‑> list[PropertyRegistryItem]
Expand source code
async def list_properties(
    self,
    search: str | None = None,
    limit: int = 100,
    offset: int = 0,
) -> list[PropertyRegistryItem]:
    """List properties in the registry."""
    params: dict[str, Any] = {"limit": limit, "offset": offset}
    if search is not None:
        params["search"] = search
    resp = await self._request_ok(
        "GET", "/api/properties/registry",
        params=params, operation="Property list",
    )

    data = resp.json()
    return [
        self._parse(PropertyRegistryItem, p, "Property list")
        for p in data.get("properties", [])
    ]

List properties in the registry.

async def list_publishers(self) ‑> list[FederatedPublisher]
Expand source code
async def list_publishers(self) -> list[FederatedPublisher]:
    """List publishers in the registry."""
    resp = await self._request_ok(
        "GET", "/api/registry/publishers",
        operation="Publisher list",
    )

    data = resp.json()
    return [
        self._parse(FederatedPublisher, p, "Publisher list")
        for p in data.get("publishers", [])
    ]

List publishers in the registry.

async def lookup_brand(self, domain: str) ‑> ResolvedBrand | None
Expand source code
async def lookup_brand(self, domain: str) -> ResolvedBrand | None:
    """Resolve a domain to its brand identity.

    Works for any domain — brand houses, sub-brands, and operators
    (agencies, DSPs) are all brands in the registry.

    Args:
        domain: Domain to resolve (e.g., "nike.com", "wpp.com").

    Returns:
        ResolvedBrand if found, None if not in the registry.

    Raises:
        RegistryError: On HTTP or parsing errors.

    Example:
        brand = await registry.lookup_brand(request.brand.domain)
    """
    client = await self._get_client()
    try:
        response = await client.get(
            f"{self._base_url}/api/brands/resolve",
            params={"domain": domain},
            headers={"User-Agent": self._user_agent},
            timeout=self._timeout,
        )
        if response.status_code == 404:
            return None
        if response.status_code != 200:
            raise RegistryError(
                f"Brand lookup failed: HTTP {response.status_code}",
                status_code=response.status_code,
            )
        data = response.json()
        if data is None:
            return None
        return ResolvedBrand.model_validate(data)
    except RegistryError:
        raise
    except httpx.TimeoutException as e:
        raise RegistryError(f"Brand lookup timed out after {self._timeout}s") from e
    except httpx.HTTPError as e:
        raise RegistryError(f"Brand lookup failed: {e}") from e
    except (ValidationError, ValueError) as e:
        raise RegistryError(f"Brand lookup failed: invalid response: {e}") from e

Resolve a domain to its brand identity.

Works for any domain — brand houses, sub-brands, and operators (agencies, DSPs) are all brands in the registry.

Args

domain
Domain to resolve (e.g., "nike.com", "wpp.com").

Returns

ResolvedBrand if found, None if not in the registry.

Raises

RegistryError
On HTTP or parsing errors.

Example

brand = await registry.lookup_brand(request.brand.domain)

async def lookup_brands(self, domains: list[str]) ‑> dict[str, ResolvedBrand | None]
Expand source code
async def lookup_brands(self, domains: list[str]) -> dict[str, ResolvedBrand | None]:
    """Bulk resolve domains to brand identities.

    Automatically chunks requests exceeding 100 domains.

    Args:
        domains: List of domains to resolve.

    Returns:
        Dict mapping each domain to its ResolvedBrand, or None if not found.

    Raises:
        RegistryError: On HTTP or parsing errors.
    """
    if not domains:
        return {}

    chunks = [
        domains[i : i + MAX_BULK_DOMAINS] for i in range(0, len(domains), MAX_BULK_DOMAINS)
    ]

    chunk_results = await asyncio.gather(
        *[self._lookup_brands_chunk(chunk) for chunk in chunks]
    )

    merged: dict[str, ResolvedBrand | None] = {}
    for result in chunk_results:
        merged.update(result)
    return merged

Bulk resolve domains to brand identities.

Automatically chunks requests exceeding 100 domains.

Args

domains
List of domains to resolve.

Returns

Dict mapping each domain to its ResolvedBrand, or None if not found.

Raises

RegistryError
On HTTP or parsing errors.
async def lookup_domain(self, domain: str) ‑> DomainLookupResult
Expand source code
async def lookup_domain(self, domain: str) -> DomainLookupResult:
    """Find all agents authorized for a publisher domain."""
    resp = await self._request_ok(
        "GET", f"/api/registry/lookup/domain/{url_quote(domain, safe='')}",
        operation="Domain lookup",
    )

    return self._parse(DomainLookupResult, resp.json(), "Domain lookup")

Find all agents authorized for a publisher domain.

async def lookup_manifest_ref(self, domain: str, *, type: str | None = None) ‑> dict[str, typing.Any]
Expand source code
async def lookup_manifest_ref(
    self, domain: str, *, type: str | None = None
) -> dict[str, Any]:
    """Find the best manifest reference for a domain."""
    params: dict[str, Any] = {"domain": domain}
    if type is not None:
        params["type"] = type
    resp = await self._request_ok(
        "GET", "/api/manifest-refs/lookup",
        params=params, operation="Manifest ref lookup",
    )

    return cast(dict[str, Any], resp.json())

Find the best manifest reference for a domain.

async def lookup_properties(self, domains: list[str]) ‑> dict[str, ResolvedProperty | None]
Expand source code
async def lookup_properties(self, domains: list[str]) -> dict[str, ResolvedProperty | None]:
    """Bulk resolve publisher domains to property info.

    Automatically chunks requests exceeding 100 domains.

    Args:
        domains: List of publisher domains to resolve.

    Returns:
        Dict mapping each domain to its ResolvedProperty, or None if not found.

    Raises:
        RegistryError: On HTTP or parsing errors.
    """
    if not domains:
        return {}

    chunks = [
        domains[i : i + MAX_BULK_DOMAINS] for i in range(0, len(domains), MAX_BULK_DOMAINS)
    ]

    chunk_results = await asyncio.gather(
        *[self._lookup_properties_chunk(chunk) for chunk in chunks]
    )

    merged: dict[str, ResolvedProperty | None] = {}
    for result in chunk_results:
        merged.update(result)
    return merged

Bulk resolve publisher domains to property info.

Automatically chunks requests exceeding 100 domains.

Args

domains
List of publisher domains to resolve.

Returns

Dict mapping each domain to its ResolvedProperty, or None if not found.

Raises

RegistryError
On HTTP or parsing errors.
async def lookup_property(self, domain: str) ‑> ResolvedProperty | None
Expand source code
async def lookup_property(self, domain: str) -> ResolvedProperty | None:
    """Resolve a publisher domain to its property info.

    Args:
        domain: Publisher domain to resolve (e.g., "nytimes.com").

    Returns:
        ResolvedProperty if found, None if the domain is not in the registry.

    Raises:
        RegistryError: On HTTP or parsing errors.
    """
    client = await self._get_client()
    try:
        response = await client.get(
            f"{self._base_url}/api/properties/resolve",
            params={"domain": domain},
            headers={"User-Agent": self._user_agent},
            timeout=self._timeout,
        )
        if response.status_code == 404:
            return None
        if response.status_code != 200:
            raise RegistryError(
                f"Property lookup failed: HTTP {response.status_code}",
                status_code=response.status_code,
            )
        data = response.json()
        if data is None:
            return None
        return ResolvedProperty.model_validate(data)
    except RegistryError:
        raise
    except httpx.TimeoutException as e:
        raise RegistryError(f"Property lookup timed out after {self._timeout}s") from e
    except httpx.HTTPError as e:
        raise RegistryError(f"Property lookup failed: {e}") from e
    except (ValidationError, ValueError) as e:
        raise RegistryError(f"Property lookup failed: invalid response: {e}") from e

Resolve a publisher domain to its property info.

Args

domain
Publisher domain to resolve (e.g., "nytimes.com").

Returns

ResolvedProperty if found, None if the domain is not in the registry.

Raises

RegistryError
On HTTP or parsing errors.
async def lookup_property_identifier(self, type: str, value: str) ‑> dict[str, typing.Any]
Expand source code
async def lookup_property_identifier(
    self, type: str, value: str
) -> dict[str, Any]:
    """Find agents holding a specific property identifier."""
    resp = await self._request_ok(
        "GET", "/api/registry/lookup/property",
        params={"type": type, "value": value},
        operation="Property identifier lookup",
    )

    return cast(dict[str, Any], resp.json())

Find agents holding a specific property identifier.

async def policy_history(self, policy_id: str, limit: int = 20, offset: int = 0) ‑> PolicyHistory | None
Expand source code
async def policy_history(
    self,
    policy_id: str,
    limit: int = 20,
    offset: int = 0,
) -> PolicyHistory | None:
    """Retrieve edit history for a policy.

    Args:
        policy_id: Policy identifier.
        limit: Maximum revisions to return (default 20, max 100).
        offset: Pagination offset.

    Returns:
        PolicyHistory if found, None if the policy doesn't exist.

    Raises:
        RegistryError: On HTTP or parsing errors.
    """
    client = await self._get_client()
    try:
        response = await client.get(
            f"{self._base_url}/api/policies/history",
            params={"policy_id": policy_id, "limit": limit, "offset": offset},
            headers={"User-Agent": self._user_agent},
            timeout=self._timeout,
        )
        if response.status_code == 404:
            return None
        if response.status_code != 200:
            raise RegistryError(
                f"Policy history failed: HTTP {response.status_code}",
                status_code=response.status_code,
            )
        data = response.json()
        if data is None:
            return None
        return PolicyHistory.model_validate(data)
    except RegistryError:
        raise
    except httpx.TimeoutException as e:
        raise RegistryError(
            f"Policy history timed out after {self._timeout}s"
        ) from e
    except httpx.HTTPError as e:
        raise RegistryError(f"Policy history failed: {e}") from e
    except (ValidationError, ValueError) as e:
        raise RegistryError(
            f"Policy history failed: invalid response: {e}"
        ) from e

Retrieve edit history for a policy.

Args

policy_id
Policy identifier.
limit
Maximum revisions to return (default 20, max 100).
offset
Pagination offset.

Returns

PolicyHistory if found, None if the policy doesn't exist.

Raises

RegistryError
On HTTP or parsing errors.
async def property_history(self, domain: str, limit: int = 20, offset: int = 0) ‑> PropertyActivity | None
Expand source code
async def property_history(
    self,
    domain: str,
    limit: int = 20,
    offset: int = 0,
) -> PropertyActivity | None:
    """Get edit history for a property."""
    resp = await self._request(
        "GET", "/api/properties/history",
        params={"domain": domain, "limit": limit, "offset": offset},
        allow_404=True, operation="Property history",
    )
    if resp is None:
        return None
    return self._parse(PropertyActivity, resp.json(), "Property history")

Get edit history for a property.

async def request_crawl(self, domain: str, *, auth_token: str) ‑> dict[str, typing.Any]
Expand source code
async def request_crawl(
    self, domain: str, *, auth_token: str
) -> dict[str, Any]:
    """Request a domain re-crawl (auth required)."""
    resp = await self._request_ok(
        "POST", "/api/registry/crawl-request",
        json_body={"domain": domain}, auth_token=auth_token,
        operation="Crawl request",
        expected_status={200, 202},
    )

    return cast(dict[str, Any], resp.json())

Request a domain re-crawl (auth required).

async def resolve_policies(self, policy_ids: list[str]) ‑> dict[str, Policy | None]
Expand source code
async def resolve_policies(
    self,
    policy_ids: list[str],
) -> dict[str, Policy | None]:
    """Bulk resolve policies by ID.

    Automatically chunks requests exceeding 100 policy IDs.

    Args:
        policy_ids: List of policy identifiers to resolve.

    Returns:
        Dict mapping each policy_id to its Policy, or None if not found.

    Raises:
        RegistryError: On HTTP or parsing errors.
    """
    if not policy_ids:
        return {}

    chunks = [
        policy_ids[i : i + MAX_BULK_POLICIES]
        for i in range(0, len(policy_ids), MAX_BULK_POLICIES)
    ]

    chunk_results = await asyncio.gather(
        *[self._resolve_policies_chunk(chunk) for chunk in chunks]
    )

    merged: dict[str, Policy | None] = {}
    for result in chunk_results:
        merged.update(result)
    return merged

Bulk resolve policies by ID.

Automatically chunks requests exceeding 100 policy IDs.

Args

policy_ids
List of policy identifiers to resolve.

Returns

Dict mapping each policy_id to its Policy, or None if not found.

Raises

RegistryError
On HTTP or parsing errors.
async def resolve_policy(self, policy_id: str, version: str | None = None) ‑> Policy | None
Expand source code
async def resolve_policy(
    self,
    policy_id: str,
    version: str | None = None,
) -> Policy | None:
    """Resolve a single policy by ID.

    Args:
        policy_id: Policy identifier (e.g., "gdpr_consent").
        version: Optional version pin; returns None if current version differs.

    Returns:
        Policy if found, None if not in the registry.

    Raises:
        RegistryError: On HTTP or parsing errors.
    """
    client = await self._get_client()
    params: dict[str, str] = {"policy_id": policy_id}
    if version is not None:
        params["version"] = version

    try:
        response = await client.get(
            f"{self._base_url}/api/policies/resolve",
            params=params,
            headers={"User-Agent": self._user_agent},
            timeout=self._timeout,
        )
        if response.status_code == 404:
            return None
        if response.status_code != 200:
            raise RegistryError(
                f"Policy resolve failed: HTTP {response.status_code}",
                status_code=response.status_code,
            )
        data = response.json()
        if data is None:
            return None
        return Policy.model_validate(data)
    except RegistryError:
        raise
    except httpx.TimeoutException as e:
        raise RegistryError(f"Policy resolve timed out after {self._timeout}s") from e
    except httpx.HTTPError as e:
        raise RegistryError(f"Policy resolve failed: {e}") from e
    except (ValidationError, ValueError) as e:
        raise RegistryError(f"Policy resolve failed: invalid response: {e}") from e

Resolve a single policy by ID.

Args

policy_id
Policy identifier (e.g., "gdpr_consent").
version
Optional version pin; returns None if current version differs.

Returns

Policy if found, None if not in the registry.

Raises

RegistryError
On HTTP or parsing errors.
async def save_brand(self,
domain: str,
brand_name: str,
*,
auth_token: str,
brand_manifest: dict[str, Any] | None = None) ‑> dict[str, typing.Any]
Expand source code
async def save_brand(
    self,
    domain: str,
    brand_name: str,
    *,
    auth_token: str,
    brand_manifest: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Save or update a brand in the registry (auth required)."""
    body: dict[str, Any] = {"domain": domain, "brand_name": brand_name}
    if brand_manifest is not None:
        body["brand_manifest"] = brand_manifest
    resp = await self._request_ok(
        "POST", "/api/brands/save",
        json_body=body, auth_token=auth_token,
        operation="Brand save",
    )

    return cast(dict[str, Any], resp.json())

Save or update a brand in the registry (auth required).

async def save_policy(self,
policy_id: str,
version: str,
name: str,
category: str,
enforcement: str,
policy: str,
*,
auth_token: str,
description: str | None = None,
jurisdictions: list[str] | None = None,
region_aliases: dict[str, list[str]] | None = None,
verticals: list[str] | None = None,
channels: list[str] | None = None,
effective_date: str | None = None,
sunset_date: str | None = None,
governance_domains: list[str] | None = None,
source_url: str | None = None,
source_name: str | None = None,
guidance: str | None = None,
exemplars: dict[str, Any] | None = None,
ext: dict[str, Any] | None = None) ‑> dict[str, typing.Any]
Expand source code
async def save_policy(
    self,
    policy_id: str,
    version: str,
    name: str,
    category: str,
    enforcement: str,
    policy: str,
    *,
    auth_token: str,
    description: str | None = None,
    jurisdictions: list[str] | None = None,
    region_aliases: dict[str, list[str]] | None = None,
    verticals: list[str] | None = None,
    channels: list[str] | None = None,
    effective_date: str | None = None,
    sunset_date: str | None = None,
    governance_domains: list[str] | None = None,
    source_url: str | None = None,
    source_name: str | None = None,
    guidance: str | None = None,
    exemplars: dict[str, Any] | None = None,
    ext: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Create or update a community-contributed policy.

    Requires authentication. Cannot edit registry-sourced or pending policies.

    Args:
        policy_id: Policy identifier (lowercase alphanumeric with underscores).
        version: Semantic version string.
        name: Human-readable policy name.
        category: "regulation" or "standard".
        enforcement: "must", "should", or "may".
        policy: Natural language policy text.
        auth_token: API key for authentication.
        description: Policy description.
        jurisdictions: ISO jurisdiction codes.
        region_aliases: Region alias mappings (e.g., {"EU": ["DE", "FR"]}).
        verticals: Industry verticals.
        channels: Media channels.
        effective_date: ISO 8601 date when enforcement begins.
        sunset_date: ISO 8601 date when enforcement ends.
        governance_domains: Applicable domains ("campaign", "creative", etc.).
        source_url: URL of the source regulation/standard.
        source_name: Name of the source.
        guidance: Implementation guidance text.
        exemplars: Pass/fail calibration scenarios.
        ext: Extension data.

    Returns:
        Dict with success, message, policy_id, and revision_number.

    Raises:
        RegistryError: On HTTP or parsing errors (400, 401, 409, 429).
    """
    client = await self._get_client()
    body: dict[str, Any] = {
        "policy_id": policy_id,
        "version": version,
        "name": name,
        "category": category,
        "enforcement": enforcement,
        "policy": policy,
    }
    for key, value in [
        ("description", description),
        ("jurisdictions", jurisdictions),
        ("region_aliases", region_aliases),
        ("verticals", verticals),
        ("channels", channels),
        ("effective_date", effective_date),
        ("sunset_date", sunset_date),
        ("governance_domains", governance_domains),
        ("source_url", source_url),
        ("source_name", source_name),
        ("guidance", guidance),
        ("exemplars", exemplars),
        ("ext", ext),
    ]:
        if value is not None:
            body[key] = value

    try:
        response = await client.post(
            f"{self._base_url}/api/policies/save",
            json=body,
            headers={
                "User-Agent": self._user_agent,
                "Authorization": f"Bearer {auth_token}",
            },
            timeout=self._timeout,
        )
        if response.status_code != 200:
            raise RegistryError(
                f"Policy save failed: HTTP {response.status_code}",
                status_code=response.status_code,
            )
        result: dict[str, Any] = response.json()
        return result
    except RegistryError:
        raise
    except httpx.TimeoutException as e:
        raise RegistryError(
            f"Policy save timed out after {self._timeout}s"
        ) from e
    except httpx.HTTPError as e:
        raise RegistryError(f"Policy save failed: {e}") from e

Create or update a community-contributed policy.

Requires authentication. Cannot edit registry-sourced or pending policies.

Args

policy_id
Policy identifier (lowercase alphanumeric with underscores).
version
Semantic version string.
name
Human-readable policy name.
category
"regulation" or "standard".
enforcement
"must", "should", or "may".
policy
Natural language policy text.
auth_token
API key for authentication.
description
Policy description.
jurisdictions
ISO jurisdiction codes.
region_aliases
Region alias mappings (e.g., {"EU": ["DE", "FR"]}).
verticals
Industry verticals.
channels
Media channels.
effective_date
ISO 8601 date when enforcement begins.
sunset_date
ISO 8601 date when enforcement ends.
governance_domains
Applicable domains ("campaign", "creative", etc.).
source_url
URL of the source regulation/standard.
source_name
Name of the source.
guidance
Implementation guidance text.
exemplars
Pass/fail calibration scenarios.
ext
Extension data.

Returns

Dict with success, message, policy_id, and revision_number.

Raises

RegistryError
On HTTP or parsing errors (400, 401, 409, 429).
async def save_property(self,
publisher_domain: str,
authorized_agents: list[dict[str, Any]],
*,
auth_token: str,
properties: list[dict[str, Any]] | None = None,
contact: dict[str, str] | None = None) ‑> dict[str, typing.Any]
Expand source code
async def save_property(
    self,
    publisher_domain: str,
    authorized_agents: list[dict[str, Any]],
    *,
    auth_token: str,
    properties: list[dict[str, Any]] | None = None,
    contact: dict[str, str] | None = None,
) -> dict[str, Any]:
    """Save or update a hosted property (auth required)."""
    body: dict[str, Any] = {
        "publisher_domain": publisher_domain,
        "authorized_agents": authorized_agents,
    }
    if properties is not None:
        body["properties"] = properties
    if contact is not None:
        body["contact"] = contact
    resp = await self._request_ok(
        "POST", "/api/properties/save",
        json_body=body, auth_token=auth_token,
        operation="Property save",
    )

    return cast(dict[str, Any], resp.json())

Save or update a hosted property (auth required).

async def search(self, q: str) ‑> dict[str, typing.Any]
Expand source code
async def search(self, q: str) -> dict[str, Any]:
    """Search across brands, publishers, and properties."""
    resp = await self._request_ok(
        "GET", "/api/search",
        params={"q": q}, operation="Search",
    )

    return cast(dict[str, Any], resp.json())

Search across brands, publishers, and properties.

async def search_agents(self,
*,
auth_token: str,
channels: str | None = None,
property_types: str | None = None,
markets: str | None = None,
categories: str | None = None,
tags: str | None = None,
delivery_types: str | None = None,
has_tmp: bool | None = None,
min_properties: int | None = None,
cursor: str | None = None,
limit: int = 50) ‑> dict[str, typing.Any]
Expand source code
async def search_agents(
    self,
    *,
    auth_token: str,
    channels: str | None = None,
    property_types: str | None = None,
    markets: str | None = None,
    categories: str | None = None,
    tags: str | None = None,
    delivery_types: str | None = None,
    has_tmp: bool | None = None,
    min_properties: int | None = None,
    cursor: str | None = None,
    limit: int = 50,
) -> dict[str, Any]:
    """Search agents by inventory profile (auth required)."""
    params: dict[str, Any] = {"limit": limit}
    for key, val in [
        ("channels", channels),
        ("property_types", property_types),
        ("markets", markets),
        ("categories", categories),
        ("tags", tags),
        ("delivery_types", delivery_types),
        ("cursor", cursor),
    ]:
        if val is not None:
            params[key] = val
    if has_tmp is not None:
        params["has_tmp"] = str(has_tmp).lower()
    if min_properties is not None:
        params["min_properties"] = min_properties
    resp = await self._request_ok(
        "GET", "/api/registry/agents/search",
        params=params, auth_token=auth_token,
        operation="Agent search",
    )

    return cast(dict[str, Any], resp.json())

Search agents by inventory profile (auth required).

async def validate_adagents(self, domain: str) ‑> dict[str, typing.Any]
Expand source code
async def validate_adagents(self, domain: str) -> dict[str, Any]:
    """Validate a domain's adagents.json via the registry API."""
    resp = await self._request_ok(
        "POST", "/api/adagents/validate",
        json_body={"domain": domain}, operation="Adagents validate",
    )

    return cast(dict[str, Any], resp.json())

Validate a domain's adagents.json via the registry API.

async def validate_product_authorization(self, agent_url: str, publisher_properties: list[dict[str, Any]]) ‑> dict[str, typing.Any]
Expand source code
async def validate_product_authorization(
    self,
    agent_url: str,
    publisher_properties: list[dict[str, Any]],
) -> dict[str, Any]:
    """Check whether an agent is authorized to sell products."""
    resp = await self._request_ok(
        "POST", "/api/registry/validate/product-authorization",
        json_body={
            "agent_url": agent_url,
            "publisher_properties": publisher_properties,
        },
        operation="Product authorization",
    )

    return cast(dict[str, Any], resp.json())

Check whether an agent is authorized to sell products.

async def validate_property(self, domain: str) ‑> ValidationResult
Expand source code
async def validate_property(self, domain: str) -> ValidationResult:
    """Validate a domain's adagents.json configuration."""
    resp = await self._request_ok(
        "GET", "/api/properties/validate",
        params={"domain": domain}, operation="Property validate",
    )

    return self._parse(ValidationResult, resp.json(), "Property validate")

Validate a domain's adagents.json configuration.

async def validate_property_authorization(self, agent_url: str, identifier_type: str, identifier_value: str) ‑> dict[str, typing.Any]
Expand source code
async def validate_property_authorization(
    self,
    agent_url: str,
    identifier_type: str,
    identifier_value: str,
) -> dict[str, Any]:
    """Quick check if a property identifier is authorized for an agent."""
    resp = await self._request_ok(
        "GET", "/api/registry/validate/property-authorization",
        params={
            "agent_url": agent_url,
            "identifier_type": identifier_type,
            "identifier_value": identifier_value,
        },
        operation="Property authorization",
    )

    return cast(dict[str, Any], resp.json())

Quick check if a property identifier is authorized for an agent.

async def validate_publisher(self, domain: str) ‑> dict[str, typing.Any]
Expand source code
async def validate_publisher(self, domain: str) -> dict[str, Any]:
    """Validate a publisher domain's adagents.json and return stats."""
    resp = await self._request_ok(
        "GET", "/api/public/validate-publisher",
        params={"domain": domain}, operation="Publisher validation",
    )

    return cast(dict[str, Any], resp.json())

Validate a publisher domain's adagents.json and return stats.

class RegistryError (message: str, status_code: int | None = None)
Expand source code
class RegistryError(ADCPError):
    """Error from AdCP registry API operations (brand/property lookups)."""

    def __init__(self, message: str, status_code: int | None = None):
        """Initialize registry error."""
        self.status_code = status_code
        suggestion = "Check that the registry API is accessible and the domain is valid."
        super().__init__(message, suggestion=suggestion)

Error from AdCP registry API operations (brand/property lookups).

Initialize registry error.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException

Inherited members

class RegistrySync (client: RegistryClient,
*,
auth_token: str,
poll_interval: float = 60.0,
cursor_store: CursorStore | None = None,
types: str | None = None,
batch_size: int = 100)
Expand source code
class RegistrySync:
    """Polls the registry change feed and dispatches events to handlers.

    Args:
        client: RegistryClient instance for HTTP calls.
        auth_token: Bearer token for feed access.
        poll_interval: Seconds between polls (default 60).
        cursor_store: Optional CursorStore for persistence.
            Defaults to FileCursorStore.
        types: Optional event type filter (e.g., "property.*,agent.*").
        batch_size: Max events per poll (default 100, max 10000).
    """

    def __init__(
        self,
        client: RegistryClient,
        *,
        auth_token: str,
        poll_interval: float = 60.0,
        cursor_store: CursorStore | None = None,
        types: str | None = None,
        batch_size: int = 100,
    ) -> None:
        self._client = client
        self._auth_token = auth_token
        self._poll_interval = poll_interval
        self._cursor_store: CursorStore = cursor_store or FileCursorStore()
        self._types = types
        self._batch_size = min(batch_size, 10000)
        self._handlers: dict[str, list[ChangeHandler]] = defaultdict(list)
        self._all_handlers: list[ChangeHandler] = []
        self._cursor: str | None = None
        self._cursor_loaded = False
        self._stop_event: asyncio.Event | None = None
        self._running = False

    def on(self, event_type: str, handler: ChangeHandler) -> None:
        """Register a handler for a specific event type.

        Supports glob patterns: "property.*" matches "property.created",
        "property.updated", etc.
        """
        self._handlers[event_type].append(handler)

    def on_all(self, handler: ChangeHandler) -> None:
        """Register a handler for all events."""
        self._all_handlers.append(handler)

    @property
    def cursor(self) -> str | None:
        """Current cursor position."""
        return self._cursor

    async def _load_cursor(self) -> None:
        """Load cursor from store on first use."""
        if not self._cursor_loaded:
            self._cursor = await self._cursor_store.load()
            self._cursor_loaded = True

    async def _dispatch(self, event: FeedEvent) -> None:
        """Dispatch a single event to matching handlers."""
        # Dispatch to type-specific handlers
        for pattern, handlers in self._handlers.items():
            if fnmatch(event.event_type, pattern):
                for handler in handlers:
                    try:
                        await handler(event)
                    except Exception:
                        logger.exception(
                            "Handler error for event %s (%s)",
                            event.event_id,
                            event.event_type,
                        )

        # Dispatch to catch-all handlers
        for handler in self._all_handlers:
            try:
                await handler(event)
            except Exception:
                logger.exception(
                    "Handler error for event %s (%s)",
                    event.event_id,
                    event.event_type,
                )

    async def poll_once(self) -> list[FeedEvent]:
        """Poll the feed once and dispatch events.

        Returns the list of events processed.
        """
        await self._load_cursor()

        try:
            page = await self._client.get_feed(
                auth_token=self._auth_token,
                cursor=self._cursor,
                types=self._types,
                limit=self._batch_size,
            )
        except RegistryError as e:
            if e.status_code == 410:
                logger.warning("Feed cursor expired, resetting to start")
                self._cursor = None
                await self._cursor_store.save("")
                return []
            raise

        for event in page.events:
            await self._dispatch(event)

        if page.cursor:
            self._cursor = page.cursor
            await self._cursor_store.save(page.cursor)

        return list(page.events)

    async def start(self) -> None:
        """Start the polling loop. Runs until stop() is called."""
        if self._running:
            return

        self._running = True
        self._stop_event = asyncio.Event()
        logger.info("RegistrySync started (interval=%.1fs)", self._poll_interval)

        try:
            while not self._stop_event.is_set():
                try:
                    events = await self.poll_once()
                    if events:
                        logger.debug("Processed %d events", len(events))
                except RegistryError as e:
                    logger.error("Feed poll failed: %s", e)
                except Exception:
                    logger.exception("Unexpected error in feed poll")

                # Wait for interval or stop signal
                try:
                    await asyncio.wait_for(
                        self._stop_event.wait(),
                        timeout=self._poll_interval,
                    )
                except asyncio.TimeoutError:
                    pass  # Normal - poll interval elapsed
        finally:
            self._running = False
            logger.info("RegistrySync stopped")

    async def stop(self) -> None:
        """Stop the polling loop gracefully."""
        if self._stop_event is not None:
            self._stop_event.set()

Polls the registry change feed and dispatches events to handlers.

Args

client
RegistryClient instance for HTTP calls.
auth_token
Bearer token for feed access.
poll_interval
Seconds between polls (default 60).
cursor_store
Optional CursorStore for persistence. Defaults to FileCursorStore.
types
Optional event type filter (e.g., "property.,agent.").
batch_size
Max events per poll (default 100, max 10000).

Instance variables

prop cursor : str | None
Expand source code
@property
def cursor(self) -> str | None:
    """Current cursor position."""
    return self._cursor

Current cursor position.

Methods

def on(self, event_type: str, handler: ChangeHandler) ‑> None
Expand source code
def on(self, event_type: str, handler: ChangeHandler) -> None:
    """Register a handler for a specific event type.

    Supports glob patterns: "property.*" matches "property.created",
    "property.updated", etc.
    """
    self._handlers[event_type].append(handler)

Register a handler for a specific event type.

Supports glob patterns: "property.*" matches "property.created", "property.updated", etc.

def on_all(self, handler: ChangeHandler) ‑> None
Expand source code
def on_all(self, handler: ChangeHandler) -> None:
    """Register a handler for all events."""
    self._all_handlers.append(handler)

Register a handler for all events.

async def poll_once(self) ‑> list[FeedEvent]
Expand source code
async def poll_once(self) -> list[FeedEvent]:
    """Poll the feed once and dispatch events.

    Returns the list of events processed.
    """
    await self._load_cursor()

    try:
        page = await self._client.get_feed(
            auth_token=self._auth_token,
            cursor=self._cursor,
            types=self._types,
            limit=self._batch_size,
        )
    except RegistryError as e:
        if e.status_code == 410:
            logger.warning("Feed cursor expired, resetting to start")
            self._cursor = None
            await self._cursor_store.save("")
            return []
        raise

    for event in page.events:
        await self._dispatch(event)

    if page.cursor:
        self._cursor = page.cursor
        await self._cursor_store.save(page.cursor)

    return list(page.events)

Poll the feed once and dispatch events.

Returns the list of events processed.

async def start(self) ‑> None
Expand source code
async def start(self) -> None:
    """Start the polling loop. Runs until stop() is called."""
    if self._running:
        return

    self._running = True
    self._stop_event = asyncio.Event()
    logger.info("RegistrySync started (interval=%.1fs)", self._poll_interval)

    try:
        while not self._stop_event.is_set():
            try:
                events = await self.poll_once()
                if events:
                    logger.debug("Processed %d events", len(events))
            except RegistryError as e:
                logger.error("Feed poll failed: %s", e)
            except Exception:
                logger.exception("Unexpected error in feed poll")

            # Wait for interval or stop signal
            try:
                await asyncio.wait_for(
                    self._stop_event.wait(),
                    timeout=self._poll_interval,
                )
            except asyncio.TimeoutError:
                pass  # Normal - poll interval elapsed
    finally:
        self._running = False
        logger.info("RegistrySync stopped")

Start the polling loop. Runs until stop() is called.

async def stop(self) ‑> None
Expand source code
async def stop(self) -> None:
    """Stop the polling loop gracefully."""
    if self._stop_event is not None:
        self._stop_event.set()

Stop the polling loop gracefully.

class ReportPlanOutcomeRequest (**data: Any)
Expand source code
class ReportPlanOutcomeRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    plan_id: Annotated[str, Field(description='The plan this outcome is for.')]
    check_id: Annotated[
        str | None,
        Field(
            description="The check_id from check_governance. Links the outcome to the governance check that authorized it. Required for 'completed' and 'failed' outcomes."
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for this request. Prevents duplicate outcome reports on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    purchase_type: Annotated[
        purchase_type_1.PurchaseType | None,
        Field(
            description="The type of financial commitment this outcome is for. Determines which budget allocation (if any) to charge against. Defaults to 'media_buy' when omitted."
        ),
    ] = purchase_type_1.PurchaseType.media_buy
    outcome: Annotated[outcome_type.OutcomeType, Field(description='Outcome type.')]
    seller_response: Annotated[
        SellerResponse | None,
        Field(description="The seller's full response. Required when outcome is 'completed'."),
    ] = None
    delivery: Annotated[
        Delivery | None, Field(description="Delivery metrics. Required when outcome is 'delivery'.")
    ] = None
    error: Annotated[
        Error | None, Field(description="Error details. Required when outcome is 'failed'.")
    ] = None
    governance_context: Annotated[
        str,
        Field(
            description='Opaque governance context from the check_governance response that authorized this action. Enables the governance agent to correlate the outcome to the original check.',
            max_length=4096,
            min_length=1,
            pattern='^[\\x20-\\x7E]+$',
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var check_id : str | None
var contextContextObject | None
var deliveryDelivery | None
var errorError | None
var extExtensionObject | None
var governance_context : str
var idempotency_key : str
var model_config
var outcomeOutcomeType
var plan_id : str
var purchase_typePurchaseType | None
var seller_responseSellerResponse | None

Inherited members

class ReportPlanOutcomeResponse (**data: Any)
Expand source code
class ReportPlanOutcomeResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    outcome_id: Annotated[str, Field(description='Unique identifier for this outcome record.')]
    status: Annotated[
        Status,
        Field(
            description="'accepted' means state updated with no issues. 'findings' means issues were detected."
        ),
    ]
    committed_budget: Annotated[
        float | None,
        Field(
            description="Budget committed from this outcome. Present for 'completed' and 'failed' outcomes."
        ),
    ] = None
    findings: Annotated[
        list[Finding] | None,
        Field(description="Issues detected. Present only when status is 'findings'."),
    ] = None
    plan_summary: Annotated[
        PlanSummary | None,
        Field(
            description="Updated plan budget state. Present for 'completed' and 'failed' outcomes."
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var committed_budget : float | None
var contextContextObject | None
var extExtensionObject | None
var findings : list[Finding] | None
var model_config
var outcome_id : str
var plan_summaryPlanSummary | None
var statusStatus

Inherited members

class ReportUsageRequest (**data: Any)
Expand source code
class ReportUsageRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description="Client-generated unique key for this request. If a request with the same key has already been accepted, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request. Prevents duplicate billing on retries."
        ),
    ]
    reporting_period: Annotated[
        datetime_range.DatetimeRange,
        Field(
            description="The time range covered by this usage report. Applies to all records in the request."
        ),
    ]
    usage: Annotated[
        list[UsageItem],
        Field(
            description="One or more usage records. Each record is self-contained: it carries its own account, allowing a single request to span multiple accounts.",
            min_length=1,
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var idempotency_key : str
var model_config
var reporting_periodDatetimeRange
var usage : list[UsageItem]

Inherited members

class ReportUsageResponse (**data: Any)
Expand source code
class ReportUsageResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    accepted: Annotated[
        int, Field(description='Number of usage records successfully stored.', ge=0)
    ]
    errors: Annotated[
        list[error.Error] | None,
        Field(
            description="Validation errors for individual records. The field property identifies which record failed (e.g., 'usage[1].pricing_option_id')."
        ),
    ] = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, the account is a sandbox account and no billing occurred.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accepted : int
var contextContextObject | None
var errors : list[Error] | None
var extExtensionObject | None
var model_config
var sandbox : bool | None

Inherited members

class ResolvedBrand (**data: Any)
Expand source code
class ResolvedBrand(BaseModel):
    """Brand identity resolved from the AdCP registry."""

    model_config = ConfigDict(extra="allow")

    canonical_id: str
    canonical_domain: str
    brand_name: str
    names: list[dict[str, str]] | None = None
    keller_type: str | None = None
    parent_brand: str | None = None
    house_domain: str | None = None
    house_name: str | None = None
    brand_agent_url: str | None = None
    brand: dict[str, Any] | None = None
    source: str

Brand identity resolved from the AdCP registry.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var brand : dict[str, typing.Any] | None
var brand_agent_url : str | None
var brand_name : str
var canonical_domain : str
var canonical_id : str
var house_domain : str | None
var house_name : str | None
var keller_type : str | None
var model_config
var names : list[dict[str, str]] | None
var parent_brand : str | None
var source : str
class ResolvedProperty (**data: Any)
Expand source code
class ResolvedProperty(BaseModel):
    """Property information resolved from the AdCP registry."""

    model_config = ConfigDict(extra="allow")

    publisher_domain: str
    source: str
    authorized_agents: list[dict[str, Any]]
    properties: list[dict[str, Any]]
    verified: bool

Property information resolved from the AdCP registry.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var authorized_agents : list[dict[str, typing.Any]]
var model_config
var properties : list[dict[str, typing.Any]]
var publisher_domain : str
var source : str
var verified : bool
class SiSendActionResponseRequest (**data: Any)
Expand source code
class SiSendMessageRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for at-most-once execution. Each conversational turn is a distinct mutation of session transcript — without this key, a timeout-and-retry produces a duplicate turn and a duplicate model response. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each user turn.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    session_id: Annotated[str, Field(description='Active session identifier')]
    message: Annotated[str | None, Field(description="User's message to the brand agent")] = None
    action_response: Annotated[
        ActionResponse | None,
        Field(description='Response to a previous action_button (e.g., user clicked checkout)'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var action_responseActionResponse | None
var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var idempotency_key : str
var message : str | None
var model_config
var session_id : str
class SiSendTextMessageRequest (**data: Any)
Expand source code
class SiSendMessageRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for at-most-once execution. Each conversational turn is a distinct mutation of session transcript — without this key, a timeout-and-retry produces a duplicate turn and a duplicate model response. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each user turn.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    session_id: Annotated[str, Field(description='Active session identifier')]
    message: Annotated[str | None, Field(description="User's message to the brand agent")] = None
    action_response: Annotated[
        ActionResponse | None,
        Field(description='Response to a previous action_button (e.g., user clicked checkout)'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var action_responseActionResponse | None
var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var idempotency_key : str
var message : str | None
var model_config
var session_id : str

Inherited members

class SignalCatalogType (*args, **kwds)
Expand source code
class SignalCatalogType(Enum):
    marketplace = 'marketplace'
    custom = 'custom'
    owned = 'owned'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var custom
var marketplace
var owned
class SignalFilters (**data: Any)
Expand source code
class SignalFilters(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    catalog_types: Annotated[
        list[signal_catalog_type.SignalCatalogType] | None,
        Field(description='Filter by catalog type', min_length=1),
    ] = None
    data_providers: Annotated[
        list[str] | None, Field(description='Filter by specific data providers', min_length=1)
    ] = None
    max_cpm: Annotated[
        float | None,
        Field(description="Maximum CPM filter. Applies only to signals with model='cpm'.", ge=0.0),
    ] = None
    max_percent: Annotated[
        float | None,
        Field(
            description='Maximum percent-of-media rate filter. Signals where all percent_of_media pricing options exceed this value are excluded. Does not account for max_cpm caps.',
            ge=0.0,
            le=100.0,
        ),
    ] = None
    min_coverage_percentage: Annotated[
        float | None, Field(description='Minimum coverage requirement', ge=0.0, le=100.0)
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var catalog_types : list[SignalCatalogType] | None
var data_providers : list[str] | None
var max_cpm : float | None
var max_percent : float | None
var min_coverage_percentage : float | None
var model_config

Inherited members

class SignalPricingOption (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class SignalPricingOption(RootModel[vendor_pricing_option.VendorPricingOption]):
    root: Annotated[
        vendor_pricing_option.VendorPricingOption,
        Field(
            description='Deprecated — use vendor-pricing-option.json for new implementations. This alias is retained for backward compatibility.',
            title='Signal Pricing Option',
        ),
    ]

Usage Documentation

RootModel and Custom Root Types

A Pydantic BaseModel for the root object of the model.

Attributes

root
The root object of the model.
__pydantic_root_model__
Whether the model is a RootModel.
__pydantic_private__
Private fields in the model.
__pydantic_extra__
Extra fields in the model.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.root_model.RootModel[VendorPricingOption]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootVendorPricingOption
class Snapshot (**data: Any)
Expand source code
class Snapshot(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    as_of: Annotated[
        AwareDatetime, Field(description='When this snapshot was captured by the platform')
    ]
    staleness_seconds: Annotated[
        int,
        Field(
            description='Maximum age of this data in seconds. For example, 3600 means the data may be up to 1 hour old.',
            ge=0,
        ),
    ]
    impressions: Annotated[
        int,
        Field(
            description='Lifetime impressions across all assignments. Not scoped to any date range.',
            ge=0,
        ),
    ]
    last_served: Annotated[
        AwareDatetime | None,
        Field(
            description='Last time this creative served an impression. Absent when the creative has never served.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var as_of : pydantic.types.AwareDatetime
var impressions : int
var last_served : pydantic.types.AwareDatetime | None
var model_config
var staleness_seconds : int

Inherited members

class SnapshotUnavailableReason (*args, **kwds)
Expand source code
class SnapshotUnavailableReason(Enum):
    SNAPSHOT_UNSUPPORTED = 'SNAPSHOT_UNSUPPORTED'
    SNAPSHOT_TEMPORARILY_UNAVAILABLE = 'SNAPSHOT_TEMPORARILY_UNAVAILABLE'
    SNAPSHOT_PERMISSION_DENIED = 'SNAPSHOT_PERMISSION_DENIED'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var SNAPSHOT_PERMISSION_DENIED
var SNAPSHOT_TEMPORARILY_UNAVAILABLE
var SNAPSHOT_UNSUPPORTED
class MediaBuyDeliveryStatus (*args, **kwds)
Expand source code
class Status(Enum):
    pending_creatives = 'pending_creatives'
    pending_start = 'pending_start'
    pending = 'pending'
    active = 'active'
    paused = 'paused'
    completed = 'completed'
    rejected = 'rejected'
    canceled = 'canceled'
    failed = 'failed'
    reporting_delayed = 'reporting_delayed'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var active
var canceled
var completed
var failed
var paused
var pending
var pending_creatives
var pending_start
var rejected
var reporting_delayed
class SyncAccountsRequest (**data: Any)
Expand source code
class SyncAccountsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for at-most-once execution. Natural per-account upsert keys (brand, operator) handle resource-level dedup, but the envelope triggers onboarding webhooks, billing setup, and audit events — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    accounts: Annotated[
        list[Account], Field(description='Advertiser accounts to sync', max_length=1000)
    ]
    delete_missing: Annotated[
        bool | None,
        Field(
            description='When true, accounts previously synced by this agent but not included in this request will be deactivated. Scoped to the authenticated agent — does not affect accounts managed by other agents. Use with caution.'
        ),
    ] = False
    dry_run: Annotated[
        bool | None,
        Field(
            description='When true, preview what would change without applying. Returns what would be created/updated/deactivated.'
        ),
    ] = False
    push_notification_config: Annotated[
        push_notification_config_1.PushNotificationConfig | None,
        Field(
            description='Webhook for async notifications when account status changes (e.g., pending_approval transitions to active).'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accounts : list[Account]
var adcp_major_version : int | None
var contextContextObject | None
var delete_missing : bool | None
var dry_run : bool | None
var extExtensionObject | None
var idempotency_key : str
var model_config
var push_notification_configPushNotificationConfig | None

Inherited members

class SyncAccountsSuccessResponse (**data: Any)
Expand source code
class SyncAccountsResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    dry_run: Annotated[
        bool | None, Field(description='Whether this was a dry run (no actual changes made)')
    ] = None
    accounts: Annotated[list[Account], Field(description='Results for each account processed')]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accounts : list[Account]
var contextContextObject | None
var dry_run : bool | None
var extExtensionObject | None
var model_config

Inherited members

class SyncAccountsErrorResponse (**data: Any)
Expand source code
class SyncAccountsResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[
        list[error.Error],
        Field(
            description='Operation-level errors (e.g., authentication failure, service unavailable)',
            min_length=1,
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class SyncAudiencesRequest (**data: Any)
Expand source code
class SyncAudiencesRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for at-most-once execution. `audience_id` gives resource-level dedup per audience, but the sync envelope emits audit events and may trigger downstream refreshes — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `audiences` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    account: Annotated[
        account_ref.AccountReference, Field(description='Account to manage audiences for.')
    ]
    audiences: Annotated[
        list[Audience] | None,
        Field(
            description='Audiences to sync (create or update). When omitted, the call is discovery-only and returns all existing audiences on the account without modification.',
            min_length=1,
        ),
    ] = None
    delete_missing: Annotated[
        bool | None,
        Field(
            description='When true, buyer-managed audiences on the account not included in this sync will be removed. Does not affect seller-managed audiences. Do not combine with an omitted audiences array or all buyer-managed audiences will be deleted.'
        ),
    ] = False
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var audiences : list[Audience] | None
var contextContextObject | None
var delete_missing : bool | None
var extExtensionObject | None
var idempotency_key : str
var model_config

Inherited members

class SyncAudiencesSuccessResponse (**data: Any)
Expand source code
class SyncAudiencesResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    audiences: Annotated[
        list[Audience], Field(description='Results for each audience on the account')
    ]
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var audiences : list[Audience]
var contextContextObject | None
var extExtensionObject | None
var model_config
var sandbox : bool | None

Inherited members

class SyncAudiencesErrorResponse (**data: Any)
Expand source code
class SyncAudiencesResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[
        list[error.Error],
        Field(description='Operation-level errors that prevented processing', min_length=1),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class SyncCatalogsInputRequired (**data: Any)
Expand source code
class SyncCatalogsInputRequired(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    reason: Annotated[
        Reason | None,
        Field(
            description='Reason code indicating why buyer input is needed. APPROVAL_REQUIRED: platform requires explicit approval before activating the catalog. FEED_VALIDATION: feed URL returned unexpected format or schema errors. ITEM_REVIEW: platform flagged items for manual review. FEED_ACCESS: platform cannot access the feed URL (authentication, CORS, etc.).'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var reasonReason | None

Inherited members

class SyncCatalogsRequest (**data: Any)
Expand source code
class SyncCatalogsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for at-most-once execution. `catalog_id` gives resource-level dedup per catalog, but the sync envelope emits audit events and triggers platform review for large feeds — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `catalogs` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    account: Annotated[
        account_ref.AccountReference, Field(description='Account that owns these catalogs.')
    ]
    catalogs: Annotated[
        list[catalog.Catalog] | None,
        Field(
            description='Array of catalog feeds to sync (create or update). When omitted, the call is discovery-only and returns all existing catalogs on the account without modification.',
            max_length=50,
            min_length=1,
        ),
    ] = None
    catalog_ids: Annotated[
        list[str] | None,
        Field(
            description='Optional filter to limit sync scope to specific catalog IDs. When provided, only these catalogs will be created/updated. Other catalogs on the account are unaffected.',
            max_length=50,
            min_length=1,
        ),
    ] = None
    delete_missing: Annotated[
        bool | None,
        Field(
            description='When true, buyer-managed catalogs on the account not included in this sync will be removed. Does not affect seller-managed catalogs. Do not combine with an omitted catalogs array or all buyer-managed catalogs will be deleted.'
        ),
    ] = False
    dry_run: Annotated[
        bool | None,
        Field(
            description='When true, preview changes without applying them. Returns what would be created/updated/deleted.'
        ),
    ] = False
    validation_mode: Annotated[
        validation_mode_1.ValidationMode | None,
        Field(
            description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid catalogs and reports errors."
        ),
    ] = validation_mode_1.ValidationMode.strict
    push_notification_config: Annotated[
        push_notification_config_1.PushNotificationConfig | None,
        Field(
            description='Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (common for large feeds requiring platform review).'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var catalog_ids : list[str] | None
var catalogs : list[Catalog] | None
var contextContextObject | None
var delete_missing : bool | None
var dry_run : bool | None
var extExtensionObject | None
var idempotency_key : str
var model_config
var push_notification_configPushNotificationConfig | None
var validation_modeValidationMode | None

Inherited members

class SyncCatalogsSuccessResponse (**data: Any)
Expand source code
class SyncCatalogsResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    dry_run: Annotated[
        bool | None, Field(description='Whether this was a dry run (no actual changes made)')
    ] = None
    catalogs: Annotated[
        list[Catalog],
        Field(
            description="Results for each catalog processed. Items with action='failed' indicate per-catalog validation/processing failures, not operation-level failures."
        ),
    ]
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var catalogs : list[Catalog]
var contextContextObject | None
var dry_run : bool | None
var extExtensionObject | None
var model_config
var sandbox : bool | None

Inherited members

class SyncCatalogsErrorResponse (**data: Any)
Expand source code
class SyncCatalogsResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[
        list[error.Error],
        Field(
            description='Operation-level errors that prevented processing any catalogs (e.g., authentication failure, service unavailable, invalid request format)',
            min_length=1,
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class SyncCatalogsSubmitted (**data: Any)
Expand source code
class SyncCatalogsSubmitted(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config

Inherited members

class SyncCatalogsWorking (**data: Any)
Expand source code
class SyncCatalogsWorking(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    percentage: Annotated[
        float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0)
    ] = None
    current_step: Annotated[
        str | None,
        Field(
            description="Current step or phase of the operation (e.g., 'Fetching product feed', 'Validating items', 'Platform review')"
        ),
    ] = None
    total_steps: Annotated[
        int | None, Field(description='Total number of steps in the operation', ge=1)
    ] = None
    step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None
    catalogs_processed: Annotated[
        int | None, Field(description='Number of catalogs processed so far', ge=0)
    ] = None
    catalogs_total: Annotated[
        int | None, Field(description='Total number of catalogs to process', ge=0)
    ] = None
    items_processed: Annotated[
        int | None,
        Field(description='Total number of catalog items processed across all catalogs', ge=0),
    ] = None
    items_total: Annotated[
        int | None,
        Field(description='Total number of catalog items to process across all catalogs', ge=0),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var catalogs_processed : int | None
var catalogs_total : int | None
var contextContextObject | None
var current_step : str | None
var extExtensionObject | None
var items_processed : int | None
var items_total : int | None
var model_config
var percentage : float | None
var step_number : int | None
var total_steps : int | None

Inherited members

class SyncCreativesRequest (**data: Any)
Expand source code
class SyncCreativesRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference, Field(description='Account that owns these creatives.')
    ]
    creatives: Annotated[
        list[creative_asset.CreativeAsset],
        Field(
            description='Array of creative assets to sync (create or update)',
            max_length=100,
            min_length=1,
        ),
    ]
    creative_ids: Annotated[
        list[str] | None,
        Field(
            description='Optional filter to limit sync scope to specific creative IDs. When provided, only these creatives will be created/updated. Other creatives in the library are unaffected. Useful for partial updates and error recovery.',
            max_length=100,
            min_length=1,
        ),
    ] = None
    assignments: Annotated[
        list[Assignment] | None,
        Field(
            description='Optional bulk assignment of creatives to packages. Each entry maps one creative to one package with optional weight and placement targeting. Standalone creative agents that do not manage media buys ignore this field.',
            min_length=1,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated idempotency key for safe retries. If a sync fails without a response, resending with the same idempotency_key guarantees at-most-once execution. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    delete_missing: Annotated[
        bool | None,
        Field(
            description='When true, creatives not included in this sync will be archived. Use with caution for full library replacement. Invalid when creative_ids is provided — delete_missing applies to the entire library scope, not a filtered subset.'
        ),
    ] = False
    dry_run: Annotated[
        bool | None,
        Field(
            description='When true, preview changes without applying them. Returns what would be created/updated/deleted.'
        ),
    ] = False
    validation_mode: Annotated[
        validation_mode_1.ValidationMode | None,
        Field(
            description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors."
        ),
    ] = validation_mode_1.ValidationMode.strict
    push_notification_config: Annotated[
        push_notification_config_1.PushNotificationConfig | None,
        Field(
            description='Optional webhook configuration for async sync notifications. The agent will send a webhook when sync completes if the operation takes longer than immediate response time (typically for large bulk operations or manual approval/HITL).'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var assignments : list[Assignment] | None
var contextContextObject | None
var creative_ids : list[str] | None
var creatives : list[CreativeAsset]
var delete_missing : bool | None
var dry_run : bool | None
var extExtensionObject | None
var idempotency_key : str
var model_config
var push_notification_configPushNotificationConfig | None
var validation_modeValidationMode | None

Inherited members

class SyncCreativesSuccessResponse (**data: Any)
Expand source code
class SyncCreativesResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    dry_run: Annotated[
        bool | None, Field(description="Whether this was a dry run (no actual changes made)")
    ] = None
    creatives: Annotated[
        list[Creative],
        Field(
            description="Results for each creative processed. Items with action='failed' indicate per-item validation/processing failures, not operation-level failures."
        ),
    ]
    sandbox: Annotated[
        bool | None,
        Field(description="When true, this response contains simulated data from sandbox mode."),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var creatives : list[Creative]
var dry_run : bool | None
var extExtensionObject | None
var model_config
var sandbox : bool | None

Inherited members

class SyncCreativesErrorResponse (**data: Any)
Expand source code
class SyncCreativesResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    errors: Annotated[
        list[error.Error],
        Field(
            description="Operation-level errors that prevented processing any creatives (e.g., authentication failure, service unavailable, invalid request format)",
            min_length=1,
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class SyncEventSourcesRequest (**data: Any)
Expand source code
class SyncEventSourcesRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for at-most-once execution. `event_source_id` gives resource-level dedup per source, but the sync envelope emits audit events and can trigger downstream pixel provisioning — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `event_sources` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    account: Annotated[
        account_ref.AccountReference, Field(description='Account to configure event sources for.')
    ]
    event_sources: Annotated[
        list[EventSource] | None,
        Field(
            description='Event sources to sync (create or update). When omitted, the call is discovery-only and returns all existing event sources on the account without modification.',
            min_length=1,
        ),
    ] = None
    delete_missing: Annotated[
        bool | None,
        Field(description='When true, event sources not included in this sync will be removed'),
    ] = False
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var contextContextObject | None
var delete_missing : bool | None
var event_sources : list[EventSource] | None
var extExtensionObject | None
var idempotency_key : str
var model_config

Inherited members

class SyncEventSourcesSuccessResponse (**data: Any)
Expand source code
class SyncEventSourcesResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    event_sources: Annotated[
        list[EventSource],
        Field(
            description='Results for each event source, including both synced and seller-managed sources on the account'
        ),
    ]
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var event_sources : list[EventSource]
var extExtensionObject | None
var model_config
var sandbox : bool | None

Inherited members

class SyncEventSourcesErrorResponse (**data: Any)
Expand source code
class SyncEventSourcesResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[
        list[error.Error],
        Field(description='Operation-level errors that prevented processing', min_length=1),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class SyncPlansRequest (**data: Any)
Expand source code
class SyncPlansRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated unique key for at-most-once execution. `plan_id` gives resource-level dedup per plan, but the sync envelope emits audit events and can trigger governance reapproval — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    plans: Annotated[list[Plan], Field(description='One or more campaign plans to sync.')]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var adcp_major_version : int | None
var contextContextObject | None
var extExtensionObject | None
var idempotency_key : str
var model_config
var plans : list[Plan]

Inherited members

class SyncPlansResponse (**data: Any)
Expand source code
class SyncPlansResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra="forbid",
    )
    plans: Annotated[list[Plan], Field(description="Status for each synced plan.")]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var plans : list[Plan]

Inherited members

class TargetingOverlay (**data: Any)
Expand source code
class TargetingOverlay(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    geo_countries: Annotated[
        list[GeoCountry] | None,
        Field(
            description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').",
            min_length=1,
        ),
    ] = None
    geo_countries_exclude: Annotated[
        list[GeoCountriesExcludeItem] | None,
        Field(
            description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').",
            min_length=1,
        ),
    ] = None
    geo_regions: Annotated[
        list[GeoRegion] | None,
        Field(
            description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').",
            min_length=1,
        ),
    ] = None
    geo_regions_exclude: Annotated[
        list[GeoRegionsExcludeItem] | None,
        Field(
            description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').",
            min_length=1,
        ),
    ] = None
    geo_metros: Annotated[
        list[GeoMetro] | None,
        Field(
            description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None
    geo_metros_exclude: Annotated[
        list[GeoMetrosExcludeItem] | None,
        Field(
            description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None
    geo_postal_areas: Annotated[
        list[GeoPostalArea] | None,
        Field(
            description='Restrict delivery to specific postal areas. Each entry specifies the postal system and target values. Seller must declare supported systems in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None
    geo_postal_areas_exclude: Annotated[
        list[GeoPostalAreasExcludeItem] | None,
        Field(
            description='Exclude specific postal areas from delivery. Each entry specifies the postal system and excluded values. Seller must declare supported systems in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None
    daypart_targets: Annotated[
        list[daypart_target.DaypartTarget] | None,
        Field(
            description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.',
            min_length=1,
        ),
    ] = None
    axe_include_segment: Annotated[
        str | None,
        Field(
            deprecated=True,
            description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.',
        ),
    ] = None
    axe_exclude_segment: Annotated[
        str | None,
        Field(
            deprecated=True,
            description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.',
        ),
    ] = None
    audience_include: Annotated[
        list[str] | None,
        Field(
            description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None
    audience_exclude: Annotated[
        list[str] | None,
        Field(
            description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None
    frequency_cap: frequency_cap_1.FrequencyCap | None = None
    property_list: Annotated[
        property_list_ref.PropertyListReference | None,
        Field(
            description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false."
        ),
    ] = None
    collection_list: Annotated[
        collection_list_ref.CollectionListReference | None,
        Field(
            description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.'
        ),
    ] = None
    collection_list_exclude: Annotated[
        collection_list_ref.CollectionListReference | None,
        Field(
            description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities."
        ),
    ] = None
    age_restriction: Annotated[
        AgeRestriction | None,
        Field(
            description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.'
        ),
    ] = None
    device_platform: Annotated[
        list[device_platform_1.DevicePlatform] | None,
        Field(
            description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.',
            min_length=1,
        ),
    ] = None
    device_type: Annotated[
        list[device_type_1.DeviceType] | None,
        Field(
            description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).',
            min_length=1,
        ),
    ] = None
    device_type_exclude: Annotated[
        list[device_type_1.DeviceType] | None,
        Field(
            description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).',
            min_length=1,
        ),
    ] = None
    store_catchments: Annotated[
        list[StoreCatchment] | None,
        Field(
            description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.',
            min_length=1,
        ),
    ] = None
    geo_proximity: Annotated[
        list[GeoProximity | GeoProximity18 | GeoProximity19] | None,
        Field(
            description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None
    language: Annotated[
        list[LanguageItem] | None,
        Field(
            description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').",
            min_length=1,
        ),
    ] = None
    keyword_targets: Annotated[
        list[KeywordTarget] | None,
        Field(
            description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None
    negative_keywords: Annotated[
        list[NegativeKeyword] | None,
        Field(
            description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var age_restrictionAgeRestriction | None
var audience_exclude : list[str] | None
var audience_include : list[str] | None
var collection_listCollectionListReference | None
var collection_list_excludeCollectionListReference | None
var daypart_targets : list[DaypartTarget] | None
var device_platform : list[DevicePlatform] | None
var device_type : list[DeviceType] | None
var device_type_exclude : list[DeviceType] | None
var frequency_capFrequencyCap | None
var geo_countries : list[GeoCountry] | None
var geo_countries_exclude : list[GeoCountriesExcludeItem] | None
var geo_metros : list[GeoMetro] | None
var geo_metros_exclude : list[GeoMetrosExcludeItem] | None
var geo_postal_areas : list[GeoPostalArea] | None
var geo_postal_areas_exclude : list[GeoPostalAreasExcludeItem] | None
var geo_proximity : list[GeoProximity | GeoProximity18 | GeoProximity19] | None
var geo_regions : list[GeoRegion] | None
var geo_regions_exclude : list[GeoRegionsExcludeItem] | None
var keyword_targets : list[KeywordTarget] | None
var language : list[LanguageItem] | None
var model_config
var negative_keywords : list[NegativeKeyword] | None
var property_listPropertyListReference | None
var store_catchments : list[StoreCatchment] | None

Instance variables

var axe_exclude_segment : str | None
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any:
    if obj is None:
        if self.wrapped_property is not None:
            return self.wrapped_property.__get__(None, obj_type)
        raise AttributeError(self.field_name)

    warnings.warn(self.msg, DeprecationWarning, stacklevel=2)

    if self.wrapped_property is not None:
        return self.wrapped_property.__get__(obj, obj_type)
    return obj.__dict__[self.field_name]

Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.

Attributes

msg
The deprecation message to be emitted.
wrapped_property
The property instance if the deprecated field is a computed field, or None.
field_name
The name of the field being deprecated.
var axe_include_segment : str | None
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any:
    if obj is None:
        if self.wrapped_property is not None:
            return self.wrapped_property.__get__(None, obj_type)
        raise AttributeError(self.field_name)

    warnings.warn(self.msg, DeprecationWarning, stacklevel=2)

    if self.wrapped_property is not None:
        return self.wrapped_property.__get__(obj, obj_type)
    return obj.__dict__[self.field_name]

Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.

Attributes

msg
The deprecation message to be emitted.
wrapped_property
The property instance if the deprecated field is a computed field, or None.
field_name
The name of the field being deprecated.

Inherited members

class TaskResult (**data: Any)
Expand source code
class TaskResult(BaseModel, Generic[T]):
    """Result from task execution."""

    model_config = ConfigDict(arbitrary_types_allowed=True)

    status: TaskStatus
    data: T | None = None
    message: str | None = None  # Human-readable message from agent (e.g., MCP content text)
    submitted: SubmittedInfo | None = None
    needs_input: NeedsInputInfo | None = None
    error: str | None = None
    success: bool = Field(default=True)
    metadata: dict[str, Any] | None = None
    debug_info: DebugInfo | None = None
    # The full idempotency_key the SDK used for this request — echoed here so
    # buyers can correlate against their own records. SENSITIVE inside the
    # seller's replay_ttl_seconds window (serves as a retry-pattern oracle);
    # do not emit to shared logs. The SDK's debug capture redacts keys by
    # default; avoid ``model_dump_json()``-ing a TaskResult into shared sinks.
    idempotency_key: str | None = None
    # True when the seller returned a cached response for a replayed key.
    # Agents that emit side effects on success (notifications, memory writes,
    # downstream tool calls) must check this flag and suppress duplicates.
    replayed: bool = False

Result from task execution.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel
  • typing.Generic

Subclasses

  • adcp.types.core.TaskResult[AdcpAsyncResponseData]
  • adcp.types.core.TaskResult[CheckGovernanceResponse]
  • adcp.types.core.TaskResult[ContextMatchResponse]
  • adcp.types.core.TaskResult[CreateCollectionListResponse]
  • adcp.types.core.TaskResult[CreatePropertyListResponse]
  • adcp.types.core.TaskResult[DeleteCollectionListResponse]
  • adcp.types.core.TaskResult[DeletePropertyListResponse]
  • adcp.types.core.TaskResult[GetAdcpCapabilitiesResponse]
  • adcp.types.core.TaskResult[GetCollectionListResponse]
  • adcp.types.core.TaskResult[GetCreativeDeliveryResponse]
  • adcp.types.core.TaskResult[GetMediaBuyDeliveryResponse]
  • adcp.types.core.TaskResult[GetMediaBuysResponse]
  • adcp.types.core.TaskResult[GetPlanAuditLogsResponse]
  • adcp.types.core.TaskResult[GetProductsResponse]
  • adcp.types.core.TaskResult[GetPropertyListResponse]
  • adcp.types.core.TaskResult[GetSignalsResponse]
  • adcp.types.core.TaskResult[IdentityMatchResponse]
  • adcp.types.core.TaskResult[ListAccountsResponse]
  • adcp.types.core.TaskResult[ListCollectionListsResponse]
  • adcp.types.core.TaskResult[ListCreativeFormatsResponse]
  • adcp.types.core.TaskResult[ListCreativesResponse]
  • adcp.types.core.TaskResult[ListPropertyListsResponse]
  • adcp.types.core.TaskResult[ReportPlanOutcomeResponse]
  • adcp.types.core.TaskResult[ReportUsageResponse]
  • adcp.types.core.TaskResult[SiGetOfferingResponse]
  • adcp.types.core.TaskResult[SiInitiateSessionResponse]
  • adcp.types.core.TaskResult[SiSendMessageResponse]
  • adcp.types.core.TaskResult[SiTerminateSessionResponse]
  • adcp.types.core.TaskResult[SyncPlansResponse]
  • adcp.types.core.TaskResult[Union[AcquireRightsResponse1, AcquireRightsResponse2, AcquireRightsResponse3, AcquireRightsResponse4]]
  • adcp.types.core.TaskResult[Union[ActivateSignalResponse1, ActivateSignalResponse2]]
  • adcp.types.core.TaskResult[Union[BuildCreativeResponse1, BuildCreativeResponse2, BuildCreativeResponse3]]
  • adcp.types.core.TaskResult[Union[CalibrateContentResponse1, CalibrateContentResponse2]]
  • adcp.types.core.TaskResult[Union[ComplyTestControllerResponse1, ComplyTestControllerResponse2, ComplyTestControllerResponse3, ComplyTestControllerResponse4]]
  • adcp.types.core.TaskResult[Union[CreateContentStandardsResponse1, CreateContentStandardsResponse2]]
  • adcp.types.core.TaskResult[Union[CreateMediaBuyResponse1, CreateMediaBuyResponse2, CreateMediaBuyResponse3]]
  • adcp.types.core.TaskResult[Union[GetAccountFinancialsResponse1, GetAccountFinancialsResponse2]]
  • adcp.types.core.TaskResult[Union[GetBrandIdentityResponse1, GetBrandIdentityResponse2]]
  • adcp.types.core.TaskResult[Union[GetContentStandardsResponse1, GetContentStandardsResponse2]]
  • adcp.types.core.TaskResult[Union[GetCreativeFeaturesResponse1, GetCreativeFeaturesResponse2]]
  • adcp.types.core.TaskResult[Union[GetMediaBuyArtifactsResponse1, GetMediaBuyArtifactsResponse2]]
  • adcp.types.core.TaskResult[Union[GetRightsResponse1, GetRightsResponse2]]
  • adcp.types.core.TaskResult[Union[ListContentStandardsResponse1, ListContentStandardsResponse2]]
  • adcp.types.core.TaskResult[Union[LogEventResponse1, LogEventResponse2]]
  • adcp.types.core.TaskResult[Union[PreviewCreativeResponse1, PreviewCreativeResponse2, PreviewCreativeResponse3]]
  • adcp.types.core.TaskResult[Union[ProvidePerformanceFeedbackResponse1, ProvidePerformanceFeedbackResponse2]]
  • adcp.types.core.TaskResult[Union[SyncAccountsResponse1, SyncAccountsResponse2]]
  • adcp.types.core.TaskResult[Union[SyncAudiencesResponse1, SyncAudiencesResponse2]]
  • adcp.types.core.TaskResult[Union[SyncCatalogsResponse1, SyncCatalogsResponse2]]
  • adcp.types.core.TaskResult[Union[SyncCreativesResponse1, SyncCreativesResponse2, SyncCreativesResponse3]]
  • adcp.types.core.TaskResult[Union[SyncEventSourcesResponse1, SyncEventSourcesResponse2]]
  • adcp.types.core.TaskResult[Union[SyncGovernanceResponse1, SyncGovernanceResponse2]]
  • adcp.types.core.TaskResult[Union[UpdateContentStandardsResponse1, UpdateContentStandardsResponse2]]
  • adcp.types.core.TaskResult[Union[UpdateMediaBuyResponse1, UpdateMediaBuyResponse2]]
  • adcp.types.core.TaskResult[Union[UpdateRightsResponse1, UpdateRightsResponse2]]
  • adcp.types.core.TaskResult[Union[ValidateContentDeliveryResponse1, ValidateContentDeliveryResponse2]]
  • adcp.types.core.TaskResult[UpdateCollectionListResponse]
  • adcp.types.core.TaskResult[UpdatePropertyListResponse]

Class variables

var data : ~T | None
var debug_infoDebugInfo | None
var error : str | None
var idempotency_key : str | None
var message : str | None
var metadata : dict[str, typing.Any] | None
var model_config
var needs_inputNeedsInputInfo | None
var replayed : bool
var statusTaskStatus
var submittedSubmittedInfo | None
var success : bool
class TaskStatus (*args, **kwds)
Expand source code
class TaskStatus(str, Enum):
    """Task execution status."""

    COMPLETED = "completed"
    SUBMITTED = "submitted"
    NEEDS_INPUT = "needs_input"
    FAILED = "failed"
    WORKING = "working"

Task execution status.

Ancestors

  • builtins.str
  • enum.Enum

Class variables

var COMPLETED
var FAILED
var NEEDS_INPUT
var SUBMITTED
var WORKING
class GeneratedTaskStatus (*args, **kwds)
Expand source code
class TaskStatus(Enum):
    submitted = 'submitted'
    working = 'working'
    input_required = 'input-required'
    completed = 'completed'
    canceled = 'canceled'
    failed = 'failed'
    rejected = 'rejected'
    auth_required = 'auth-required'
    unknown = 'unknown'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var auth_required
var canceled
var completed
var failed
var input_required
var rejected
var submitted
var unknown
var working
class TimeBasedPricingOption (**data: Any)
Expand source code
class TimeBasedPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[
        Literal['time'],
        Field(description='Cost per time unit - rate scales with campaign duration'),
    ]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    parameters: Annotated[Parameters, Field(description='Time-based pricing parameters')]
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var min_spend_per_package : float | None
var model_config
var parametersParameters
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['time']
var pricing_option_id : str

Inherited members

class TimeUnit (*args, **kwds)
Expand source code
class TimeUnit(Enum):
    hour = 'hour'
    day = 'day'
    week = 'week'
    month = 'month'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var day
var hour
var month
var week
class Transform (*args, **kwds)
Expand source code
class Transform(Enum):
    date = 'date'
    divide = 'divide'
    boolean = 'boolean'
    split = 'split'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var boolean
var date
var divide
var split
class UpdateContentStandardsSuccessResponse (**data: Any)
Expand source code
class UpdateContentStandardsResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    success: Annotated[
        Literal[True], Field(description='Indicates the update was applied successfully')
    ]
    standards_id: Annotated[str, Field(description='ID of the updated standards configuration')]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var standards_id : str
var success : Literal[True]

Inherited members

class UpdateContentStandardsErrorResponse (**data: Any)
Expand source code
class UpdateContentStandardsResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    success: Annotated[Literal[False], Field(description='Indicates the update failed')]
    errors: Annotated[
        list[error.Error], Field(description='Errors that occurred during the update', min_length=1)
    ]
    conflicting_standards_id: Annotated[
        str | None,
        Field(
            description='If scope change conflicts with another configuration, the ID of the conflicting standards'
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var conflicting_standards_id : str | None
var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config
var success : Literal[False]

Inherited members

class UpdateFrequency (*args, **kwds)
Expand source code
class UpdateFrequency(Enum):
    realtime = 'realtime'
    hourly = 'hourly'
    daily = 'daily'
    weekly = 'weekly'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var daily
var hourly
var realtime
var weekly
class UpdateMediaBuyRequest (**data: Any)
Expand source code
class UpdateMediaBuyRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference,
        Field(
            description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.'
        ),
    ]
    media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")]
    revision: Annotated[
        int | None,
        Field(
            description="Expected current revision for optimistic concurrency. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match. Obtain from get_media_buys or the most recent update response.",
            ge=1,
        ),
    ] = None
    paused: Annotated[
        bool | None,
        Field(description='Pause/resume the entire media buy (true = paused, false = active)'),
    ] = None
    canceled: Annotated[
        Literal[True],
        Field(
            description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.'
        ),
    ] = True
    cancellation_reason: Annotated[
        str | None,
        Field(
            description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.',
            max_length=500,
        ),
    ] = None
    start_time: start_timing.StartTiming | None = None
    end_time: Annotated[
        AwareDatetime | None, Field(description='New end date/time in ISO 8601 format')
    ] = None
    packages: Annotated[
        list[package_update.PackageUpdate] | None,
        Field(description='Package-specific updates for existing packages', min_length=1),
    ] = None
    invoice_recipient: Annotated[
        business_entity.BusinessEntity | None,
        Field(
            description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request."
        ),
    ] = None
    new_packages: Annotated[
        list[package_request.PackageRequest] | None,
        Field(
            description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. Sellers that support mid-flight package additions advertise add_packages in valid_actions. Sellers that do not support this MUST reject with UNSUPPORTED_FEATURE.',
            min_length=1,
        ),
    ] = None
    reporting_webhook: Annotated[
        reporting_webhook_1.ReportingWebhook | None,
        Field(
            description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.'
        ),
    ] = None
    push_notification_config: Annotated[
        push_notification_config_1.PushNotificationConfig | None,
        Field(
            description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.'
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var canceled : Literal[True]
var cancellation_reason : str | None
var contextContextObject | None
var end_time : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var idempotency_key : str
var invoice_recipientBusinessEntity | None
var media_buy_id : str
var model_config
var new_packages : list[PackageRequest] | None
var packages : list[PackageUpdate] | None
var paused : bool | None
var push_notification_configPushNotificationConfig | None
var reporting_webhookReportingWebhook | None
var revision : int | None
var start_timeStartTiming | None
class UpdateMediaBuyPackagesRequest (**data: Any)
Expand source code
class UpdateMediaBuyRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference,
        Field(
            description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.'
        ),
    ]
    media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")]
    revision: Annotated[
        int | None,
        Field(
            description="Expected current revision for optimistic concurrency. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match. Obtain from get_media_buys or the most recent update response.",
            ge=1,
        ),
    ] = None
    paused: Annotated[
        bool | None,
        Field(description='Pause/resume the entire media buy (true = paused, false = active)'),
    ] = None
    canceled: Annotated[
        Literal[True],
        Field(
            description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.'
        ),
    ] = True
    cancellation_reason: Annotated[
        str | None,
        Field(
            description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.',
            max_length=500,
        ),
    ] = None
    start_time: start_timing.StartTiming | None = None
    end_time: Annotated[
        AwareDatetime | None, Field(description='New end date/time in ISO 8601 format')
    ] = None
    packages: Annotated[
        list[package_update.PackageUpdate] | None,
        Field(description='Package-specific updates for existing packages', min_length=1),
    ] = None
    invoice_recipient: Annotated[
        business_entity.BusinessEntity | None,
        Field(
            description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request."
        ),
    ] = None
    new_packages: Annotated[
        list[package_request.PackageRequest] | None,
        Field(
            description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. Sellers that support mid-flight package additions advertise add_packages in valid_actions. Sellers that do not support this MUST reject with UNSUPPORTED_FEATURE.',
            min_length=1,
        ),
    ] = None
    reporting_webhook: Annotated[
        reporting_webhook_1.ReportingWebhook | None,
        Field(
            description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.'
        ),
    ] = None
    push_notification_config: Annotated[
        push_notification_config_1.PushNotificationConfig | None,
        Field(
            description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.'
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var canceled : Literal[True]
var cancellation_reason : str | None
var contextContextObject | None
var end_time : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var idempotency_key : str
var invoice_recipientBusinessEntity | None
var media_buy_id : str
var model_config
var new_packages : list[PackageRequest] | None
var packages : list[PackageUpdate] | None
var paused : bool | None
var push_notification_configPushNotificationConfig | None
var reporting_webhookReportingWebhook | None
var revision : int | None
var start_timeStartTiming | None
class UpdateMediaBuyPropertiesRequest (**data: Any)
Expand source code
class UpdateMediaBuyRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    adcp_major_version: Annotated[
        int | None,
        Field(
            description="The AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.",
            ge=1,
            le=99,
        ),
    ] = None
    account: Annotated[
        account_ref.AccountReference,
        Field(
            description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.'
        ),
    ]
    media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")]
    revision: Annotated[
        int | None,
        Field(
            description="Expected current revision for optimistic concurrency. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match. Obtain from get_media_buys or the most recent update response.",
            ge=1,
        ),
    ] = None
    paused: Annotated[
        bool | None,
        Field(description='Pause/resume the entire media buy (true = paused, false = active)'),
    ] = None
    canceled: Annotated[
        Literal[True],
        Field(
            description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.'
        ),
    ] = True
    cancellation_reason: Annotated[
        str | None,
        Field(
            description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.',
            max_length=500,
        ),
    ] = None
    start_time: start_timing.StartTiming | None = None
    end_time: Annotated[
        AwareDatetime | None, Field(description='New end date/time in ISO 8601 format')
    ] = None
    packages: Annotated[
        list[package_update.PackageUpdate] | None,
        Field(description='Package-specific updates for existing packages', min_length=1),
    ] = None
    invoice_recipient: Annotated[
        business_entity.BusinessEntity | None,
        Field(
            description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request."
        ),
    ] = None
    new_packages: Annotated[
        list[package_request.PackageRequest] | None,
        Field(
            description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. Sellers that support mid-flight package additions advertise add_packages in valid_actions. Sellers that do not support this MUST reject with UNSUPPORTED_FEATURE.',
            min_length=1,
        ),
    ] = None
    reporting_webhook: Annotated[
        reporting_webhook_1.ReportingWebhook | None,
        Field(
            description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.'
        ),
    ] = None
    push_notification_config: Annotated[
        push_notification_config_1.PushNotificationConfig | None,
        Field(
            description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.'
        ),
    ] = None
    idempotency_key: Annotated[
        str,
        Field(
            description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.',
            max_length=255,
            min_length=16,
            pattern='^[A-Za-z0-9_.:-]{16,255}$',
        ),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var accountAccountReference
var adcp_major_version : int | None
var canceled : Literal[True]
var cancellation_reason : str | None
var contextContextObject | None
var end_time : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var idempotency_key : str
var invoice_recipientBusinessEntity | None
var media_buy_id : str
var model_config
var new_packages : list[PackageRequest] | None
var packages : list[PackageUpdate] | None
var paused : bool | None
var push_notification_configPushNotificationConfig | None
var reporting_webhookReportingWebhook | None
var revision : int | None
var start_timeStartTiming | None

Inherited members

class UpdateMediaBuySuccessResponse (**data: Any)
Expand source code
class UpdateMediaBuyResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    media_buy_id: Annotated[str, Field(description="Seller's identifier for the media buy")]
    status: Annotated[
        media_buy_status.MediaBuyStatus | None,
        Field(
            description="Media buy status after the update. Present when the update changes the media buy's status (e.g., cancellation transitions to 'canceled', pause transitions to 'paused')."
        ),
    ] = None
    revision: Annotated[
        int | None,
        Field(
            description='Revision number after this update. Use this value in subsequent update_media_buy requests for optimistic concurrency.',
            ge=1,
        ),
    ] = None
    implementation_date: Annotated[
        AwareDatetime | None,
        Field(description='ISO 8601 timestamp when changes take effect (null if pending approval)'),
    ] = None
    invoice_recipient: Annotated[
        business_entity.BusinessEntity | None,
        Field(
            description='Updated invoice recipient, echoed from the request when provided. Confirms the seller accepted the billing override. Bank details are omitted (write-only).'
        ),
    ] = None
    affected_packages: Annotated[
        list[package.Package] | None,
        Field(description='Array of packages that were modified with complete state information'),
    ] = None
    valid_actions: Annotated[
        list[ValidAction] | None,
        Field(
            description='Actions the buyer can perform after this update. Saves a round-trip to get_media_buys.'
        ),
    ] = None
    sandbox: Annotated[
        bool | None,
        Field(description='When true, this response contains simulated data from sandbox mode.'),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var affected_packages : list[Package] | None
var contextContextObject | None
var extExtensionObject | None
var implementation_date : pydantic.types.AwareDatetime | None
var invoice_recipientBusinessEntity | None
var media_buy_id : str
var model_config
var revision : int | None
var sandbox : bool | None
var statusMediaBuyStatus | None
var valid_actions : list[ValidAction] | None

Inherited members

class UpdateMediaBuyErrorResponse (**data: Any)
Expand source code
class UpdateMediaBuyResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    errors: Annotated[
        list[error.Error],
        Field(description='Array of errors explaining why the operation failed', min_length=1),
    ]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class ValidateContentDeliverySuccessResponse (**data: Any)
Expand source code
class ValidateContentDeliveryResponse1(AdCPBaseModel):
    summary: Annotated[Summary, Field(description='Summary counts across all records')]
    results: Annotated[list[Result], Field(description='Per-record evaluation results')]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var extExtensionObject | None
var model_config
var results : list[Result]
var summarySummary

Inherited members

class ValidateContentDeliveryErrorResponse (**data: Any)
Expand source code
class ValidateContentDeliveryResponse2(AdCPBaseModel):
    errors: list[error.Error]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var contextContextObject | None
var errors : list[Error]
var extExtensionObject | None
var model_config

Inherited members

class ValidationError (*args, **kwargs)
Expand source code
class ValidationError(ValueError):
    """Raised when runtime validation fails."""

    pass

Raised when runtime validation fails.

Ancestors

  • builtins.ValueError
  • builtins.Exception
  • builtins.BaseException
class ValidationResult (**data: Any)
Expand source code
class ValidationResult(RegistryBaseModel):
    valid: bool
    domain: str | None = None
    url: str | None = None
    errors: list[str | dict[str, Any]] | None = None
    warnings: list[str | dict[str, Any]] | None = None
    status_code: int | None = None
    raw_data: dict[str, Any] | None = None

Base model for registry API types.

Uses extra='allow' so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults to extra='ignore' for protocol types.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var domain : str | None
var errors : list[str | dict[str, typing.Any]] | None
var model_config
var raw_data : dict[str, typing.Any] | None
var status_code : int | None
var url : str | None
var valid : bool
var warnings : list[str | dict[str, typing.Any]] | None
class UrlVastAsset (**data: Any)
Expand source code
class VastAsset1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    delivery_type: Annotated[
        Literal['url'],
        Field(description='Discriminator indicating VAST is delivered via URL endpoint'),
    ]
    url: Annotated[AnyUrl, Field(description='URL endpoint that returns VAST XML')]
    vast_version: Annotated[
        vast_version_1.VastVersion | None, Field(description='VAST specification version')
    ] = None
    vpaid_enabled: Annotated[
        bool | None,
        Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'),
    ] = None
    duration_ms: Annotated[
        int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0)
    ] = None
    tracking_events: Annotated[
        list[vast_tracking_event.VastTrackingEvent] | None,
        Field(description='Tracking events supported by this VAST tag'),
    ] = None
    captions_url: Annotated[
        AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)')
    ] = None
    audio_description_url: Annotated[
        AnyUrl | None,
        Field(description='URL to audio description track for visually impaired users'),
    ] = None
    provenance: Annotated[
        provenance_1.Provenance | None,
        Field(
            description='Provenance metadata for this asset, overrides manifest-level provenance'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var audio_description_url : pydantic.networks.AnyUrl | None
var captions_url : pydantic.networks.AnyUrl | None
var delivery_type : Literal['url']
var duration_ms : int | None
var model_config
var provenanceProvenance | None
var tracking_events : list[VastTrackingEvent] | None
var url : pydantic.networks.AnyUrl
var vast_versionVastVersion | None
var vpaid_enabled : bool | None

Inherited members

class InlineVastAsset (**data: Any)
Expand source code
class VastAsset2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    delivery_type: Annotated[
        Literal['inline'],
        Field(description='Discriminator indicating VAST is delivered as inline XML content'),
    ]
    content: Annotated[str, Field(description='Inline VAST XML content')]
    vast_version: Annotated[
        vast_version_1.VastVersion | None, Field(description='VAST specification version')
    ] = None
    vpaid_enabled: Annotated[
        bool | None,
        Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'),
    ] = None
    duration_ms: Annotated[
        int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0)
    ] = None
    tracking_events: Annotated[
        list[vast_tracking_event.VastTrackingEvent] | None,
        Field(description='Tracking events supported by this VAST tag'),
    ] = None
    captions_url: Annotated[
        AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)')
    ] = None
    audio_description_url: Annotated[
        AnyUrl | None,
        Field(description='URL to audio description track for visually impaired users'),
    ] = None
    provenance: Annotated[
        provenance_1.Provenance | None,
        Field(
            description='Provenance metadata for this asset, overrides manifest-level provenance'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var audio_description_url : pydantic.networks.AnyUrl | None
var captions_url : pydantic.networks.AnyUrl | None
var content : str
var delivery_type : Literal['inline']
var duration_ms : int | None
var model_config
var provenanceProvenance | None
var tracking_events : list[VastTrackingEvent] | None
var vast_versionVastVersion | None
var vpaid_enabled : bool | None

Inherited members

class VcpmPricingOption (**data: Any)
Expand source code
class VcpmPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[
        Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)')
    ]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['vcpm']
var pricing_option_id : str
class VcpmAuctionPricingOption (**data: Any)
Expand source code
class VcpmPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[
        Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)')
    ]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['vcpm']
var pricing_option_id : str
class VcpmFixedRatePricingOption (**data: Any)
Expand source code
class VcpmPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    pricing_option_id: Annotated[
        str, Field(description='Unique identifier for this pricing option within the product')
    ]
    pricing_model: Annotated[
        Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)')
    ]
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    fixed_price: Annotated[
        float | None,
        Field(
            description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.',
            ge=0.0,
        ),
    ] = None
    floor_price: Annotated[
        float | None,
        Field(
            description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.',
            ge=0.0,
        ),
    ] = None
    max_bid: Annotated[
        bool | None,
        Field(
            description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor."
        ),
    ] = False
    price_guidance: Annotated[
        price_guidance_1.PriceGuidance | None,
        Field(description='Optional pricing guidance for auction-based bidding'),
    ] = None
    min_spend_per_package: Annotated[
        float | None,
        Field(
            description='Minimum spend requirement per package using this pricing option, in the specified currency',
            ge=0.0,
        ),
    ] = None
    price_breakdown: Annotated[
        price_breakdown_1.PriceBreakdown | None,
        Field(
            description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.'
        ),
    ] = None
    eligible_adjustments: Annotated[
        list[adjustment_kind.PriceAdjustmentKind] | None,
        Field(
            description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

Defaults to extra='ignore' so that unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas set additionalProperties: true override this with extra='allow' in their own model_config. Consumers who want strict validation can override with extra='forbid'.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

Class variables

var currency : str
var eligible_adjustments : list[PriceAdjustmentKind] | None
var fixed_price : float | None
var floor_price : float | None
var max_bid : bool | None
var min_spend_per_package : float | None
var model_config
var price_breakdownPriceBreakdown | None
var price_guidancePriceGuidance | None
var pricing_model : Literal['vcpm']
var pricing_option_id : str

Inherited members

class WcagLevel (*args, **kwds)
Expand source code
class WcagLevel(Enum):
    A = 'A'
    AA = 'AA'
    AAA = 'AAA'

Create a collection of name/value pairs.

Example enumeration:

>>> class Color(Enum):
...     RED = 1
...     BLUE = 2
...     GREEN = 3

Access them by:

  • attribute access::
>>> Color.RED
<Color.RED: 1>
  • value lookup:
>>> Color(1)
<Color.RED: 1>
  • name lookup:
>>> Color['RED']
<Color.RED: 1>

Enumerations can be iterated over, and know how many members they have:

>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.

Ancestors

  • enum.Enum

Class variables

var A
var AA
var AAA
class WebhookDedupStore (backend: IdempotencyBackend,
ttl_seconds: int = 86400,
*,
namespace: str = 'webhook',
clock: Callable[[], float] = <built-in function time>)
Expand source code
class WebhookDedupStore:
    """Dedup ``(sender_id, idempotency_key)`` pairs to suppress retried webhooks.

    :param backend: any :class:`IdempotencyBackend`. Same MemoryBackend or
        PgBackend type used by :class:`IdempotencyStore` is fine — the
        ``namespace`` parameter prefixes all sender IDs so request-side and
        webhook-side scopes can't alias even when sharing one backend instance.
    :param ttl_seconds: replay window. Must be within ``[86400, 604800]`` per
        the spec minimum. Defaults to 86400 (24h).
    :param namespace: prefix applied to every ``sender_id`` before it hits
        the backend. Defaults to ``"webhook"``, which is safe when the same
        backend is shared with :class:`IdempotencyStore` (request-side keys
        are scoped by a principal_id that isn't wrapped in this namespace,
        so collisions are impossible). Override only if you run multiple
        webhook scopes against one backend (e.g., separate dedup spaces for
        task webhooks vs list-change webhooks).
    """

    def __init__(
        self,
        backend: IdempotencyBackend,
        ttl_seconds: int = _MIN_TTL_SECONDS,
        *,
        namespace: str = "webhook",
        clock: Callable[[], float] = time.time,
    ) -> None:
        if not _MIN_TTL_SECONDS <= ttl_seconds <= _MAX_TTL_SECONDS:
            raise ValueError(
                f"ttl_seconds must be in [{_MIN_TTL_SECONDS}, {_MAX_TTL_SECONDS}] "
                f"per webhook spec minimum, got {ttl_seconds}"
            )
        if not namespace:
            raise ValueError("namespace must be a non-empty string")
        self.backend = backend
        self.ttl_seconds = ttl_seconds
        self.namespace = namespace
        self._clock = clock

    async def check_and_record(self, sender_id: str, idempotency_key: str) -> bool:
        """Atomically check for first-seen and record if new.

        Returns ``True`` when the pair is first-seen (event should be
        processed), ``False`` on duplicate (caller MUST still return 2xx to
        the sender — the event was delivered successfully, it's just a retry).

        Race note: the check-then-put pattern is not atomic across concurrent
        callers unless the backend provides its own atomicity. MemoryBackend
        serializes individual ``get`` and ``put`` under an ``asyncio.Lock`` but
        does NOT bracket them together — two concurrent retries of the same
        event CAN both observe "first-seen" and both process the event. That's
        a tolerable failure mode: the ultimate guarantee is "at most once per
        replay window in the common case"; a concurrent retry arriving in the
        same few milliseconds is rare and, if it happens, produces the same
        "duplicated side effect" outcome the at-least-once contract already
        warns callers to tolerate. PgBackend implementations SHOULD use
        ``INSERT ... ON CONFLICT DO NOTHING`` returning ``rowcount`` for
        lock-free atomicity.
        """
        if not sender_id:
            raise ValueError("sender_id must be a non-empty string")
        if not idempotency_key:
            raise ValueError("idempotency_key must be a non-empty string")

        scoped_sender = f"{self.namespace}:{sender_id}"
        existing = await self.backend.get(scoped_sender, idempotency_key)
        if existing is not None:
            logger.debug(
                "webhook dedup: duplicate sender=%s key_prefix=%s",
                sender_id,
                idempotency_key[:8],
            )
            return False

        entry = CachedResponse(
            payload_hash=_SENTINEL_HASH,
            response={},
            expires_at_epoch=self._clock() + self.ttl_seconds,
        )
        try:
            await self.backend.put(scoped_sender, idempotency_key, entry)
        except Exception:
            # Same fail-open reasoning as the request-side store: log and
            # process. Swallowing the put failure means this event MIGHT
            # reprocess on retry, not that we drop it. Better than raising,
            # which would look like handler failure to the sender.
            logger.warning(
                "webhook dedup put failed for sender=%s key_prefix=%s — "
                "event processed but next retry will reprocess",
                sender_id,
                idempotency_key[:8],
                exc_info=True,
            )
        return True

Dedup (sender_id, idempotency_key) pairs to suppress retried webhooks.

:param backend: any :class:IdempotencyBackend. Same MemoryBackend or PgBackend type used by :class:IdempotencyStore is fine — the namespace parameter prefixes all sender IDs so request-side and webhook-side scopes can't alias even when sharing one backend instance. :param ttl_seconds: replay window. Must be within [86400, 604800] per the spec minimum. Defaults to 86400 (24h). :param namespace: prefix applied to every sender_id before it hits the backend. Defaults to "webhook", which is safe when the same backend is shared with :class:IdempotencyStore (request-side keys are scoped by a principal_id that isn't wrapped in this namespace, so collisions are impossible). Override only if you run multiple webhook scopes against one backend (e.g., separate dedup spaces for task webhooks vs list-change webhooks).

Methods

async def check_and_record(self, sender_id: str, idempotency_key: str) ‑> bool
Expand source code
async def check_and_record(self, sender_id: str, idempotency_key: str) -> bool:
    """Atomically check for first-seen and record if new.

    Returns ``True`` when the pair is first-seen (event should be
    processed), ``False`` on duplicate (caller MUST still return 2xx to
    the sender — the event was delivered successfully, it's just a retry).

    Race note: the check-then-put pattern is not atomic across concurrent
    callers unless the backend provides its own atomicity. MemoryBackend
    serializes individual ``get`` and ``put`` under an ``asyncio.Lock`` but
    does NOT bracket them together — two concurrent retries of the same
    event CAN both observe "first-seen" and both process the event. That's
    a tolerable failure mode: the ultimate guarantee is "at most once per
    replay window in the common case"; a concurrent retry arriving in the
    same few milliseconds is rare and, if it happens, produces the same
    "duplicated side effect" outcome the at-least-once contract already
    warns callers to tolerate. PgBackend implementations SHOULD use
    ``INSERT ... ON CONFLICT DO NOTHING`` returning ``rowcount`` for
    lock-free atomicity.
    """
    if not sender_id:
        raise ValueError("sender_id must be a non-empty string")
    if not idempotency_key:
        raise ValueError("idempotency_key must be a non-empty string")

    scoped_sender = f"{self.namespace}:{sender_id}"
    existing = await self.backend.get(scoped_sender, idempotency_key)
    if existing is not None:
        logger.debug(
            "webhook dedup: duplicate sender=%s key_prefix=%s",
            sender_id,
            idempotency_key[:8],
        )
        return False

    entry = CachedResponse(
        payload_hash=_SENTINEL_HASH,
        response={},
        expires_at_epoch=self._clock() + self.ttl_seconds,
    )
    try:
        await self.backend.put(scoped_sender, idempotency_key, entry)
    except Exception:
        # Same fail-open reasoning as the request-side store: log and
        # process. Swallowing the put failure means this event MIGHT
        # reprocess on retry, not that we drop it. Better than raising,
        # which would look like handler failure to the sender.
        logger.warning(
            "webhook dedup put failed for sender=%s key_prefix=%s — "
            "event processed but next retry will reprocess",
            sender_id,
            idempotency_key[:8],
            exc_info=True,
        )
    return True

Atomically check for first-seen and record if new.

Returns True when the pair is first-seen (event should be processed), False on duplicate (caller MUST still return 2xx to the sender — the event was delivered successfully, it's just a retry).

Race note: the check-then-put pattern is not atomic across concurrent callers unless the backend provides its own atomicity. MemoryBackend serializes individual get and put under an asyncio.Lock but does NOT bracket them together — two concurrent retries of the same event CAN both observe "first-seen" and both process the event. That's a tolerable failure mode: the ultimate guarantee is "at most once per replay window in the common case"; a concurrent retry arriving in the same few milliseconds is rare and, if it happens, produces the same "duplicated side effect" outcome the at-least-once contract already warns callers to tolerate. PgBackend implementations SHOULD use INSERT … ON CONFLICT DO NOTHING returning rowcount for lock-free atomicity.

class WebhookMetadata (**data: Any)
Expand source code
class WebhookMetadata(BaseModel):
    """Metadata passed to webhook handlers."""

    operation_id: str
    agent_id: str
    task_type: str
    status: TaskStatus
    sequence_number: int | None = None
    notification_type: Literal["scheduled", "final", "delayed"] | None = None
    timestamp: str

Metadata passed to webhook handlers.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Ancestors

  • pydantic.main.BaseModel

Class variables

var agent_id : str
var model_config
var notification_type : Literal['scheduled', 'final', 'delayed'] | None
var operation_id : str
var sequence_number : int | None
var statusTaskStatus
var task_type : str
var timestamp : str
class WebhookReceiver (config: WebhookReceiverConfig)
Expand source code
class WebhookReceiver:
    """Stateless webhook entry point, one instance per receiver configuration.

    Instance state (``config``) is read-only after construction. Per-request
    state lives in the :class:`WebhookOutcome` returned from :meth:`receive`.
    """

    def __init__(self, config: WebhookReceiverConfig) -> None:
        self._config = config

    async def receive(
        self,
        *,
        method: str,
        url: str,
        headers: Mapping[str, str],
        body: bytes,
    ) -> WebhookOutcome:
        """Verify, dedupe, parse. Returns a :class:`WebhookOutcome`.

        Never raises for sender-caused cryptographic or protocol failures —
        returns an outcome with ``rejected=True`` and populated
        ``response_headers`` so the caller can convert to an HTTP response
        without try/except around every call. Operational failures inside
        the dedup backend or verify-options factory MAY still raise; wrap
        the call if you need to 5xx cleanly on internal errors.
        """
        if not _content_type_is_json(headers):
            return _reject("content_type_invalid", sender_identity=None)

        signer, rejection = await self._verify(method=method, url=url, headers=headers, body=body)
        if rejection is not None:
            return rejection
        assert signer is not None  # verification succeeded

        sender_id = signer.as_sender_identity()

        try:
            payload_dict = json.loads(body)
        except json.JSONDecodeError:
            return _reject("body_invalid_json", sender_identity=sender_id)
        if not isinstance(payload_dict, dict):
            return _reject("body_invalid_json", sender_identity=sender_id)

        idempotency_key = payload_dict.get("idempotency_key")
        if not isinstance(idempotency_key, str) or not idempotency_key:
            # Spec 3.0-rc: idempotency_key is REQUIRED on every webhook payload.
            return _reject("idempotency_key_missing", sender_identity=sender_id)
        if not _IDEMPOTENCY_KEY_RE.match(idempotency_key):
            # Non-conformant format — charset or length out of bounds.
            return _reject("idempotency_key_invalid", sender_identity=sender_id)

        parsed = self._parse(payload_dict)
        if parsed is None:
            return _reject("payload_invalid", sender_identity=sender_id)

        is_first_seen = await self._config.dedup.check_and_record(
            sender_id=sender_id, idempotency_key=idempotency_key
        )

        return WebhookOutcome(
            sender_identity=sender_id,
            payload=parsed,
            duplicate=not is_first_seen,
            idempotency_key=idempotency_key,
        )

    def receive_sync(
        self,
        *,
        method: str,
        url: str,
        headers: Mapping[str, str],
        body: bytes,
    ) -> WebhookOutcome:
        """Synchronous wrapper around :meth:`receive` for WSGI-style frameworks.

        Use this from Flask, Gunicorn sync workers, ``http.server``, or any
        other sync-only HTTP entry point where wrapping every call in
        ``asyncio.run(...)`` is just noise::

            @app.post("/webhooks/adcp")
            def hook():
                outcome = receiver.receive_sync(
                    method=request.method,
                    url=request.url,
                    headers=dict(request.headers),
                    body=request.get_data(),
                )
                ...

        Raises :class:`RuntimeError` if invoked from a thread that already has
        a running event loop — the underlying verify / dedup path is async and
        cannot be driven from inside an active loop without blocking it. From
        async code, call :meth:`receive` directly.
        """
        try:
            asyncio.get_running_loop()
        except RuntimeError:
            # No running loop in this thread — safe to spin one up.
            return asyncio.run(self.receive(method=method, url=url, headers=headers, body=body))
        raise RuntimeError(
            "WebhookReceiver.receive_sync() cannot be called from a running "
            "event loop. Use `await receiver.receive(...)` instead."
        )

    async def _verify(
        self,
        *,
        method: str,
        url: str,
        headers: Mapping[str, str],
        body: bytes,
    ) -> tuple[VerifiedSignerLike | None, WebhookOutcome | None]:
        """Returns (signer, None) on success or (None, rejection_outcome)."""
        has_9421 = _has_9421_headers(headers)

        if has_9421:
            try:
                signer = verify_webhook_signature(
                    method=method,
                    url=url,
                    headers=headers,
                    body=body,
                    options=self._config.verify_options,
                )
                return signer, None
            except SignatureVerificationError as exc:
                # Downgrade defense: when 9421 IS present but fails, do NOT
                # consult HMAC fallback by default. A MITM that stripped a
                # valid 9421 signature and replaced it with a forged HMAC one
                # is exactly what the downgrade guard exists for.
                fallback = self._config.legacy_hmac
                allow_hmac = fallback is not None and not fallback.only_when_9421_absent
                if not allow_hmac:
                    return None, WebhookOutcome(
                        rejected=True,
                        rejection_reason="signature_invalid",
                        response_headers=_www_authenticate_header(exc.code),
                    )
                logger.warning(
                    "9421 webhook verify failed (%s); trying HMAC legacy because "
                    "legacy_hmac.only_when_9421_absent=False is set",
                    exc.code,
                )

        fallback = self._config.legacy_hmac
        if fallback is None:
            # No 9421 headers AND no HMAC fallback configured → spec says 9421
            # is baseline-required in 3.0, so this is non-conformant.
            return None, WebhookOutcome(
                rejected=True,
                rejection_reason="signature_missing",
                response_headers=_www_authenticate_header("webhook_signature_required"),
            )

        hmac_options = fallback.options_for(headers)
        if hmac_options is None:
            return None, WebhookOutcome(
                rejected=True,
                rejection_reason="signature_missing",
                response_headers=_www_authenticate_header("webhook_signature_required"),
            )
        try:
            legacy_signer = verify_webhook_hmac(headers=headers, body=body, options=hmac_options)
            return legacy_signer, None
        except LegacyWebhookHmacError:
            return None, WebhookOutcome(
                rejected=True,
                rejection_reason="signature_legacy_failed",
                response_headers=_www_authenticate_header("webhook_signature_invalid"),
            )

    def _parse(self, payload_dict: dict[str, Any]) -> WebhookPayload | None:
        model = _MODEL_BY_KIND[self._config.kind]
        try:
            return cast(WebhookPayload, model.model_validate(payload_dict))
        except ValidationError as exc:
            # Operators need the field-level reason to diagnose sender bugs.
            # The receiver still returns payload_invalid downstream; this is
            # just observability.
            logger.warning(
                "webhook payload failed %s validation: %s",
                self._config.kind,
                exc.errors(include_url=False),
            )
            return None

Stateless webhook entry point, one instance per receiver configuration.

Instance state (adcp.config) is read-only after construction. Per-request state lives in the :class:WebhookOutcome returned from :meth:receive.

Methods

async def receive(self, *, method: str, url: str, headers: Mapping[str, str], body: bytes) ‑> WebhookOutcome
Expand source code
async def receive(
    self,
    *,
    method: str,
    url: str,
    headers: Mapping[str, str],
    body: bytes,
) -> WebhookOutcome:
    """Verify, dedupe, parse. Returns a :class:`WebhookOutcome`.

    Never raises for sender-caused cryptographic or protocol failures —
    returns an outcome with ``rejected=True`` and populated
    ``response_headers`` so the caller can convert to an HTTP response
    without try/except around every call. Operational failures inside
    the dedup backend or verify-options factory MAY still raise; wrap
    the call if you need to 5xx cleanly on internal errors.
    """
    if not _content_type_is_json(headers):
        return _reject("content_type_invalid", sender_identity=None)

    signer, rejection = await self._verify(method=method, url=url, headers=headers, body=body)
    if rejection is not None:
        return rejection
    assert signer is not None  # verification succeeded

    sender_id = signer.as_sender_identity()

    try:
        payload_dict = json.loads(body)
    except json.JSONDecodeError:
        return _reject("body_invalid_json", sender_identity=sender_id)
    if not isinstance(payload_dict, dict):
        return _reject("body_invalid_json", sender_identity=sender_id)

    idempotency_key = payload_dict.get("idempotency_key")
    if not isinstance(idempotency_key, str) or not idempotency_key:
        # Spec 3.0-rc: idempotency_key is REQUIRED on every webhook payload.
        return _reject("idempotency_key_missing", sender_identity=sender_id)
    if not _IDEMPOTENCY_KEY_RE.match(idempotency_key):
        # Non-conformant format — charset or length out of bounds.
        return _reject("idempotency_key_invalid", sender_identity=sender_id)

    parsed = self._parse(payload_dict)
    if parsed is None:
        return _reject("payload_invalid", sender_identity=sender_id)

    is_first_seen = await self._config.dedup.check_and_record(
        sender_id=sender_id, idempotency_key=idempotency_key
    )

    return WebhookOutcome(
        sender_identity=sender_id,
        payload=parsed,
        duplicate=not is_first_seen,
        idempotency_key=idempotency_key,
    )

Verify, dedupe, parse. Returns a :class:WebhookOutcome.

Never raises for sender-caused cryptographic or protocol failures — returns an outcome with rejected=True and populated response_headers so the caller can convert to an HTTP response without try/except around every call. Operational failures inside the dedup backend or verify-options factory MAY still raise; wrap the call if you need to 5xx cleanly on internal errors.

def receive_sync(self, *, method: str, url: str, headers: Mapping[str, str], body: bytes) ‑> WebhookOutcome
Expand source code
def receive_sync(
    self,
    *,
    method: str,
    url: str,
    headers: Mapping[str, str],
    body: bytes,
) -> WebhookOutcome:
    """Synchronous wrapper around :meth:`receive` for WSGI-style frameworks.

    Use this from Flask, Gunicorn sync workers, ``http.server``, or any
    other sync-only HTTP entry point where wrapping every call in
    ``asyncio.run(...)`` is just noise::

        @app.post("/webhooks/adcp")
        def hook():
            outcome = receiver.receive_sync(
                method=request.method,
                url=request.url,
                headers=dict(request.headers),
                body=request.get_data(),
            )
            ...

    Raises :class:`RuntimeError` if invoked from a thread that already has
    a running event loop — the underlying verify / dedup path is async and
    cannot be driven from inside an active loop without blocking it. From
    async code, call :meth:`receive` directly.
    """
    try:
        asyncio.get_running_loop()
    except RuntimeError:
        # No running loop in this thread — safe to spin one up.
        return asyncio.run(self.receive(method=method, url=url, headers=headers, body=body))
    raise RuntimeError(
        "WebhookReceiver.receive_sync() cannot be called from a running "
        "event loop. Use `await receiver.receive(...)` instead."
    )

Synchronous wrapper around :meth:receive for WSGI-style frameworks.

Use this from Flask, Gunicorn sync workers, http.server, or any other sync-only HTTP entry point where wrapping every call in asyncio.run(…) is just noise::

@app.post("/webhooks/adcp")
def hook():
    outcome = receiver.receive_sync(
        method=request.method,
        url=request.url,
        headers=dict(request.headers),
        body=request.get_data(),
    )
    ...

Raises :class:RuntimeError if invoked from a thread that already has a running event loop — the underlying verify / dedup path is async and cannot be driven from inside an active loop without blocking it. From async code, call :meth:receive directly.

class WebhookReceiverConfig (verify_options: WebhookVerifyOptions,
dedup: WebhookDedupStore,
legacy_hmac: LegacyHmacFallback | None = None,
kind: WebhookKind = 'mcp')
Expand source code
@dataclass(frozen=True)
class WebhookReceiverConfig:
    """Configuration bundle.

    :param verify_options: verifier configuration (JWKS, replay store, etc.).
        A single instance is reused for every request — the verifier stamps
        ``now`` itself via ``verify_options.clock()``, so there's no need to
        refresh a time field per request.
    :param dedup: webhook-dedup store.
    :param legacy_hmac: optional HMAC-SHA256 fallback for 3.x migration.
    :param kind: which webhook payload type to parse into. Default ``"mcp"``
        (the task-status webhook that dominates most integrations); pass
        explicitly for list-change / artifact / revocation receivers.
    """

    verify_options: WebhookVerifyOptions
    dedup: WebhookDedupStore
    legacy_hmac: LegacyHmacFallback | None = None
    kind: WebhookKind = "mcp"

Configuration bundle.

:param verify_options: verifier configuration (JWKS, replay store, etc.). A single instance is reused for every request — the verifier stamps now itself via verify_options.clock(), so there's no need to refresh a time field per request. :param dedup: webhook-dedup store. :param legacy_hmac: optional HMAC-SHA256 fallback for 3.x migration. :param kind: which webhook payload type to parse into. Default "mcp" (the task-status webhook that dominates most integrations); pass explicitly for list-change / artifact / revocation receivers.

Instance variables

var dedupWebhookDedupStore
var kind : Literal['mcp', 'revocation_notification', 'collection_list_changed', 'property_list_changed', 'artifact']
var legacy_hmacLegacyHmacFallback | None
var verify_optionsWebhookVerifyOptions
class WebhookVerifyOptions (*,
jwks_resolver: JwksResolver,
replay_store: ReplayStore | None = None,
revocation_checker: RevocationChecker | None = None,
revocation_list: RevocationList | None = None,
max_skew_seconds: int = 60,
max_window_seconds: int = 300,
label: str = 'sig1',
allowed_algs: frozenset[str] = frozenset({'ed25519', 'ecdsa-p256-sha256'}),
sender_url: str | None = None,
clock: Callable[[], float] = <built-in function time>)
Expand source code
@dataclass(frozen=True, kw_only=True)
class WebhookVerifyOptions:
    """Options for the webhook verifier.

    Subset of :class:`VerifyOptions` — several fields are pinned (tag, adcp_use,
    content-digest policy) because the webhook profile doesn't leave them as
    caller choices.

    Unlike the request verifier, there is no ``now`` field — the webhook
    verifier stamps time-of-check itself, so the same :class:`WebhookVerifyOptions`
    instance can live for the lifetime of your receiver without a factory
    closure around it. Override via ``clock=`` for deterministic tests.
    """

    jwks_resolver: JwksResolver
    replay_store: ReplayStore | None = None
    revocation_checker: RevocationChecker | None = None
    revocation_list: RevocationList | None = None
    max_skew_seconds: int = DEFAULT_SKEW_SECONDS
    max_window_seconds: int = MAX_WINDOW_SECONDS
    label: str = SIG_LABEL_DEFAULT
    allowed_algs: frozenset[str] = ALLOWED_ALGS
    sender_url: str | None = None
    clock: Callable[[], float] = time.time

Options for the webhook verifier.

Subset of :class:VerifyOptions — several fields are pinned (tag, adcp_use, content-digest policy) because the webhook profile doesn't leave them as caller choices.

Unlike the request verifier, there is no now field — the webhook verifier stamps time-of-check itself, so the same :class:WebhookVerifyOptions instance can live for the lifetime of your receiver without a factory closure around it. Override via clock= for deterministic tests.

Instance variables

var allowed_algs : frozenset[str]
var jwks_resolverJwksResolver
var label : str
var max_skew_seconds : int
var max_window_seconds : int
var replay_storeReplayStore | None
var revocation_checkerRevocationChecker | None
var revocation_listRevocationList | None
var sender_url : str | None

Methods

def clock(...) ‑> Callable[[], float]

time() -> floating point number

Return the current time in seconds since the Epoch. Fractions of a second may be present if the system clock provides them.