Package adcp

Sub-modules

adcp.adagents
adcp.client
adcp.config
adcp.exceptions
adcp.protocols
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.webhooks

Webhook creation and signing utilities 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,
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) ‑> dict[str, typing.Any]
Expand source code
def create_mcp_webhook_payload(
    task_id: str,
    status: GeneratedTaskStatus,
    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,
) -> 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

    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)

    # 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] = {
        "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

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.

    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

    Raises:
        AdagentsNotFoundError: If adagents.json not found (404)
        AdagentsValidationError: If JSON is invalid or malformed
        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 URL
    url = f"https://{publisher_domain}/.well-known/adagents.json"

    try:
        # Use provided client or create a new one
        if client is not None:
            # Reuse provided client (connection pooling)
            response = await client.get(
                url,
                headers={"User-Agent": user_agent},
                timeout=timeout,
                follow_redirects=True,
            )
        else:
            # Create new client for single request
            async with httpx.AsyncClient() as new_client:
                response = await new_client.get(
                    url,
                    headers={"User-Agent": user_agent},
                    timeout=timeout,
                    follow_redirects=True,
                )

        # Process response (same for both paths)
        if response.status_code == 404:
            raise AdagentsNotFoundError(publisher_domain)

        if response.status_code != 200:
            raise AdagentsValidationError(
                f"Failed to fetch adagents.json: HTTP {response.status_code}"
            )

        # Parse JSON
        try:
            data = response.json()
        except Exception as e:
            raise AdagentsValidationError(f"Invalid JSON in adagents.json: {e}") from e

        # Validate basic structure
        if not isinstance(data, dict):
            raise AdagentsValidationError("adagents.json must be a JSON object")

        if "authorized_agents" not in data:
            raise AdagentsValidationError("adagents.json must have 'authorized_agents' field")

        if not isinstance(data["authorized_agents"], list):
            raise AdagentsValidationError("'authorized_agents' must be an array")

        # Validate mutual exclusivity constraints
        try:
            validate_adagents(data)
        except ValidationError as e:
            raise AdagentsValidationError(f"Invalid adagents.json structure: {e}") from e

        return data

    except httpx.TimeoutException as e:
        raise AdagentsTimeoutError(publisher_domain, timeout) from e
    except httpx.RequestError as e:
        raise AdagentsValidationError(f"Failed to fetch adagents.json: {e}") from e

Fetch and parse adagents.json from publisher domain.

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

Raises

AdagentsNotFoundError
If adagents.json not found (404)
AdagentsValidationError
If JSON is invalid or malformed
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.
    For the "push" approach where the agent tells you what it's authorized for,
    use the agent's list_authorized_properties endpoint via ADCPClient.

    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}")

    See Also:
        ADCPClient.list_authorized_properties: "Push" approach using the agent's API

    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. For the "push" approach where the agent tells you what it's authorized for, use the agent's list_authorized_properties endpoint via ADCPClient.

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}")

See Also: ADCPClient.list_authorized_properties: "Push" approach using the agent's API

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 get_adcp_signed_headers_for_webhook(headers: dict[str, Any],
secret: str,
timestamp: str,
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, 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: ISO 8601 timestamp used in signature generation

    The signing algorithm:
    1. Constructs message as "{timestamp}.{json_payload}"
    2. JSON-serializes payload with compact separators (no sorted keys for performance)
    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: ISO 8601 timestamp string (e.g., "2025-01-15T10:00:00Z")
        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:
        >>> from adcp.webhooks import create_mcp_webhook_payload get_adcp_signed_headers_for_webhook
        >>> from datetime import datetime, timezone
        >>>
        >>> payload = create_mcp_webhook_payload(
        ...     task_id="task_123",
        ...     task_type="get_products",
        ...     status="completed",
        ...     result={"products": [...]}
        ... )
        >>> headers = {"Content-Type": "application/json"}
        >>> timestamp = datetime.now(timezone.utc).isoformat()
        >>> signed_headers = get_adcp_signed_headers_for_webhook(
        ...     headers, secret="my-webhook-secret", timestamp=timestamp, 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": "2025-01-15T10:00:00Z"
        }

        Sign with Pydantic model directly:
        >>> from adcp import GetMediaBuyDeliveryResponse
        >>> from datetime import datetime, timezone
        >>>
        >>> response: GetMediaBuyDeliveryResponse = ...  # From API call
        >>> headers = {"Content-Type": "application/json"}
        >>> timestamp = datetime.now(timezone.utc).isoformat()
        >>> signed_headers = get_adcp_signed_headers_for_webhook(
        ...     headers, secret="my-webhook-secret", timestamp=timestamp, payload=response
        ... )
        >>> # Pydantic model is automatically converted to dict for signing
    """
    # Convert Pydantic model to dict if needed
    # All AdCP types inherit from AdCPBaseModel (Pydantic BaseModel)
    if hasattr(payload, "model_dump"):
        payload_dict = payload.model_dump(mode="json")
    else:
        payload_dict = payload

    # Serialize payload to JSON with consistent formatting
    # Note: sort_keys=False for performance (key order doesn't affect signature)
    payload_bytes = json.dumps(payload_dict, separators=(",", ":"), sort_keys=False).encode("utf-8")

    # Construct signed message: timestamp.payload
    # Including timestamp prevents replay attacks
    signed_message = f"{timestamp}.{payload_bytes.decode('utf-8')}"

    # Generate HMAC-SHA256 signature over timestamp + payload
    signature_hex = hmac.new(
        secret.encode("utf-8"), signed_message.encode("utf-8"), hashlib.sha256
    ).hexdigest()

    # Add AdCP-compliant signature headers
    headers["X-AdCP-Signature"] = f"sha256={signature_hex}"
    headers["X-AdCP-Timestamp"] = timestamp

    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: ISO 8601 timestamp used in signature generation

The signing algorithm: 1. Constructs message as "{timestamp}.{json_payload}" 2. JSON-serializes payload with compact separators (no sorted keys for performance) 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
ISO 8601 timestamp string (e.g., "2025-01-15T10:00:00Z")
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:

>>> from adcp.webhooks import create_mcp_webhook_payload get_adcp_signed_headers_for_webhook
>>> from datetime import datetime, timezone
>>>
>>> payload = create_mcp_webhook_payload(
...     task_id="task_123",
...     task_type="get_products",
...     status="completed",
...     result={"products": [...]}
... )
>>> headers = {"Content-Type": "application/json"}
>>> timestamp = datetime.now(timezone.utc).isoformat()
>>> signed_headers = get_adcp_signed_headers_for_webhook(
...     headers, secret="my-webhook-secret", timestamp=timestamp, 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": "2025-01-15T10:00:00Z"
}

Sign with Pydantic model directly:

>>> from adcp import GetMediaBuyDeliveryResponse
>>> from datetime import datetime, timezone
>>>
>>> response: GetMediaBuyDeliveryResponse = ...  # From API call
>>> headers = {"Content-Type": "application/json"}
>>> timestamp = datetime.now(timezone.utc).isoformat()
>>> signed_headers = get_adcp_signed_headers_for_webhook(
...     headers, secret="my-webhook-secret", timestamp=timestamp, payload=response
... )
>>> # Pydantic model is automatically converted to dict for signing
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.

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

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

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

        agent_properties = agent.get("properties", [])
        if not isinstance(agent_properties, list):
            continue

        # Add each property with the agent URL for reference
        for prop in agent_properties:
            if isinstance(prop, dict):
                # Create a copy and add agent_url
                prop_with_agent = {**prop, "agent_url": agent_url}
                properties.append(prop_with_agent)

    return properties

Extract all properties from adagents.json data.

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_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")

    # Get top-level properties for reference-based authorization types
    top_level_properties = adagents_data.get("properties", [])
    if not isinstance(top_level_properties, list):
        top_level_properties = []

    # Normalize the agent URL for comparison
    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

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

        # Found the agent - determine authorization type
        authorization_type = agent.get("authorization_type", "")

        # Handle inline_properties (properties array directly on agent)
        if authorization_type == "inline_properties" or "properties" in agent:
            properties = agent.get("properties", [])
            if not isinstance(properties, list):
                return []
            return [p for p in properties if isinstance(p, dict)]

        # Handle property_ids (filter top-level properties by property_id)
        if authorization_type == "property_ids":
            authorized_ids = set(agent.get("property_ids", []))
            return [
                p
                for p in top_level_properties
                if isinstance(p, dict) and p.get("property_id") in authorized_ids
            ]

        # Handle property_tags (filter top-level properties by tags)
        if authorization_type == "property_tags":
            authorized_tags = set(agent.get("property_tags", []))
            return [
                p
                for p in top_level_properties
                if isinstance(p, dict) and set(p.get("tags", [])) & authorized_tags
            ]

        # Handle publisher_properties (cross-domain references)
        # Returns the selector objects; caller must resolve against other domains
        if authorization_type == "publisher_properties":
            publisher_props = agent.get("publisher_properties", [])
            if not isinstance(publisher_props, list):
                return []
            return [p for p in publisher_props if isinstance(p, dict)]

        # No recognized authorization type - return empty
        return []

    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 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 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_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
class ADCPClient (agent_config: AgentConfig,
webhook_url_template: str | None = None,
webhook_secret: str | None = None,
on_activity: Callable[[Activity], None] | 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,
    ):
        """
        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
        """
        self.agent_config = agent_config
        self.webhook_url_template = webhook_url_template
        self.webhook_secret = webhook_secret
        self.on_activity = on_activity

        # 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}")

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

        self.simple = SimpleAPI(self)

    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)

    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(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(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(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)  # type: ignore[attr-defined]

        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(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(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(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 list_authorized_properties(
        self,
        request: ListAuthorizedPropertiesRequest,
    ) -> TaskResult[ListAuthorizedPropertiesResponse]:
        """
        List Authorized Properties.

        Args:
            request: Request parameters

        Returns:
            TaskResult containing ListAuthorizedPropertiesResponse
        """
        operation_id = create_operation_id()
        params = request.model_dump(exclude_none=True)

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

        raw_result = await self.adapter.list_authorized_properties(params)

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

        return self.adapter._parse_response(raw_result, ListAuthorizedPropertiesResponse)

    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(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(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(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_manifest: Advertiser brand information and creative assets
                - 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
            >>> client = ADCPClient(agent_config)
            >>> request = CreateMediaBuyRequest(
            ...     brand_manifest=brand,
            ...     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(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(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(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_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
    ) -> bool:
        """
        Verify HMAC-SHA256 signature of webhook payload.

        The verification algorithm matches get_adcp_signed_headers_for_webhook:
        1. Constructs message as "{timestamp}.{json_payload}"
        2. JSON-serializes payload with compact separators
        3. UTF-8 encodes the message
        4. HMAC-SHA256 signs with the shared secret
        5. Compares against the provided signature (with "sha256=" prefix stripped)

        Args:
            payload: Webhook payload dict
            signature: Signature to verify (with or without "sha256=" prefix)
            timestamp: ISO 8601 timestamp from X-AdCP-Timestamp header

        Returns:
            True if signature is valid, False otherwise
        """
        if not self.webhook_secret:
            return True

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

        # Serialize payload to JSON with consistent formatting (matches signing)
        payload_bytes = json.dumps(payload, separators=(",", ":"), sort_keys=False).encode("utf-8")

        # Construct signed message: timestamp.payload (matches get_adcp_signed_headers_for_webhook)
        signed_message = f"{timestamp}.{payload_bytes.decode('utf-8')}"

        # 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] = {
            "get_products": GetProductsResponse,
            "list_creative_formats": ListCreativeFormatsResponse,
            "sync_creatives": SyncCreativesResponse,  # Union type
            "list_creatives": ListCreativesResponse,
            "get_media_buy_delivery": GetMediaBuyDeliveryResponse,
            "list_authorized_properties": ListAuthorizedPropertiesResponse,
            "get_signals": GetSignalsResponse,
            "activate_signal": ActivateSignalResponse,  # Union type
            "provide_performance_feedback": ProvidePerformanceFeedbackResponse,
        }

        # 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,
    ) -> 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 timestamp for signature verification (X-AdCP-Timestamp header)

        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

        # Verify signature before processing (requires both signature and timestamp)
        if (
            signature
            and timestamp
            and not self._verify_webhook_signature(payload, signature, timestamp)
        ):
            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:
                        if len(adcp_data) == 1:
                            adcp_data = adcp_data["response"]
                        else:
                            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:
                            if len(adcp_data) == 1:
                                adcp_data = adcp_data["response"]
                            else:
                                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,
    ) -> 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 timestamp for MCP webhook signature verification
                (X-AdCP-Timestamp header). Required when signature is provided.

        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):
            >>>     payload = await request.json()
            >>>     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
            >>>     )
            >>>     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
            )

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

Methods

async def activate_signal(self,
request: ActivateSignalRequest) ‑> TaskResult[ActivateSignalResponse]
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(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[BuildCreativeResponse]
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(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 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 create_media_buy(self,
request: CreateMediaBuyRequest) ‑> TaskResult[CreateMediaBuyResponse]
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_manifest: Advertiser brand information and creative assets
            - 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
        >>> client = ADCPClient(agent_config)
        >>> request = CreateMediaBuyRequest(
        ...     brand_manifest=brand,
        ...     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(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_manifest: Advertiser brand information and creative assets - 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
>>> client = ADCPClient(agent_config)
>>> request = CreateMediaBuyRequest(
...     brand_manifest=brand,
...     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 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_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(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_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(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_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(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) ‑> 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,
) -> 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 timestamp for MCP webhook signature verification
            (X-AdCP-Timestamp header). Required when signature is provided.

    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):
        >>>     payload = await request.json()
        >>>     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
        >>>     )
        >>>     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
        )

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 timestamp for MCP webhook signature verification (X-AdCP-Timestamp header). Required when signature is provided.

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):
>>>     payload = await request.json()
>>>     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
>>>     )
>>>     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 list_authorized_properties(self,
request: ListAuthorizedPropertiesRequest) ‑> TaskResult[ListAuthorizedPropertiesResponse]
Expand source code
async def list_authorized_properties(
    self,
    request: ListAuthorizedPropertiesRequest,
) -> TaskResult[ListAuthorizedPropertiesResponse]:
    """
    List Authorized Properties.

    Args:
        request: Request parameters

    Returns:
        TaskResult containing ListAuthorizedPropertiesResponse
    """
    operation_id = create_operation_id()
    params = request.model_dump(exclude_none=True)

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

    raw_result = await self.adapter.list_authorized_properties(params)

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

    return self.adapter._parse_response(raw_result, ListAuthorizedPropertiesResponse)

List Authorized Properties.

Args

request
Request parameters

Returns

TaskResult containing ListAuthorizedPropertiesResponse

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(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(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_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 preview_creative(self,
request: PreviewCreativeRequest) ‑> TaskResult[PreviewCreativeResponse]
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(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)  # type: ignore[attr-defined]

    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[ProvidePerformanceFeedbackResponse]
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(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 sync_creatives(self,
request: SyncCreativesRequest) ‑> TaskResult[SyncCreativesResponse]
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(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 update_media_buy(self,
request: UpdateMediaBuyRequest) ‑> TaskResult[UpdateMediaBuyResponse]
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(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
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)

Connection to agent failed.

Initialize connection error.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException
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}"

        super().__init__(full_message)

Base exception for all AdCP client errors.

Initialize exception with context.

Ancestors

  • builtins.Exception
  • builtins.BaseException

Subclasses

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)
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,
    ):
        """
        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
        """
        self.agents = {
            agent.id: ADCPClient(
                agent,
                webhook_url_template=webhook_url_template,
                webhook_secret=webhook_secret,
                on_activity=on_activity,
            )
            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

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

Request timed out.

Initialize timeout error.

Ancestors

  • ADCPError
  • builtins.Exception
  • builtins.BaseException
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
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

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

class ActivateSignalRequest (**data: Any)
Expand source code
class ActivateSignalRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    deployments: Annotated[
        list[destination.Destination],
        Field(
            description='Target deployment(s) for activation. If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.',
            min_length=1,
        ),
    ]
    ext: ext_1.ExtensionObject | None = None
    signal_agent_segment_id: Annotated[
        str, Field(description='The universal identifier for the signal to activate')
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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[Destination]
var extExtensionObject | None
var model_config
var signal_agent_segment_id : str

Inherited members

class ActivateSignalResponse (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class ActivateSignalResponse(RootModel[ActivateSignalResponse1 | ActivateSignalResponse2]):
    root: Annotated[
        ActivateSignalResponse1 | ActivateSignalResponse2,
        Field(
            description='Response payload for activate_signal task. Returns either complete success data OR error information, never both. This enforces atomic operation semantics - the signal is either fully activated or not activated at all.',
            title='Activate Signal Response',
        ),
    ]

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[ActivateSignalResponse1, ActivateSignalResponse2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootActivateSignalResponse1 | ActivateSignalResponse2
class ActivateSignalSuccessResponse (**data: Any)
Expand source code
class ActivateSignalResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    deployments: Annotated[
        list[deployment.Deployment],
        Field(description='Array of deployment results for each deployment target'),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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

Inherited members

class ActivateSignalErrorResponse (**data: Any)
Expand source code
class ActivateSignalResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    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,
        ),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 PropertyIdActivationKey (**data: Any)
Expand source code
class ActivationKey1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    segment_id: Annotated[
        str,
        Field(description='The platform-specific segment identifier to use in campaign targeting'),
    ]
    type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 segment_id : str
var type : Literal['segment_id']

Inherited members

class PropertyTagActivationKey (**data: Any)
Expand source code
class ActivationKey2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    key: Annotated[str, Field(description='The targeting parameter key')]
    type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')]
    value: Annotated[str, Field(description='The targeting parameter value')]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 key : str
var model_config
var type : Literal['key_value']
var value : str

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

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

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

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 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'
    promoted_offerings = 'promoted_offerings'
    url = 'url'
    webhook = 'webhook'

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 css
var daast
var html
var image
var javascript
var markdown
var promoted_offerings
var text
var url
var vast
var video
var webhook
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 BrandManifest (**data: Any)
Expand source code
class BrandManifest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    assets: Annotated[
        list[Asset] | None,
        Field(
            description='Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files.'
        ),
    ] = None
    colors: Annotated[Colors | None, Field(description='Brand color palette')] = None
    contact: Annotated[Contact | None, Field(description='Brand contact information')] = None
    disclaimers: Annotated[
        list[Disclaimer] | None,
        Field(description='Legal disclaimers or required text that must appear in creatives'),
    ] = None
    fonts: Annotated[Fonts | None, Field(description='Brand typography guidelines')] = None
    industry: Annotated[
        str | None,
        Field(
            description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
        ),
    ] = None
    logos: Annotated[
        list[Logo] | None,
        Field(description='Brand logo assets with semantic tags for different use cases'),
    ] = None
    metadata: Annotated[Metadata | None, Field(description='Additional brand metadata')] = None
    name: Annotated[str, Field(description='Brand or business name')]
    product_catalog: Annotated[
        ProductCatalog | None,
        Field(
            description='Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection.'
        ),
    ] = None
    tagline: Annotated[str | None, Field(description='Brand tagline or slogan')] = None
    target_audience: Annotated[
        str | None, Field(description='Primary target audience description')
    ] = None
    tone: Annotated[
        str | None,
        Field(
            description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
        ),
    ] = None
    url: Annotated[
        AnyUrl | None,
        Field(
            description='Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 colorsColors | None
var contactContact | None
var disclaimers : list[Disclaimer] | None
var fontsFonts | None
var industry : str | None
var logos : list[Logo] | None
var metadataMetadata | None
var model_config
var name : str
var product_catalogProductCatalog | None
var tagline : str | None
var target_audience : str | None
var tone : str | None
var url : pydantic.networks.AnyUrl | None

Inherited members

class BuildCreativeRequest (**data: Any)
Expand source code
class BuildCreativeRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = 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 (e.g., promoted_offerings for generative formats). For transformation (e.g., resizing, reformatting), this is the complete creative to adapt.'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = 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.'
        ),
    ] = None
    target_format_id: Annotated[
        format_id.FormatId,
        Field(
            description='Format ID to generate. The format definition specifies required input assets and output structure.'
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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_manifestCreativeManifest | None
var extExtensionObject | None
var message : str | None
var model_config
var target_format_idFormatId

Inherited members

class BuildCreativeResponse (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class BuildCreativeResponse(RootModel[BuildCreativeResponse1 | BuildCreativeResponse2]):
    root: Annotated[
        BuildCreativeResponse1 | BuildCreativeResponse2,
        Field(
            description='Response containing the transformed or generated creative manifest, ready for use with preview_creative or sync_creatives. Returns either the complete creative manifest OR error information, never both.',
            title='Build Creative Response',
        ),
    ]

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[BuildCreativeResponse1, BuildCreativeResponse2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootBuildCreativeResponse1 | BuildCreativeResponse2
class BuildCreativeSuccessResponse (**data: Any)
Expand source code
class BuildCreativeResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    creative_manifest: Annotated[
        creative_manifest_1.CreativeManifest,
        Field(description='The generated or transformed creative manifest'),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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_manifestCreativeManifest
var extExtensionObject | None
var model_config

Inherited members

class BuildCreativeErrorResponse (**data: Any)
Expand source code
class BuildCreativeResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    errors: Annotated[
        list[error.Error],
        Field(
            description='Array of errors explaining why creative generation failed', min_length=1
        ),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 CpcPricingOption (**data: Any)
Expand source code
class CpcPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[True],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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
    pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'cpc_usd_fixed')"
        ),
    ]
    rate: Annotated[float, Field(description='Fixed CPC rate (cost per click)', ge=0.0)]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[True]
var min_spend_per_package : float | None
var model_config
var pricing_model : Literal['cpc']
var pricing_option_id : str
var rate : float

Inherited members

class CpcvPricingOption (**data: Any)
Expand source code
class CpcvPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[True],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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
    pricing_model: Annotated[
        Literal['cpcv'], Field(description='Cost per completed view (100% completion)')
    ]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'cpcv_usd_guaranteed')"
        ),
    ]
    rate: Annotated[float, Field(description='Fixed CPCV rate (cost per 100% completion)', ge=0.0)]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[True]
var min_spend_per_package : float | None
var model_config
var pricing_model : Literal['cpcv']
var pricing_option_id : str
var rate : float

Inherited members

class CpmAuctionPricingOption (**data: Any)
Expand source code
class CpmAuctionPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[False],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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_guidance: Annotated[
        PriceGuidance, Field(description='Pricing guidance for auction-based CPM bidding')
    ]
    pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_auction')"
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[False]
var min_spend_per_package : float | None
var model_config
var price_guidancePriceGuidance
var pricing_model : Literal['cpm']
var pricing_option_id : str

Inherited members

class CpmFixedRatePricingOption (**data: Any)
Expand source code
class CpmFixedRatePricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[True],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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
    pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_guaranteed')"
        ),
    ]
    rate: Annotated[float, Field(description='Fixed CPM rate (cost per 1,000 impressions)', ge=0.0)]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[True]
var min_spend_per_package : float | None
var model_config
var pricing_model : Literal['cpm']
var pricing_option_id : str
var rate : float

Inherited members

class CppPricingOption (**data: Any)
Expand source code
class CppPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[True],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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
    parameters: Annotated[
        Parameters,
        Field(description='CPP-specific parameters for demographic targeting and GRP requirements'),
    ]
    pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'cpp_usd_p18-49')"
        ),
    ]
    rate: Annotated[float, Field(description='Fixed CPP rate (cost per rating point)', ge=0.0)]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[True]
var min_spend_per_package : float | None
var model_config
var parametersParameters
var pricing_model : Literal['cpp']
var pricing_option_id : str
var rate : float

Inherited members

class CpvPricingOption (**data: Any)
Expand source code
class CpvPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[True],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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
    parameters: Annotated[
        Parameters, Field(description='CPV-specific parameters defining the view threshold')
    ]
    pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'cpv_usd_50pct')"
        ),
    ]
    rate: Annotated[float, Field(description='Fixed CPV rate (cost per view)', ge=0.0)]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[True]
var min_spend_per_package : float | None
var model_config
var parametersParameters
var pricing_model : Literal['cpv']
var pricing_option_id : str
var rate : float

Inherited members

class CreateMediaBuyRequest (**data: Any)
Expand source code
class CreateMediaBuyRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    brand_manifest: Annotated[
        brand_manifest_ref.BrandManifestReference,
        Field(
            description='Brand information manifest serving as the namespace and identity for this media buy. Provides brand context, assets, and product catalog. Can be provided inline or as a URL reference to a hosted manifest. Can be cached and reused across multiple requests.'
        ),
    ]
    buyer_ref: Annotated[str, Field(description="Buyer's reference identifier for this media buy")]
    context: context_1.ContextObject | None = None
    end_time: Annotated[
        AwareDatetime, Field(description='Campaign end date/time in ISO 8601 format')
    ]
    ext: ext_1.ExtensionObject | None = None
    packages: Annotated[
        list[package_request.PackageRequest], Field(description='Array of package configurations')
    ]
    po_number: Annotated[str | None, Field(description='Purchase order number for tracking')] = None
    reporting_webhook: ReportingWebhook | None = None
    start_time: start_timing.StartTiming

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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_manifestBrandManifestReference
var buyer_ref : str
var contextContextObject | None
var end_time : pydantic.types.AwareDatetime
var extExtensionObject | None
var model_config
var packages : list[PackageRequest]
var po_number : str | None
var reporting_webhookReportingWebhook | None
var start_timeStartTiming

Inherited members

class CreateMediaBuyResponse (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class CreateMediaBuyResponse(RootModel[CreateMediaBuyResponse1 | CreateMediaBuyResponse2]):
    root: Annotated[
        CreateMediaBuyResponse1 | CreateMediaBuyResponse2,
        Field(
            description='Response payload for create_media_buy task. Returns either complete success data OR error information, never both. This enforces atomic operation semantics - the media buy is either fully created or not created at all.',
            title='Create Media Buy Response',
        ),
    ]

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[CreateMediaBuyResponse1, CreateMediaBuyResponse2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootCreateMediaBuyResponse1 | CreateMediaBuyResponse2
class CreateMediaBuySuccessResponse (**data: Any)
Expand source code
class CreateMediaBuyResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    buyer_ref: Annotated[str, Field(description="Buyer's reference identifier for this media buy")]
    context: context_1.ContextObject | None = None
    creative_deadline: Annotated[
        AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline')
    ] = None
    ext: ext_1.ExtensionObject | None = None
    media_buy_id: Annotated[
        str, Field(description="Publisher's unique identifier for the created media buy")
    ]
    packages: Annotated[
        list[package.Package],
        Field(description='Array of created packages with complete state information'),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 buyer_ref : str
var contextContextObject | None
var creative_deadline : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var media_buy_id : str
var model_config
var packages : list[Package]

Inherited members

class CreateMediaBuyErrorResponse (**data: Any)
Expand source code
class CreateMediaBuyResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    errors: Annotated[
        list[error.Error],
        Field(description='Array of errors explaining why the operation failed', min_length=1),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    assets: Annotated[
        dict[
            str,
            image_asset.ImageAsset
            | video_asset.VideoAsset
            | audio_asset.AudioAsset
            | text_asset.TextAsset
            | html_asset.HtmlAsset
            | css_asset.CssAsset
            | javascript_asset.JavascriptAsset
            | vast_asset.VastAsset
            | daast_asset.DaastAsset
            | promoted_offerings.PromotedOfferings
            | url_asset.UrlAsset,
        ]
        | None,
        Field(description='Assets for this creative, keyed by asset_role'),
    ] = None
    assignments: Annotated[
        Assignments | None,
        Field(description='Current package assignments (included when include_assignments=true)'),
    ] = None
    created_date: Annotated[
        AwareDatetime, Field(description='When the creative was uploaded to the library')
    ]
    creative_id: Annotated[str, Field(description='Unique identifier for the creative')]
    format_id: Annotated[
        format_id_1.FormatId,
        Field(description='Format identifier specifying which format this creative conforms to'),
    ]
    name: Annotated[str, Field(description='Human-readable creative name')]
    performance: Annotated[
        Performance | None,
        Field(
            description='Aggregated performance metrics (included when include_performance=true)'
        ),
    ] = None
    status: Annotated[
        creative_status.CreativeStatus, Field(description='Current approval status of the creative')
    ]
    sub_assets: Annotated[
        list[sub_asset.SubAsset] | None,
        Field(
            description='Sub-assets for multi-asset formats (included when include_sub_assets=true)'
        ),
    ] = None
    tags: Annotated[
        list[str] | None, Field(description='User-defined tags for organization and searchability')
    ] = None
    updated_date: Annotated[AwareDatetime, Field(description='When the creative was last modified')]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 | TextAsset | HtmlAsset | CssAsset | JavascriptAsset | VastAsset | DaastAsset | PromotedOfferings | UrlAsset] | None
var assignmentsAssignments | None
var created_date : pydantic.types.AwareDatetime
var creative_id : str
var format_idFormatId
var model_config
var name : str
var performancePerformance | None
var statusCreativeStatus
var sub_assets : list[SubAsset] | None
var tags : list[str] | None
var updated_date : pydantic.types.AwareDatetime

Inherited members

class CreativeFilters (**data: Any)
Expand source code
class CreativeFilters(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    assigned_to_package: Annotated[
        str | None, Field(description='Filter creatives assigned to this specific package')
    ] = None
    assigned_to_packages: Annotated[
        list[str] | None, Field(description='Filter creatives assigned to any of these packages')
    ] = None
    buyer_refs: Annotated[
        list[str] | None,
        Field(
            description='Filter creatives assigned to media buys with any of these buyer references'
        ),
    ] = 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
    creative_ids: Annotated[
        list[str] | None, Field(description='Filter by specific creative IDs', max_length=100)
    ] = None
    format: Annotated[
        str | None,
        Field(description='Filter by creative format type (e.g., video, audio, display)'),
    ] = None
    formats: Annotated[
        list[str] | None, Field(description='Filter by multiple creative format types')
    ] = None
    has_performance_data: Annotated[
        bool | None, Field(description='Filter creatives that have performance data when true')
    ] = None
    media_buy_ids: Annotated[
        list[str] | None, Field(description='Filter creatives assigned to any of these media buys')
    ] = None
    name_contains: Annotated[
        str | None,
        Field(description='Filter by creative names containing this text (case-insensitive)'),
    ] = None
    status: Annotated[
        creative_status.CreativeStatus | None,
        Field(description='Filter by creative approval status'),
    ] = None
    statuses: Annotated[
        list[creative_status.CreativeStatus] | None,
        Field(description='Filter by multiple creative statuses'),
    ] = None
    tags: Annotated[
        list[str] | None, Field(description='Filter by creative tags (all tags must match)')
    ] = None
    tags_any: Annotated[
        list[str] | None, Field(description='Filter by creative tags (any tag must match)')
    ] = None
    unassigned: Annotated[
        bool | None,
        Field(
            description='Filter for unassigned creatives when true, assigned creatives when false'
        ),
    ] = 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

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 assigned_to_package : str | None
var assigned_to_packages : list[str] | None
var buyer_refs : 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 : str | None
var formats : list[str] | None
var has_performance_data : bool | None
var media_buy_ids : list[str] | None
var model_config
var name_contains : str | None
var statusCreativeStatus | 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='forbid',
    )
    assets: Annotated[
        dict[
            str,
            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
            | promoted_offerings.PromotedOfferings,
        ],
        Field(
            description="Map of asset IDs to actual asset content. Each key MUST match an asset_id from the format's assets_required 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: Creative manifest validation MUST be performed in the context of the format specification. The format defines what type each asset_id should be, which eliminates any validation ambiguity."
        ),
    ]
    ext: ext_1.ExtensionObject | None = None
    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'})."
        ),
    ]
    promoted_offering: Annotated[
        str | None,
        Field(
            description='Product name or offering being advertised. Maps to promoted_offerings in create_media_buy request to associate creative with the product being promoted.'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 | PromotedOfferings]
var extExtensionObject | None
var format_idFormatId
var model_config
var promoted_offering : str | None

Inherited members

class CreativeStatus (*args, **kwds)
Expand source code
class CreativeStatus(Enum):
    processing = 'processing'
    approved = 'approved'
    rejected = 'rejected'
    pending_review = 'pending_review'

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 processing
var rejected
class UrlDaastAsset (**data: Any)
Expand source code
class DaastAsset1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    companion_ads: Annotated[
        bool | None, Field(description='Whether companion display ads are included')
    ] = None
    daast_version: Annotated[
        daast_version_1.DaastVersion | None, Field(description='DAAST specification version')
    ] = None
    delivery_type: Annotated[
        Literal['url'],
        Field(description='Discriminator indicating DAAST is delivered via URL endpoint'),
    ]
    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
    url: Annotated[AnyUrl, Field(description='URL endpoint that returns DAAST XML')]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 tracking_events : list[DaastTrackingEvent] | None
var url : pydantic.networks.AnyUrl

Inherited members

class InlineDaastAsset (**data: Any)
Expand source code
class DaastAsset2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    companion_ads: Annotated[
        bool | None, Field(description='Whether companion display ads are included')
    ] = None
    content: Annotated[str, Field(description='Inline DAAST XML content')]
    daast_version: Annotated[
        daast_version_1.DaastVersion | None, Field(description='DAAST specification version')
    ] = None
    delivery_type: Annotated[
        Literal['inline'],
        Field(description='Discriminator indicating DAAST is delivered as inline XML content'),
    ]
    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

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 tracking_events : list[DaastTrackingEvent] | None

Inherited members

class PlatformDeployment (**data: Any)
Expand source code
class Deployment1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    account: Annotated[str | None, Field(description='Account identifier if applicable')] = None
    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
    deployed_at: Annotated[
        AwareDatetime | None,
        Field(description='Timestamp when activation completed (if is_live=true)'),
    ] = 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
    is_live: Annotated[
        bool, Field(description='Whether signal is currently active on this deployment')
    ]
    platform: Annotated[str, Field(description='Platform identifier for DSPs')]
    type: Annotated[
        Literal['platform'],
        Field(description='Discriminator indicating this is a platform-based deployment'),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    account: Annotated[str | None, Field(description='Account identifier if applicable')] = None
    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
    agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')]
    deployed_at: Annotated[
        AwareDatetime | None,
        Field(description='Timestamp when activation completed (if is_live=true)'),
    ] = 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
    is_live: Annotated[
        bool, Field(description='Whether signal is currently active on this deployment')
    ]
    type: Annotated[
        Literal['agent'],
        Field(description='Discriminator indicating this is an agent URL-based deployment'),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    account: Annotated[
        str | None, Field(description='Optional account identifier on the platform')
    ] = None
    platform: Annotated[
        str,
        Field(description="Platform identifier for DSPs (e.g., 'the-trade-desk', 'amazon-dsp')"),
    ]
    type: Annotated[
        Literal['platform'],
        Field(description='Discriminator indicating this is a platform-based deployment'),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    account: Annotated[
        str | None, Field(description='Optional account identifier on the agent')
    ] = None
    agent_url: Annotated[
        AnyUrl, Field(description='URL identifying the deployment agent (for sales agents, etc.)')
    ]
    type: Annotated[
        Literal['agent'],
        Field(description='Discriminator indicating this is an agent URL-based deployment'),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 Error (**data: Any)
Expand source code
class Error(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    code: Annotated[str, Field(description='Error code for programmatic handling')]
    details: Annotated[Any | None, Field(description='Additional task-specific error details')] = (
        None
    )
    field: Annotated[
        str | None,
        Field(description="Field path associated with the error (e.g., 'packages[0].targeting')"),
    ] = None
    message: Annotated[str, Field(description='Human-readable error message')]
    retry_after: Annotated[
        float | None, Field(description='Seconds to wait before retrying the operation', ge=0.0)
    ] = None
    suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 : typing.Any | None
var field : str | None
var message : str
var model_config
var retry_after : float | None
var suggestion : str | None

Inherited members

class FlatRatePricingOption (**data: Any)
Expand source code
class FlatRatePricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[True],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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
    parameters: Annotated[
        Parameters | None,
        Field(description='Flat rate parameters for DOOH and time-based campaigns'),
    ] = None
    pricing_model: Annotated[
        Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume')
    ]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'flat_rate_usd_24h_takeover')"
        ),
    ]
    rate: Annotated[float, Field(description='Flat rate cost', ge=0.0)]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[True]
var min_spend_per_package : float | None
var model_config
var parametersParameters | None
var pricing_model : Literal['flat_rate']
var pricing_option_id : str
var rate : float

Inherited members

class Format (**data: Any)
Expand source code
class Format(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    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
    assets_required: Annotated[
        list[AssetsRequired | AssetsRequired1] | None,
        Field(
            description='Array of required assets or asset groups for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Can contain individual assets or repeatable asset sequences (e.g., carousel products, slideshow frames).'
        ),
    ] = None
    delivery: Annotated[
        dict[str, Any] | None,
        Field(description='Delivery method specifications (e.g., hosted, VAST, third-party tags)'),
    ] = None
    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
    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
    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
    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')]
    output_format_ids: Annotated[
        list[format_id_1.FormatId] | None,
        Field(
            description='For generative formats: array of format IDs that this format can generate. When a format accepts inputs like brand_manifest and message, this specifies what concrete output formats can be produced (e.g., a generative banner format might output standard image banner formats).'
        ),
    ] = None
    preview_image: Annotated[
        AnyUrl | None,
        Field(
            description='DEPRECATED: Use format_card instead. Optional preview image URL for format browsing/discovery UI. Should be 400x300px (4:3 aspect ratio) PNG or JPG. Used as thumbnail/card image in format browsers. This field is maintained for backward compatibility but format_card provides a more flexible, structured approach.'
        ),
    ] = None
    renders: Annotated[
        list[Renders | Renders1] | 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
    supported_macros: Annotated[
        list[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.'
        ),
    ] = None
    type: Annotated[
        format_category.FormatCategory,
        Field(
            description='Media type of this format - determines rendering method and asset requirements'
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 assets_required : list[AssetsRequired | AssetsRequired1] | None
var delivery : dict[str, typing.Any] | None
var description : str | None
var example_url : pydantic.networks.AnyUrl | None
var format_cardFormatCard | None
var format_card_detailedFormatCardDetailed | None
var format_idFormatId
var model_config
var name : str
var output_format_ids : list[FormatId] | None
var preview_image : pydantic.networks.AnyUrl | None
var renders : list[Renders | Renders1] | None
var supported_macros : list[str] | None
var typeFormatCategory

Inherited members

class FormatCategory (*args, **kwds)
Expand source code
class FormatCategory(Enum):
    audio = 'audio'
    video = 'video'
    display = 'display'
    native = 'native'
    dooh = 'dooh'
    rich_media = 'rich_media'
    universal = 'universal'

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 display
var dooh
var native
var rich_media
var universal
var video
class FormatId (**data: Any)
Expand source code
class FormatId(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    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)"
        ),
    ]
    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
    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
    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

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 GetMediaBuyDeliveryRequest (**data: Any)
Expand source code
class GetMediaBuyDeliveryRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    buyer_refs: Annotated[
        list[str] | None, Field(description='Array of buyer reference IDs to get delivery data for')
    ] = None
    context: context_1.ContextObject | None = None
    end_date: Annotated[
        str | None,
        Field(
            description='End date for reporting period (YYYY-MM-DD)',
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None
    media_buy_ids: Annotated[
        list[str] | None,
        Field(description='Array of publisher media buy IDs to get delivery data for'),
    ] = None
    start_date: Annotated[
        str | None,
        Field(
            description='Start date for reporting period (YYYY-MM-DD)',
            pattern='^\\d{4}-\\d{2}-\\d{2}$',
        ),
    ] = None
    status_filter: Annotated[
        media_buy_status.MediaBuyStatus | list[media_buy_status.MediaBuyStatus] | None,
        Field(description='Filter by status. Can be a single status or array of statuses'),
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 buyer_refs : list[str] | None
var contextContextObject | None
var end_date : str | None
var extExtensionObject | None
var media_buy_ids : list[str] | None
var model_config
var start_date : str | None
var status_filterMediaBuyStatus | list[MediaBuyStatus] | None

Inherited members

class GetMediaBuyDeliveryResponse (**data: Any)
Expand source code
class GetMediaBuyDeliveryResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    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
    context: context_1.ContextObject | None = None
    currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')]
    errors: Annotated[
        list[error.Error] | None,
        Field(
            description='Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = 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.'
        ),
    ]
    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
    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 updated data'
        ),
    ] = 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
    reporting_period: Annotated[
        ReportingPeriod,
        Field(description='Date range for the report. All periods use UTC timezone.'),
    ]
    sequence_number: Annotated[
        int | None,
        Field(
            description='Sequential notification number (only present in webhook deliveries, starts at 1)',
            ge=1,
        ),
    ] = 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

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 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 sequence_number : int | None
var unavailable_count : int | None

Inherited members

class GetProductsRequest (**data: Any)
Expand source code
class GetProductsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    brand_manifest: Annotated[
        brand_manifest_ref.BrandManifestReference | None,
        Field(
            description='Brand information manifest providing brand context, assets, and product catalog. Can be provided inline or as a URL reference to a hosted manifest.'
        ),
    ] = None
    brief: Annotated[
        str | None, Field(description='Natural language description of campaign requirements')
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None
    filters: product_filters.ProductFilters | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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_manifestBrandManifestReference | None
var brief : str | None
var contextContextObject | None
var extExtensionObject | None
var filtersProductFilters | None
var model_config

Inherited members

class GetProductsResponse (**data: Any)
Expand source code
class GetProductsResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    errors: Annotated[
        list[error.Error] | None,
        Field(description='Task-specific errors and warnings (e.g., product filtering issues)'),
    ] = None
    ext: ext_1.ExtensionObject | None = None
    products: Annotated[list[product.Product], Field(description='Array of matching products')]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 products : list[Product]

Inherited members

class GetSignalsRequest (**data: Any)
Expand source code
class GetSignalsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    deliver_to: Annotated[
        DeliverTo, Field(description='Deployment targets where signals need to be activated')
    ]
    ext: ext_1.ExtensionObject | None = None
    filters: signal_filters.SignalFilters | None = None
    max_results: Annotated[
        int | None, Field(description='Maximum number of results to return', ge=1)
    ] = None
    signal_spec: Annotated[
        str, Field(description='Natural language description of the desired signals')
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 deliver_toDeliverTo
var extExtensionObject | None
var filtersSignalFilters | None
var max_results : int | None
var model_config
var signal_spec : str

Inherited members

class GetSignalsResponse (**data: Any)
Expand source code
class GetSignalsResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    errors: Annotated[
        list[error.Error] | None,
        Field(
            description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)'
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None
    signals: Annotated[list[Signal], Field(description='Array of matching signals')]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 signals : list[Signal]

Inherited members

class ListAuthorizedPropertiesRequest (**data: Any)
Expand source code
class ListAuthorizedPropertiesRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None
    publisher_domains: Annotated[
        list[PublisherDomain] | None,
        Field(
            description='Filter to specific publisher domains (optional). If omitted, returns all publishers this agent represents.',
            min_length=1,
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 publisher_domains : list[PublisherDomain] | None

Inherited members

class ListAuthorizedPropertiesResponse (**data: Any)
Expand source code
class ListAuthorizedPropertiesResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    advertising_policies: Annotated[
        str | None,
        Field(
            description="Publisher's advertising content policies, restrictions, and guidelines in natural language. May include prohibited categories, blocked advertisers, restricted tactics, brand safety requirements, or links to full policy documentation.",
            max_length=10000,
            min_length=1,
        ),
    ] = None
    context: context_1.ContextObject | None = None
    errors: Annotated[
        list[error.Error] | None,
        Field(description='Task-specific errors and warnings (e.g., property availability issues)'),
    ] = None
    ext: ext_1.ExtensionObject | None = None
    last_updated: Annotated[
        AwareDatetime | None,
        Field(
            description="ISO 8601 timestamp of when the agent's publisher authorization list was last updated. Buyers can use this to determine if their cached publisher adagents.json files might be stale."
        ),
    ] = None
    portfolio_description: Annotated[
        str | None,
        Field(
            description='Markdown-formatted description of the property portfolio, including inventory types, audience characteristics, and special features.',
            max_length=5000,
            min_length=1,
        ),
    ] = None
    primary_channels: Annotated[
        list[channels.AdvertisingChannels] | None,
        Field(
            description='Primary advertising channels represented in this property portfolio. Helps buying agents quickly filter relevance.',
            min_length=1,
        ),
    ] = None
    primary_countries: Annotated[
        list[PrimaryCountry] | None,
        Field(
            description='Primary countries (ISO 3166-1 alpha-2 codes) where properties are concentrated. Helps buying agents quickly filter relevance.',
            min_length=1,
        ),
    ] = None
    publisher_domains: Annotated[
        list[PublisherDomain],
        Field(
            description="Publisher domains this agent is authorized to represent. Buyers should fetch each publisher's adagents.json to see property definitions and verify this agent is in their authorized_agents list with authorization scope.",
            min_length=1,
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 advertising_policies : str | None
var contextContextObject | None
var errors : list[Error] | None
var extExtensionObject | None
var last_updated : pydantic.types.AwareDatetime | None
var model_config
var portfolio_description : str | None
var primary_channels : list[AdvertisingChannels] | None
var primary_countries : list[PrimaryCountry] | None
var publisher_domains : list[PublisherDomain]

Inherited members

class ListCreativeFormatsRequest (**data: Any)
Expand source code
class ListCreativeFormatsRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    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."
        ),
    ] = None
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None
    format_ids: Annotated[
        list[format_id.FormatId] | None,
        Field(
            description='Return only these specific format IDs (e.g., from get_products response)'
        ),
    ] = 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
    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
    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
    min_height: Annotated[
        int | None,
        Field(
            description='Minimum height in pixels (inclusive). Returns formats where ANY render has height >= this value.'
        ),
    ] = None
    min_width: Annotated[
        int | None,
        Field(
            description='Minimum width in pixels (inclusive). Returns formats where ANY render has width >= this value.'
        ),
    ] = None
    name_search: Annotated[
        str | None, Field(description='Search for formats by name (case-insensitive partial match)')
    ] = None
    type: Annotated[
        format_category.FormatCategory | None,
        Field(
            description='Filter by format type (technical categories with distinct requirements)'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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_types : list[AssetContentType] | None
var contextContextObject | None
var extExtensionObject | None
var 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 typeFormatCategory | None

Inherited members

class ListCreativeFormatsResponse (**data: Any)
Expand source code
class ListCreativeFormatsResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    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
    ext: ext_1.ExtensionObject | None = None
    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."
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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

Inherited members

class ListCreativesRequest (**data: Any)
Expand source code
class ListCreativesRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None
    fields: Annotated[
        list[FieldModel] | None,
        Field(description='Specific fields to include in response (omit for all fields)'),
    ] = None
    filters: creative_filters.CreativeFilters | None = None
    include_assignments: Annotated[
        bool | None, Field(description='Include package assignment information in response')
    ] = True
    include_performance: Annotated[
        bool | None, Field(description='Include aggregated performance metrics in response')
    ] = False
    include_sub_assets: Annotated[
        bool | None,
        Field(description='Include sub-assets (for carousel/native formats) in response'),
    ] = False
    pagination: Annotated[Pagination | None, Field(description='Pagination parameters')] = None
    sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 fields : list[FieldModel] | None
var filtersCreativeFilters | None
var include_assignments : bool | None
var include_performance : bool | None
var include_sub_assets : bool | None
var model_config
var paginationPagination | None
var sortSort | None

Inherited members

class ListCreativesResponse (**data: Any)
Expand source code
class ListCreativesResponse(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    creatives: Annotated[
        list[Creative], Field(description='Array of creative assets matching the query')
    ]
    ext: ext_1.ExtensionObject | None = None
    format_summary: Annotated[
        dict[str, int] | None, Field(description='Breakdown of creatives by format type')
    ] = None
    pagination: Annotated[
        Pagination, Field(description='Pagination information for navigating results')
    ]
    query_summary: Annotated[
        QuerySummary, Field(description='Summary of the query that was executed')
    ]
    status_summary: Annotated[
        StatusSummary | None, Field(description='Breakdown of creatives by status')
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 extExtensionObject | None
var format_summary : dict[str, int] | None
var model_config
var paginationPagination
var query_summaryQuerySummary
var status_summaryStatusSummary | None

Inherited members

class McpWebhookPayload (**data: Any)
Expand source code
class McpWebhookPayload(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    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
    domain: Annotated[
        adcp_domain.AdcpDomain | None,
        Field(
            description='AdCP domain this task belongs to. Helps classify the operation type at a high level.'
        ),
    ] = None
    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
    operation_id: Annotated[
        str | None,
        Field(
            description='Publisher-defined operation identifier correlating a sequence of task updates across webhooks.'
        ),
    ] = 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
    status: Annotated[
        task_status.TaskStatus,
        Field(
            description='Current task status. Webhooks are triggered for status changes after initial submission.'
        ),
    ]
    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.'
        ),
    ]
    timestamp: Annotated[
        AwareDatetime, Field(description='ISO 8601 timestamp when this webhook was generated.')
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 domainAdcpDomain | None
var message : str | None
var model_config
var operation_id : str | 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='forbid',
    )
    buyer_ref: Annotated[
        str | None, Field(description="Buyer's reference identifier for this media buy")
    ] = None
    created_at: Annotated[AwareDatetime | None, Field(description='Creation timestamp')] = None
    creative_deadline: Annotated[
        AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline')
    ] = None
    ext: ext_1.ExtensionObject | None = None
    media_buy_id: Annotated[
        str, Field(description="Publisher's unique identifier for the media buy")
    ]
    packages: Annotated[
        list[package.Package], Field(description='Array of packages within this media buy')
    ]
    promoted_offering: Annotated[
        str, Field(description='Description of advertiser and what is being promoted')
    ]
    status: media_buy_status.MediaBuyStatus
    total_budget: Annotated[float, Field(description='Total budget amount', ge=0.0)]
    updated_at: Annotated[AwareDatetime | None, Field(description='Last update timestamp')] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 buyer_ref : str | None
var created_at : pydantic.types.AwareDatetime | None
var creative_deadline : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var media_buy_id : str
var model_config
var packages : list[Package]
var promoted_offering : str
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_activation = 'pending_activation'
    active = 'active'
    paused = 'paused'
    completed = 'completed'

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 completed
var paused
var pending_activation
class Package (**data: Any)
Expand source code
class Package(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    bid_price: Annotated[
        float | None,
        Field(
            description='Bid price for auction-based CPM pricing (present if using cpm-auction-option)',
            ge=0.0,
        ),
    ] = None
    budget: Annotated[
        float | None,
        Field(
            description='Budget allocation for this package in the currency specified by the pricing option',
            ge=0.0,
        ),
    ] = None
    buyer_ref: Annotated[
        str | None, Field(description="Buyer's reference identifier for this package")
    ] = None
    creative_assignments: Annotated[
        list[creative_assignment.CreativeAssignment] | None,
        Field(description='Creative assets assigned to this package'),
    ] = None
    ext: ext_1.ExtensionObject | None = 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
    impressions: Annotated[
        float | None, Field(description='Impression goal for this package', ge=0.0)
    ] = None
    pacing: pacing_1.Pacing | None = None
    package_id: Annotated[str, Field(description="Publisher's unique identifier for the package")]
    paused: Annotated[
        bool | None,
        Field(
            description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.'
        ),
    ] = False
    pricing_option_id: Annotated[
        str | None,
        Field(
            description="ID of the selected pricing option from the product's pricing_options array"
        ),
    ] = None
    product_id: Annotated[
        str | None, Field(description='ID of the product this package is based on')
    ] = None
    targeting_overlay: targeting.TargetingOverlay | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 buyer_ref : str | None
var creative_assignments : list[CreativeAssignment] | None
var extExtensionObject | None
var format_ids_to_provide : list[FormatId] | None
var impressions : float | None
var model_config
var pacingPacing | None
var package_id : str
var paused : bool | None
var pricing_option_id : str | None
var product_id : str | None
var targeting_overlayTargetingOverlay | None

Inherited members

class PackageRequest (**data: Any)
Expand source code
class PackageRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    bid_price: Annotated[
        float | None,
        Field(
            description='Bid price for auction-based CPM pricing (required if using cpm-auction-option)',
            ge=0.0,
        ),
    ] = None
    budget: Annotated[
        float,
        Field(description="Budget allocation for this package in the media buy's currency", ge=0.0),
    ]
    buyer_ref: Annotated[str, Field(description="Buyer's reference identifier for this package")]
    creative_ids: Annotated[
        list[str] | None,
        Field(
            description='Creative IDs to assign to this package at creation time (references existing library creatives)'
        ),
    ] = None
    creatives: Annotated[
        list[creative_asset.CreativeAsset] | None,
        Field(
            description='Full creative objects to upload and assign to this package at creation time (alternative to creative_ids - creatives will be added to library). Supports both static and generative creatives.',
            max_length=100,
        ),
    ] = None
    ext: ext_1.ExtensionObject | None = None
    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
    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"
        ),
    ]
    product_id: Annotated[str, Field(description='Product ID for this package')]
    targeting_overlay: targeting.TargetingOverlay | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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
var buyer_ref : str
var creative_ids : list[str] | None
var creatives : list[CreativeAsset] | None
var extExtensionObject | None
var format_ids : list[FormatId] | None
var model_config
var pacingPacing | None
var pricing_option_id : str
var product_id : str
var targeting_overlayTargetingOverlay | None

Inherited members

class PreviewCreativeRequest (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class PreviewCreativeRequest(RootModel[PreviewCreativeRequest1 | PreviewCreativeRequest2]):
    root: Annotated[
        PreviewCreativeRequest1 | PreviewCreativeRequest2,
        Field(
            discriminator='request_type',
            description='Request to generate previews of one or more creative manifests. Accepts either a single creative request or an array of requests for batch processing.',
            title='Preview Creative Request',
        ),
    ]

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[PreviewCreativeRequest1, PreviewCreativeRequest2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootPreviewCreativeRequest1 | PreviewCreativeRequest2
class PreviewCreativeFormatRequest (**data: Any)
Expand source code
class PreviewCreativeRequest1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    creative_manifest: Annotated[
        creative_manifest_1.CreativeManifest,
        Field(
            description='Complete creative manifest with all required assets (including promoted_offerings if required by the format)'
        ),
    ]
    ext: ext_1.ExtensionObject | None = None
    format_id: Annotated[
        format_id_1.FormatId, Field(description='Format identifier for rendering the preview')
    ]
    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. If not provided, creative agent will generate default previews.'
        ),
    ] = None
    output_format: Annotated[
        preview_output_format.PreviewOutputFormat | None,
        Field(
            description="Output format for previews. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML for direct embedding). Default: 'url' for backward compatibility."
        ),
    ] = preview_output_format.PreviewOutputFormat.url
    request_type: Annotated[
        Literal['single'],
        Field(description='Discriminator indicating this is a single preview request'),
    ]
    template_id: Annotated[
        str | None, Field(description='Specific template ID for custom format rendering')
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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_manifestCreativeManifest
var extExtensionObject | None
var format_idFormatId
var inputs : list[Input] | None
var model_config
var output_formatPreviewOutputFormat | None
var request_type : Literal['single']
var template_id : str | None

Inherited members

class PreviewCreativeManifestRequest (**data: Any)
Expand source code
class PreviewCreativeRequest2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None
    output_format: Annotated[
        preview_output_format.PreviewOutputFormat | None,
        Field(
            description="Default output format for all requests in this batch. Individual requests can override this. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML for direct embedding)."
        ),
    ] = preview_output_format.PreviewOutputFormat.url
    request_type: Annotated[
        Literal['batch'],
        Field(description='Discriminator indicating this is a batch preview request'),
    ]
    requests: Annotated[
        list[Request],
        Field(
            description='Array of preview requests (1-50 items). Each follows the single request structure.',
            max_length=50,
            min_length=1,
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 output_formatPreviewOutputFormat | None
var request_type : Literal['batch']
var requests : list[Request]

Inherited members

class PreviewCreativeResponse (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class PreviewCreativeResponse(RootModel[PreviewCreativeResponse1 | PreviewCreativeResponse2]):
    root: Annotated[
        PreviewCreativeResponse1 | PreviewCreativeResponse2,
        Field(
            description='Response containing preview links for one or more creatives. Format matches the request: single preview response for single requests, batch results for batch requests.',
            title='Preview Creative Response',
        ),
    ]

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[PreviewCreativeResponse1, PreviewCreativeResponse2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootPreviewCreativeResponse1 | PreviewCreativeResponse2
class PreviewCreativeStaticResponse (**data: Any)
Expand source code
class PreviewCreativeResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    expires_at: Annotated[
        AwareDatetime, Field(description='ISO 8601 timestamp when preview links expire')
    ]
    ext: ext_1.ExtensionObject | None = None
    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
    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,
        ),
    ]
    response_type: Annotated[
        Literal['single'],
        Field(description='Discriminator indicating this is a single preview response'),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 PreviewCreativeInteractiveResponse (**data: Any)
Expand source code
class PreviewCreativeResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None
    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,
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 UrlPreviewRender (**data: Any)
Expand source code
class PreviewRender1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    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
    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.'
        ),
    ]
    render_id: Annotated[
        str, Field(description='Unique identifier for this rendered piece within the variant')
    ]
    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."
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    dimensions: Annotated[
        Dimensions | None, Field(description='Dimensions for this rendered piece')
    ] = None
    embedding: Annotated[
        Embedding | None, Field(description='Optional security and embedding metadata')
    ] = None
    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.'
        ),
    ]
    render_id: Annotated[
        str, Field(description='Unique identifier for this rendered piece within the variant')
    ]
    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."
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    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
    output_format: Annotated[
        Literal['both'],
        Field(
            description='Discriminator indicating both preview_url and preview_html are 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.'
        ),
    ]
    preview_url: Annotated[
        AnyUrl,
        Field(
            description='URL to an HTML page that renders this piece. Can be embedded in an iframe.'
        ),
    ]
    render_id: Annotated[
        str, Field(description='Unique identifier for this rendered piece within the variant')
    ]
    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."
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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):
    floor: Annotated[
        float,
        Field(
            description='Minimum bid price - publisher will reject bids under this value', ge=0.0
        ),
    ]
    p25: Annotated[float | None, Field(description='25th percentile winning price', ge=0.0)] = None
    p50: Annotated[float | None, Field(description='Median winning price', ge=0.0)] = None
    p75: Annotated[float | None, Field(description='75th percentile winning price', ge=0.0)] = None
    p90: Annotated[float | None, Field(description='90th percentile winning price', ge=0.0)] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 floor : float
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'
    flat_rate = 'flat_rate'

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 cpc
var cpcv
var cpm
var cpp
var cpv
var flat_rate
var vcpm
class Product (**data: Any)
Expand source code
class Product(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    brief_relevance: Annotated[
        str | None,
        Field(
            description='Explanation of why this product matches the brief (only included when brief is provided)'
        ),
    ] = None
    creative_policy: creative_policy_1.CreativePolicy | None = None
    delivery_measurement: Annotated[
        DeliveryMeasurement,
        Field(
            description='Measurement provider and methodology for delivery metrics. The buyer accepts the declared provider as the source of truth for the buy. REQUIRED for all products.'
        ),
    ]
    delivery_type: delivery_type_1.DeliveryType
    description: Annotated[
        str, Field(description='Detailed description of the product and its inventory')
    ]
    estimated_exposures: Annotated[
        int | None,
        Field(description='Estimated exposures/impressions for guaranteed products', ge=0),
    ] = None
    expires_at: Annotated[
        AwareDatetime | None, Field(description='Expiration timestamp for custom products')
    ] = None
    ext: ext_1.ExtensionObject | None = 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'
        ),
    ]
    is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None
    measurement: measurement_1.Measurement | None = None
    name: Annotated[str, Field(description='Human-readable product name')]
    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
    pricing_options: Annotated[
        list[pricing_option.PricingOption],
        Field(description='Available pricing models for this product', min_length=1),
    ]
    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
    product_id: Annotated[str, Field(description='Unique identifier for the product')]
    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,
        ),
    ]
    reporting_capabilities: reporting_capabilities_1.ReportingCapabilities | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 creative_policyCreativePolicy | None
var delivery_measurementDeliveryMeasurement
var delivery_typeDeliveryType
var description : str
var estimated_exposures : int | None
var expires_at : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var format_ids : list[FormatId]
var is_custom : bool | None
var measurementMeasurement | None
var model_config
var name : str
var placements : list[Placement] | None
var pricing_options : list[PricingOption]
var product_cardProductCard | None
var product_card_detailedProductCardDetailed | None
var product_id : str
var publisher_properties : list[PublisherPropertySelector]
var reporting_capabilitiesReportingCapabilities | None

Inherited members

class ProductFilters (**data: Any)
Expand source code
class ProductFilters(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    budget_range: Annotated[
        BudgetRange | BudgetRange1 | None,
        Field(description='Budget range to filter appropriate products'),
    ] = None
    channels: Annotated[
        list[channels_1.AdvertisingChannels] | None,
        Field(description="Filter by advertising channels (e.g., ['display', 'video', 'dooh'])"),
    ] = None
    countries: Annotated[
        list[Country] | None,
        Field(
            description="Filter by target countries using ISO 3166-1 alpha-2 country codes (e.g., ['US', 'CA', 'GB'])"
        ),
    ] = None
    delivery_type: delivery_type_1.DeliveryType | None = None
    end_date: Annotated[
        date | None,
        Field(
            description='Campaign end date (ISO 8601 date format: YYYY-MM-DD) for availability checks'
        ),
    ] = None
    format_ids: Annotated[
        list[format_id.FormatId] | None, Field(description='Filter by specific format IDs')
    ] = None
    format_types: Annotated[
        list[format_category.FormatCategory] | None, Field(description='Filter by format types')
    ] = None
    is_fixed_price: Annotated[
        bool | None, Field(description='Filter for fixed price vs auction products')
    ] = None
    min_exposures: Annotated[
        int | None,
        Field(description='Minimum exposures/impressions needed for measurement validity', ge=1),
    ] = None
    standard_formats_only: Annotated[
        bool | None, Field(description='Only return products accepting IAB standard formats')
    ] = None
    start_date: Annotated[
        date | None,
        Field(
            description='Campaign start date (ISO 8601 date format: YYYY-MM-DD) for availability checks'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 | BudgetRange1 | None
var channels : list[AdvertisingChannels] | None
var countries : list[Country] | None
var delivery_typeDeliveryType | None
var end_date : datetime.date | None
var format_ids : list[FormatId] | None
var format_types : list[FormatCategory] | None
var is_fixed_price : bool | None
var min_exposures : int | None
var model_config
var standard_formats_only : bool | None
var start_date : datetime.date | None

Inherited members

class Property (**data: Any)
Expand source code
class Property(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    identifiers: Annotated[
        list[Identifier], Field(description='Array of identifiers for this property', min_length=1)
    ]
    name: Annotated[str, Field(description='Human-readable property name')]
    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')
    ]
    publisher_domain: Annotated[
        str | None,
        Field(
            description='Domain where adagents.json should be checked for authorization validation. Required for list_authorized_properties response. Optional in adagents.json (file location implies domain).'
        ),
    ] = None
    tags: Annotated[
        list[property_tag.PropertyTag] | None,
        Field(
            description='Tags for categorization and grouping (e.g., network membership, content categories)'
        ),
    ] = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 tags : list[PropertyTag] | None

Inherited members

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 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 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 (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class ProvidePerformanceFeedbackRequest(
    RootModel[ProvidePerformanceFeedbackRequest1 | ProvidePerformanceFeedbackRequest2]
):
    root: Annotated[
        ProvidePerformanceFeedbackRequest1 | ProvidePerformanceFeedbackRequest2,
        Field(
            description='Request payload for provide_performance_feedback task',
            title='Provide Performance Feedback Request',
        ),
    ]

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[ProvidePerformanceFeedbackRequest1, ProvidePerformanceFeedbackRequest2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootProvidePerformanceFeedbackRequest1 | ProvidePerformanceFeedbackRequest2
class ProvidePerformanceFeedbackResponse (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class ProvidePerformanceFeedbackResponse(
    RootModel[ProvidePerformanceFeedbackResponse1 | ProvidePerformanceFeedbackResponse2]
):
    root: Annotated[
        ProvidePerformanceFeedbackResponse1 | ProvidePerformanceFeedbackResponse2,
        Field(
            description='Response payload for provide_performance_feedback task. Returns either success confirmation OR error information, never both.',
            title='Provide Performance Feedback Response',
        ),
    ]

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[ProvidePerformanceFeedbackResponse1, ProvidePerformanceFeedbackResponse2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootProvidePerformanceFeedbackResponse1 | ProvidePerformanceFeedbackResponse2
class ProvidePerformanceFeedbackSuccessResponse (**data: Any)
Expand source code
class ProvidePerformanceFeedbackResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None
    success: Annotated[
        Literal[True],
        Field(description='Whether the performance feedback was successfully received'),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 success : Literal[True]

Inherited members

class ProvidePerformanceFeedbackErrorResponse (**data: Any)
Expand source code
class ProvidePerformanceFeedbackResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    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,
        ),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    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.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    property_ids: Annotated[
        list[property_id.PropertyId],
        Field(description="Specific property IDs from the publisher's adagents.json", min_length=1),
    ]
    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'),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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='forbid',
    )
    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,
        ),
    ]
    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')
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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):
    authentication: Annotated[
        Authentication,
        Field(description='Authentication configuration for webhook delivery (A2A-compatible)'),
    ]
    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
    url: Annotated[AnyUrl, Field(description='Webhook endpoint URL for task status notifications')]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 authenticationAuthentication
var model_config
var token : str | None
var url : pydantic.networks.AnyUrl

Inherited members

class SignalFilters (**data: Any)
Expand source code
class SignalFilters(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    catalog_types: Annotated[
        list[signal_catalog_type.SignalCatalogType] | None,
        Field(description='Filter by catalog type'),
    ] = None
    data_providers: Annotated[
        list[str] | None, Field(description='Filter by specific data providers')
    ] = None
    max_cpm: Annotated[float | None, Field(description='Maximum CPM price filter', ge=0.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.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 min_coverage_percentage : float | None
var model_config

Inherited members

class MediaSubAsset (**data: Any)
Expand source code
class SubAsset1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    asset_id: Annotated[
        str, Field(description='Unique identifier for the asset within the creative')
    ]
    asset_kind: Annotated[
        Literal['media'],
        Field(description='Discriminator indicating this is a media asset with content_uri'),
    ]
    asset_type: Annotated[
        str,
        Field(
            description='Type of asset. Common types: thumbnail_image, product_image, featured_image, logo'
        ),
    ]
    content_uri: Annotated[AnyUrl, Field(description='URL for media assets (images, videos, etc.)')]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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_id : str
var asset_kind : Literal['media']
var asset_type : str
var content_uri : pydantic.networks.AnyUrl
var model_config

Inherited members

class TextSubAsset (**data: Any)
Expand source code
class SubAsset2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    asset_id: Annotated[
        str, Field(description='Unique identifier for the asset within the creative')
    ]
    asset_kind: Annotated[
        Literal['text'],
        Field(description='Discriminator indicating this is a text asset with content'),
    ]
    asset_type: Annotated[
        str,
        Field(
            description='Type of asset. Common types: headline, body_text, cta_text, price_text, sponsor_name, author_name, click_url'
        ),
    ]
    content: Annotated[
        str | list[str],
        Field(
            description='Text content for text-based assets like headlines, body text, CTA text, etc.'
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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_id : str
var asset_kind : Literal['text']
var asset_type : str
var content : str | list[str]
var model_config

Inherited members

class SyncCreativesRequest (**data: Any)
Expand source code
class SyncCreativesRequest(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    assignments: Annotated[
        dict[str, list[str]] | None,
        Field(description='Optional bulk assignment of creatives to packages'),
    ] = None
    context: context_1.ContextObject | None = None
    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,
        ),
    ] = None
    creatives: Annotated[
        list[creative_asset.CreativeAsset],
        Field(description='Array of creative assets to sync (create or update)', max_length=100),
    ]
    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.'
        ),
    ] = False
    dry_run: Annotated[
        bool | None,
        Field(
            description='When true, preview changes without applying them. Returns what would be created/updated/deleted.'
        ),
    ] = False
    ext: ext_1.ExtensionObject | None = None
    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 (typically for large bulk operations or manual approval/HITL).'
        ),
    ] = None
    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

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 assignments : dict[str, list[str]] | 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 model_config
var push_notification_configPushNotificationConfig | None
var validation_modeValidationMode | None

Inherited members

class SyncCreativesResponse (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class SyncCreativesResponse(RootModel[SyncCreativesResponse1 | SyncCreativesResponse2]):
    root: Annotated[
        SyncCreativesResponse1 | SyncCreativesResponse2,
        Field(
            description='Response from creative sync operation. Returns either per-creative results (best-effort processing) OR operation-level errors (complete failure). This enforces atomic semantics at the operation level while allowing per-item failures within successful operations.',
            title='Sync Creatives Response',
        ),
    ]

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[SyncCreativesResponse1, SyncCreativesResponse2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootSyncCreativesResponse1 | SyncCreativesResponse2
class SyncCreativesSuccessResponse (**data: Any)
Expand source code
class SyncCreativesResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = 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."
        ),
    ]
    dry_run: Annotated[
        bool | None, Field(description='Whether this was a dry run (no actual changes made)')
    ] = None
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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

Inherited members

class SyncCreativesErrorResponse (**data: Any)
Expand source code
class SyncCreativesResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    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,
        ),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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

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[ActivateSignalResponse]
  • adcp.types.core.TaskResult[AdcpAsyncResponseData]
  • adcp.types.core.TaskResult[BuildCreativeResponse]
  • adcp.types.core.TaskResult[CreateMediaBuyResponse]
  • adcp.types.core.TaskResult[GetMediaBuyDeliveryResponse]
  • adcp.types.core.TaskResult[GetProductsResponse]
  • adcp.types.core.TaskResult[GetSignalsResponse]
  • adcp.types.core.TaskResult[ListAuthorizedPropertiesResponse]
  • adcp.types.core.TaskResult[ListCreativeFormatsResponse]
  • adcp.types.core.TaskResult[ListCreativesResponse]
  • adcp.types.core.TaskResult[PreviewCreativeResponse]
  • adcp.types.core.TaskResult[ProvidePerformanceFeedbackResponse]
  • adcp.types.core.TaskResult[SyncCreativesResponse]
  • adcp.types.core.TaskResult[UpdateMediaBuyResponse]

Class variables

var data : ~T | None
var debug_infoDebugInfo | None
var error : str | None
var message : str | None
var metadata : dict[str, typing.Any] | None
var model_config
var needs_inputNeedsInputInfo | None
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 UpdateMediaBuyRequest (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class UpdateMediaBuyRequest(RootModel[UpdateMediaBuyRequest1 | UpdateMediaBuyRequest2]):
    root: Annotated[
        UpdateMediaBuyRequest1 | UpdateMediaBuyRequest2,
        Field(
            description='Request parameters for updating campaign and package settings',
            title='Update Media Buy Request',
        ),
    ]

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[UpdateMediaBuyRequest1, UpdateMediaBuyRequest2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootUpdateMediaBuyRequest1 | UpdateMediaBuyRequest2
class UpdateMediaBuyPackagesRequest (**data: Any)
Expand source code
class UpdateMediaBuyRequest1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    buyer_ref: Annotated[
        str | None, Field(description="Buyer's reference for the media buy to update")
    ] = None
    context: context_1.ContextObject | None = None
    end_time: Annotated[
        AwareDatetime | None, Field(description='New end date/time in ISO 8601 format')
    ] = None
    ext: ext_1.ExtensionObject | None = None
    media_buy_id: Annotated[str, Field(description="Publisher's ID of the media buy to update")]
    packages: Annotated[
        list[Packages | Packages1] | None, Field(description='Package-specific updates')
    ] = None
    paused: Annotated[
        bool | None,
        Field(description='Pause/resume the entire media buy (true = paused, false = active)'),
    ] = 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.'
        ),
    ] = None
    start_time: start_timing.StartTiming | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 buyer_ref : str | None
var contextContextObject | None
var end_time : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var media_buy_id : str
var model_config
var packages : list[Packages | Packages1] | None
var paused : bool | None
var push_notification_configPushNotificationConfig | None
var start_timeStartTiming | None

Inherited members

class UpdateMediaBuyPropertiesRequest (**data: Any)
Expand source code
class UpdateMediaBuyRequest2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    buyer_ref: Annotated[str, Field(description="Buyer's reference for the media buy to update")]
    context: context_1.ContextObject | None = None
    end_time: Annotated[
        AwareDatetime | None, Field(description='New end date/time in ISO 8601 format')
    ] = None
    ext: ext_1.ExtensionObject | None = None
    media_buy_id: Annotated[
        str | None, Field(description="Publisher's ID of the media buy to update")
    ] = None
    packages: Annotated[
        list[Packages2 | Packages3] | None, Field(description='Package-specific updates')
    ] = None
    paused: Annotated[
        bool | None,
        Field(description='Pause/resume the entire media buy (true = paused, false = active)'),
    ] = 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.'
        ),
    ] = None
    start_time: start_timing.StartTiming | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 buyer_ref : str
var contextContextObject | None
var end_time : pydantic.types.AwareDatetime | None
var extExtensionObject | None
var media_buy_id : str | None
var model_config
var packages : list[Packages | Packages1] | None
var paused : bool | None
var push_notification_configPushNotificationConfig | None
var start_timeStartTiming | None

Inherited members

class UpdateMediaBuyResponse (root: RootModelRootType = PydanticUndefined, **data)
Expand source code
class UpdateMediaBuyResponse(RootModel[UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2]):
    root: Annotated[
        UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2,
        Field(
            description='Response payload for update_media_buy task. Returns either complete success data OR error information, never both. This enforces atomic operation semantics - updates are either fully applied or not applied at all.',
            title='Update Media Buy Response',
        ),
    ]

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[UpdateMediaBuyResponse1, UpdateMediaBuyResponse2]]
  • pydantic.root_model.RootModel
  • pydantic.main.BaseModel
  • typing.Generic

Class variables

var model_config
var rootUpdateMediaBuyResponse1 | UpdateMediaBuyResponse2
class UpdateMediaBuySuccessResponse (**data: Any)
Expand source code
class UpdateMediaBuyResponse1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    affected_packages: Annotated[
        list[package.Package] | None,
        Field(description='Array of packages that were modified with complete state information'),
    ] = None
    buyer_ref: Annotated[str, Field(description="Buyer's reference identifier for the media buy")]
    context: context_1.ContextObject | None = None
    ext: ext_1.ExtensionObject | None = None
    implementation_date: Annotated[
        AwareDatetime | None,
        Field(description='ISO 8601 timestamp when changes take effect (null if pending approval)'),
    ] = None
    media_buy_id: Annotated[str, Field(description="Publisher's identifier for the media buy")]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 buyer_ref : str
var contextContextObject | None
var extExtensionObject | None
var implementation_date : pydantic.types.AwareDatetime | None
var media_buy_id : str
var model_config

Inherited members

class UpdateMediaBuyErrorResponse (**data: Any)
Expand source code
class UpdateMediaBuyResponse2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    context: context_1.ContextObject | None = None
    errors: Annotated[
        list[error.Error],
        Field(description='Array of errors explaining why the operation failed', min_length=1),
    ]
    ext: ext_1.ExtensionObject | None = None

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 UrlVastAsset (**data: Any)
Expand source code
class VastAsset1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    delivery_type: Annotated[
        Literal['url'],
        Field(description='Discriminator indicating VAST is delivered via URL endpoint'),
    ]
    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
    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

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 delivery_type : Literal['url']
var duration_ms : int | None
var model_config
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='forbid',
    )
    content: Annotated[str, Field(description='Inline VAST XML content')]
    delivery_type: Annotated[
        Literal['inline'],
        Field(description='Discriminator indicating VAST is delivered as inline XML content'),
    ]
    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
    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

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 content : str
var delivery_type : Literal['inline']
var duration_ms : int | None
var model_config
var tracking_events : list[VastTrackingEvent] | None
var vast_versionVastVersion | None
var vpaid_enabled : bool | None

Inherited members

class VcpmAuctionPricingOption (**data: Any)
Expand source code
class VcpmAuctionPricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[False],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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_guidance: Annotated[
        PriceGuidance, Field(description='Statistical guidance for auction pricing')
    ]
    pricing_model: Annotated[
        Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)')
    ]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'vcpm_usd_auction')"
        ),
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[False]
var min_spend_per_package : float | None
var model_config
var price_guidancePriceGuidance
var pricing_model : Literal['vcpm']
var pricing_option_id : str

Inherited members

class VcpmFixedRatePricingOption (**data: Any)
Expand source code
class VcpmFixedRatePricingOption(AdCPBaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    currency: Annotated[
        str,
        Field(
            description='ISO 4217 currency code',
            examples=['USD', 'EUR', 'GBP', 'JPY'],
            pattern='^[A-Z]{3}$',
        ),
    ]
    is_fixed: Annotated[
        Literal[True],
        Field(description='Whether this is a fixed rate (true) or auction-based (false)'),
    ]
    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
    pricing_model: Annotated[
        Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)')
    ]
    pricing_option_id: Annotated[
        str,
        Field(
            description="Unique identifier for this pricing option within the product (e.g., 'vcpm_usd_guaranteed')"
        ),
    ]
    rate: Annotated[
        float, Field(description='Fixed vCPM rate (cost per 1,000 viewable impressions)', ge=0.0)
    ]

Base model for AdCP types with spec-compliant serialization.

AdCP JSON schemas use additionalProperties: false and do not allow null for optional fields. Therefore, optional fields must be omitted entirely when not present (not sent as null).

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 is_fixed : Literal[True]
var min_spend_per_package : float | None
var model_config
var pricing_model : Literal['vcpm']
var pricing_option_id : str
var rate : float

Inherited members

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