Package adcp
Sub-modules
adcp.adagentsadcp.audit_sink-
Audit-event observability seam paralleling :class:
~adcp.webhook_supervisor.DeliveryLogSink… adcp.canonical_formats-
Pythonic v1↔v2 canonical-formats projection layer …
adcp.capabilities-
Feature capability resolution for AdCP …
adcp.clientadcp.compat-
AdCP wire-shape compatibility for buyers on older spec versions …
adcp.configadcp.decisioning-
Decisioning Platform v6.0 — Protocol-driven adopter framework …
adcp.error_sanitization-
Sanitizers for public error/account authorization metadata.
adcp.exceptions-
Exception hierarchy for AdCP client.
adcp.feed_mirror-
In-memory mirror of an AdCP agent's wholesale product and signal feeds …
adcp.migrate-
Migration tooling for the AdCP SDK …
adcp.property_registry-
PropertyRegistry — local authorization cache backed by the AAO registry …
adcp.protocolsadcp.registry-
Client for the AdCP registry API (brand, property, member, and policy lookups).
adcp.registry_sync-
Registry change feed synchronization.
adcp.schemas-
Access bundled AdCP JSON schemas by name …
adcp.server-
ADCP Server Framework …
adcp.signing-
AdCP RFC 9421 request-signing profile …
adcp.simple-
Simplified API accessor for ADCPClient …
adcp.testing-
Test helpers for AdCP client library …
adcp.types-
AdCP Type System …
adcp.utilsadcp.validation-
AdCP validation helpers …
adcp.webhook_auth-
Auth-mode strategies for :class:
WebhookSender… adcp.webhook_receiver-
One-call webhook receiver: verify signature, dedupe, parse …
adcp.webhook_sender-
One-call outbound webhook delivery for AdCP senders …
adcp.webhook_supervisor-
Webhook delivery supervisor — retry, circuit breaker, attempt audit …
adcp.webhook_supervisor_pg-
PostgreSQL-backed :class:
WebhookDeliverySupervisorfor multi-worker durability … adcp.webhook_transport_hooks-
Pre-SSRF URL rewrite hooks for :class:
WebhookSender… adcp.webhooks-
Webhook creation, signing, and reception for AdCP agents …
Functions
async def challenge_webhook_destination(*,
url: str | AnyUrl,
account_id: str,
subscriber_id: str,
sender: WebhookSender | None = None,
authentication: AdCPBaseModel | Mapping[str, Any] | None = None,
challenge: str | None = None,
timeout_seconds: float | None = None,
policy: WebhookDestinationPolicy | None = None,
field: str | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookChallengeResult-
Expand source code
async def challenge_webhook_destination( *, url: str | AnyUrl, account_id: str, subscriber_id: str, sender: WebhookSender | None = None, authentication: AdCPBaseModel | Mapping[str, Any] | None = None, challenge: str | None = None, timeout_seconds: float | None = None, policy: WebhookDestinationPolicy | None = None, field: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookChallengeResult: """Validate and prove control of a durable webhook destination. Use before activating a new or changed active ``sync_accounts.accounts[].notification_configs[]`` entry. Inactive configs can be persisted without calling this helper. ``authentication`` follows the durable config's legacy auth selector: when present, the challenge is sent with Bearer or HMAC-SHA256. When omitted, pass an RFC 9421 :class:`WebhookSender`; the helper uses that sender's webhook-signing key and the SDK-managed pinned transport. """ error_url = str(url) if isinstance(url, (str, AnyUrl)) else None if sender is not None and authentication is not None: raise WebhookChallengeError( "pass either sender= for RFC 9421 or authentication= for legacy auth, not both", reason="ambiguous_auth_mode", field=field, url=error_url, ) sender_owns_client = bool(getattr(cast(Any, sender), "_owns_client", False)) sender_transport_hooks = tuple(getattr(cast(Any, sender), "_transport_hooks", ())) if sender is not None and not sender_owns_client: raise WebhookChallengeError( "proof-of-control requires a WebhookSender constructed without client=", reason="unsafe_sender_client", field=field, url=error_url, ) if sender is not None and sender_transport_hooks: raise WebhookChallengeError( "proof-of-control does not support sender transport_hooks", reason="unsupported_sender_hooks", field=field, url=error_url, ) if sender is not None and not sender.signs_with_rfc9421: raise WebhookChallengeError( "proof-of-control requires an RFC 9421 WebhookSender when authentication is omitted", reason="sender_auth_mode_mismatch", field=field, url=error_url, suggestion=( "Use WebhookSender.from_jwk(...) for default durable configs, " "or pass config.authentication for legacy Bearer/HMAC configs." ), ) if sender is None and authentication is None: raise WebhookChallengeError( "webhook challenge requires sender= when authentication is omitted", reason="sender_required", field=field, url=error_url, suggestion=( "Pass the seller's WebhookSender, or pass config.authentication " "for legacy auth." ), ) try: destination = validate_webhook_destination_url(url, policy=policy, field=field) payload = create_webhook_challenge_payload( account_id=account_id, subscriber_id=subscriber_id, challenge=challenge, ) except WebhookDestinationValidationError as exc: raise WebhookChallengeError( str(exc), reason=exc.reason, field=exc.field, url=exc.url, suggestion=exc.suggestion, ) from exc except ValueError as exc: raise WebhookChallengeError( f"webhook challenge configuration is invalid: {exc}", reason="invalid_configuration", field=field, url=error_url, ) from exc challenge_value = payload["challenge"] try: if sender is not None: effective_timeout = ( timeout_seconds if timeout_seconds is not None else float(getattr(cast(Any, sender), "_timeout", _DEFAULT_TIMEOUT_SECONDS)) ) response = await _send_sender_webhook_challenge( url=destination.effective_url, sender=sender, payload=payload, timeout_seconds=effective_timeout, policy=destination.policy, extra_headers=extra_headers, ) status_code = response.status_code response_headers = dict(response.headers) response_body = response.content else: auth_config = _authentication_to_config(cast(Any, authentication)) response = await _send_legacy_webhook_challenge( url=destination.effective_url, authentication=auth_config, payload=payload, extra_headers=extra_headers, timeout_seconds=timeout_seconds, policy=destination.policy, ) status_code = response.status_code response_headers = dict(response.headers) response_body = response.content except httpx.TimeoutException as exc: raise WebhookChallengeError( "webhook challenge timed out", reason="timeout", field=field, url=destination.original_url, ) from exc except httpx.HTTPError as exc: raise WebhookChallengeError( f"webhook challenge request failed: {exc}", reason="request_failed", field=field, url=destination.original_url, ) from exc except ValueError as exc: raise WebhookChallengeError( f"webhook challenge configuration is invalid: {exc}", reason="invalid_configuration", field=field, url=destination.original_url, ) from exc if not 200 <= status_code < 300: raise WebhookChallengeError( f"webhook challenge failed with HTTP {status_code}", reason="http_status", field=field, url=destination.original_url, status_code=status_code, ) echoed_field = validate_webhook_challenge_response( response_body, challenge=challenge_value, field=field, url=destination.original_url, ) return WebhookChallengeResult( challenge=challenge_value, echoed_field=echoed_field, destination=destination, status_code=status_code, response_headers=response_headers, response_body=response_body, )Validate and prove control of a durable webhook destination.
Use before activating a new or changed active
sync_accounts.accounts[].notification_configs[]entry. Inactive configs can be persisted without calling this helper.authenticationfollows the durable config's legacy auth selector: when present, the challenge is sent with Bearer or HMAC-SHA256. When omitted, pass an RFC 9421 :class:WebhookSender; the helper uses that sender's webhook-signing key and the SDK-managed pinned transport. def create_a2a_webhook_payload(task_id: str,
status: TaskStatus,
context_id: str,
result: PydanticBaseModel | dict[str, Any],
timestamp: datetime | None = None) ‑> a2a_pb2.Task | a2a_pb2.TaskStatusUpdateEvent-
Expand source code
def create_a2a_webhook_payload( task_id: str, status: GeneratedTaskStatus, context_id: str, result: PydanticBaseModel | dict[str, Any], timestamp: datetime | None = None, ) -> Task | TaskStatusUpdateEvent: """ Create A2A webhook payload (Task or TaskStatusUpdateEvent). Per A2A specification: - Terminated statuses (completed, failed, canceled, rejected): Returns Task with artifacts[].parts[] - Intermediate statuses (working, input-required, submitted, auth-required): 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 — any Pydantic model or plain dict 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", ... context_id="ctx_123", ... status=GeneratedTaskStatus.completed, ... result={"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", ... context_id="ctx_456", ... status=GeneratedTaskStatus.working, ... result={"current_step": "processing", "percentage": 30}, ... ) >>> # 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 timestamp_proto = _isoformat_to_proto_timestamp(timestamp_str) if timestamp_str else None # Map GeneratedTaskStatus to A2A TaskState enum value. # GeneratedTaskStatus is always an Enum so .value is guaranteed. status_value = status.value adcp_to_task_state: dict[str, int] = { "completed": pb.TaskState.TASK_STATE_COMPLETED, "failed": pb.TaskState.TASK_STATE_FAILED, "canceled": pb.TaskState.TASK_STATE_CANCELED, "rejected": pb.TaskState.TASK_STATE_REJECTED, "working": pb.TaskState.TASK_STATE_WORKING, "submitted": pb.TaskState.TASK_STATE_SUBMITTED, # GeneratedTaskStatus enum values are hyphenated ("input-required", # "auth-required"). The underscore forms are accepted as a convenience # for callers passing raw strings rather than enum members. "input_required": pb.TaskState.TASK_STATE_INPUT_REQUIRED, "input-required": pb.TaskState.TASK_STATE_INPUT_REQUIRED, "auth_required": pb.TaskState.TASK_STATE_AUTH_REQUIRED, "auth-required": pb.TaskState.TASK_STATE_AUTH_REQUIRED, } task_state_enum = adcp_to_task_state.get(status_value) if task_state_enum is None: # Falling back to TASK_STATE_UNSPECIFIED (proto3 zero) would be # silently omitted by MessageToDict, producing an invalid wire # shape ``{"status": {}}`` that A2A v0.3 receivers reject as # missing the required ``state`` field. Fail loud at the builder # boundary so callers can't ship a broken envelope. known = [ "submitted", "working", "input-required", "completed", "canceled", "failed", "rejected", "auth-required", ] raise ValueError( f"create_a2a_webhook_payload: unknown status {status_value!r}. " f"Known AdCP→A2A states: {known}. " "Note: 'unknown' has no a2a-sdk 1.0 protobuf constant; build a " "Task manually and pass it through to_wire_dict if you need to " "emit that state." ) # Build parts for the message/artifact. parts: list[pb.Part] = [] # Convert Pydantic model to dict if needed if hasattr(result, "model_dump"): result_dict: dict[str, Any] = result.model_dump(mode="json") else: result_dict = result value = Value() ParseDict(result_dict, value) parts.append(pb.Part(data=value)) # Determine if this is a terminated status (Task) or intermediate (TaskStatusUpdateEvent). # canceled and rejected are terminal: the task will not continue. is_terminated = status in ( GeneratedTaskStatus.completed, GeneratedTaskStatus.failed, GeneratedTaskStatus.canceled, GeneratedTaskStatus.rejected, ) if is_terminated: status_kwargs: dict[str, Any] = {"state": task_state_enum} if timestamp_proto is not None: status_kwargs["timestamp"] = timestamp_proto task_status = pb.TaskStatus(**status_kwargs) artifacts = ( [ pb.Artifact( artifact_id=f"{task_id}_result", parts=parts, ) ] if parts else [] ) return pb.Task( id=task_id, status=task_status, artifacts=artifacts, context_id=context_id, ) # Intermediate status: build a Message carrying the parts and nest it # inside TaskStatus.message so the event mirrors the spec shape. message_obj = None if parts: message_obj = pb.Message( message_id=f"{task_id}_msg", role=pb.Role.ROLE_AGENT, parts=parts, ) status_kwargs = {"state": task_state_enum} if timestamp_proto is not None: status_kwargs["timestamp"] = timestamp_proto if message_obj is not None: status_kwargs["message"] = message_obj task_status = pb.TaskStatus(**status_kwargs) return pb.TaskStatusUpdateEvent( task_id=task_id, status=task_status, context_id=context_id, )Create A2A webhook payload (Task or TaskStatusUpdateEvent).
Per A2A specification: - Terminated statuses (completed, failed, canceled, rejected): Returns Task with artifacts[].parts[] - Intermediate statuses (working, input-required, submitted, auth-required): 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 — any Pydantic model or plain dict
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", ... context_id="ctx_123", ... status=GeneratedTaskStatus.completed, ... result={"products": [...]}, ... ) >>> # task is a Task object with artifacts containing the resultCreate a working status update:
>>> event = create_a2a_webhook_payload( ... task_id="task_456", ... context_id="ctx_456", ... status=GeneratedTaskStatus.working, ... result={"current_step": "processing", "percentage": 30}, ... ) >>> # event is a TaskStatusUpdateEvent with status.messageSend A2A webhook via HTTP POST:
>>> import httpx >>> from a2a.types import Task >>> >>> payload = create_a2a_webhook_payload(...) >>> # Serialize to dict for JSON >>> if isinstance(payload, Task): ... payload_dict = payload.model_dump(mode='json') ... else: ... payload_dict = payload.model_dump(mode='json') >>> >>> response = await httpx.post(webhook_url, json=payload_dict) def create_mcp_webhook_payload(task_id: str,
status: TaskStatus | str,
task_type: TaskType | str,
*,
result: PydanticBaseModel | dict[str, Any] | None = None,
timestamp: datetime | None = None,
operation_id: str | None = None,
message: str | None = None,
context_id: str | None = None,
protocol: AdcpProtocol | str | None = None,
idempotency_key: str | None = None,
token: str | None = None) ‑> adcp.types.generated_poc.core.mcp_webhook_payload.McpWebhookPayload-
Expand source code
def create_mcp_webhook_payload( task_id: str, status: GeneratedTaskStatus | str, task_type: TaskType | str, *, result: PydanticBaseModel | dict[str, Any] | None = None, timestamp: datetime | None = None, operation_id: str | None = None, message: str | None = None, context_id: str | None = None, protocol: AdcpProtocol | str | None = None, idempotency_key: str | None = None, token: str | None = None, ) -> McpWebhookPayload: """ Build an :class:`McpWebhookPayload` for a tracked async task. Pair with :func:`to_wire_dict` for HTTP transport — Pydantic-typed at construction so the publisher catches schema drift before it leaves the process. ``task_type`` is restricted to the closed :class:`TaskType` enum (the spec's complete set of async/tracked operations). Passing a value not present in the enum produces a validation error before an invalid webhook payload can leave the process. Args: task_id: Unique identifier for the task. status: Current task status. task_type: Type of AdCP async operation (see :class:`TaskType`). result: Task-specific payload — any Pydantic model or plain dict. Plain dicts are validated against :class:`AdcpAsyncResponseData`'s discriminated union. timestamp: When the webhook was generated. Defaults to current UTC. operation_id: Client-generated identifier the buyer embedded in the webhook URL when registering push-notification config. Publishers MUST echo this back so buyers correlate notifications without parsing URL paths. message: Human-readable summary of task state. context_id: Session/conversation identifier. protocol: AdCP protocol this task belongs to (see :class:`AdcpProtocol`). Auto-derived from ``task_type`` when omitted, matching the JS SDK's ``protocolForTool`` so cross-SDK bodies classify operations identically. Pass an explicit value to override. idempotency_key: Sender-generated key stable across retries of the same event. Defaults to a freshly-generated UUID v4 — callers retrying delivery of the same event MUST pass the key from their first attempt; passing None twice mints two keys and defeats dedup. token: Buyer-supplied token from ``push_notification_config.token``, echoed back per spec for authenticity validation. Returns: :class:`McpWebhookPayload` instance. Use :func:`to_wire_dict` (or ``payload.model_dump(mode="json", exclude_none=True)``) to get the JSON-ready dict for HTTP transport. Examples: Create a completed webhook with results: >>> from adcp.webhooks import create_mcp_webhook_payload, to_wire_dict >>> from adcp.types import GeneratedTaskStatus >>> >>> payload = create_mcp_webhook_payload( ... task_id="task_123", ... status=GeneratedTaskStatus.completed, ... task_type="create_media_buy", ... result={"media_buy_id": "mb_1", "buyer_ref": "ref_1"}, ... message="Created campaign" ... ) >>> wire = to_wire_dict(payload) Create a failed webhook with error: >>> payload = create_mcp_webhook_payload( ... task_id="task_456", ... status=GeneratedTaskStatus.failed, ... task_type="create_media_buy", ... result={"errors": [{"code": "INVALID_INPUT", "message": "..."}]}, ... message="Validation failed" ... ) Create a working status update: >>> payload = create_mcp_webhook_payload( ... task_id="task_789", ... status=GeneratedTaskStatus.working, ... task_type="sync_creatives", ... message="Processing 3 of 10 creatives" ... ) """ if timestamp is None: timestamp = datetime.now(timezone.utc) if idempotency_key is None: idempotency_key = generate_webhook_idempotency_key() status_value = status.value if hasattr(status, "value") else str(status) # Auto-derive `protocol` from `task_type` when caller doesn't override. # Matches `protocolForTool` in the JS reference SDK so cross-SDK bodies # classify operations identically. if protocol is None: try: task_type_enum = task_type if isinstance(task_type, TaskType) else TaskType(task_type) except ValueError: # Unknown string — let `model_validate` raise the canonical # task_type error below rather than swallow it here. task_type_enum = None if task_type_enum is not None: protocol = _TASK_TYPE_TO_PROTOCOL.get(task_type_enum) # Foreign BaseModel subclasses (anything outside AdcpAsyncResponseData) # don't match the discriminated-union variants by identity — dump to a # dict so the union picks by shape, matching the dict path. result_value: PydanticBaseModel | dict[str, Any] | None if isinstance(result, PydanticBaseModel): result_value = result.model_dump(mode="json") else: result_value = result payload = McpWebhookPayload.model_validate( { "idempotency_key": idempotency_key, "task_id": task_id, "task_type": task_type, "protocol": protocol, "status": status_value, "timestamp": timestamp, "operation_id": operation_id, "message": message, "context_id": context_id, "token": token, } ) # Preserve task result payloads byte-for-byte. Validating through the # generated AdcpAsyncResponseData union can coerce arbitrary dicts into # typed response models and inject response defaults, changing webhook # bodies before signing. payload.result = result_value # type: ignore[assignment] return payloadBuild an :class:
McpWebhookPayloadfor a tracked async task.Pair with :func:
to_wire_dict()for HTTP transport — Pydantic-typed at construction so the publisher catches schema drift before it leaves the process.task_typeis restricted to the closed :class:TaskTypeenum (the spec's complete set of async/tracked operations). Passing a value not present in the enum produces a validation error before an invalid webhook payload can leave the process.Args
task_id- Unique identifier for the task.
status- Current task status.
task_type- Type of AdCP async operation (see :class:
TaskType). result- Task-specific payload — any Pydantic model or plain dict.
Plain dicts are validated against
:class:
AdcpAsyncResponseData's discriminated union. timestamp- When the webhook was generated. Defaults to current UTC.
operation_id- Client-generated identifier the buyer embedded in the webhook URL when registering push-notification config. Publishers MUST echo this back so buyers correlate notifications without parsing URL paths.
message- Human-readable summary of task state.
context_id- Session/conversation identifier.
protocol- AdCP protocol this task belongs to (see :class:
AdcpProtocol). Auto-derived fromtask_typewhen omitted, matching the JS SDK'sprotocolForToolso cross-SDK bodies classify operations identically. Pass an explicit value to override. idempotency_key- Sender-generated key stable across retries of the same event. Defaults to a freshly-generated UUID v4 — callers retrying delivery of the same event MUST pass the key from their first attempt; passing None twice mints two keys and defeats dedup.
token- Buyer-supplied token from
push_notification_config.token, echoed back per spec for authenticity validation.
Returns
:class:
McpWebhookPayloadinstance. Use :func:to_wire_dict()(orpayload.model_dump(mode="json", exclude_none=True)) to get the JSON-ready dict for HTTP transport.Examples
Create a completed webhook with results:
>>> from adcp.webhooks import create_mcp_webhook_payload, to_wire_dict >>> from adcp.types import GeneratedTaskStatus >>> >>> payload = create_mcp_webhook_payload( ... task_id="task_123", ... status=GeneratedTaskStatus.completed, ... task_type="create_media_buy", ... result={"media_buy_id": "mb_1", "buyer_ref": "ref_1"}, ... message="Created campaign" ... ) >>> wire = to_wire_dict(payload)Create a failed webhook with error:
>>> payload = create_mcp_webhook_payload( ... task_id="task_456", ... status=GeneratedTaskStatus.failed, ... task_type="create_media_buy", ... result={"errors": [{"code": "INVALID_INPUT", "message": "..."}]}, ... message="Validation failed" ... )Create a working status update:
>>> payload = create_mcp_webhook_payload( ... task_id="task_789", ... status=GeneratedTaskStatus.working, ... task_type="sync_creatives", ... 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 create_webhook_challenge_payload(*, account_id: str, subscriber_id: str, challenge: str | None = None) ‑> dict[str, str]-
Expand source code
def create_webhook_challenge_payload( *, account_id: str, subscriber_id: str, challenge: str | None = None, ) -> dict[str, str]: """Build the durable ``notification_configs[]`` challenge payload.""" if not isinstance(account_id, str) or not account_id: raise ValueError("account_id must be a non-empty string") if not isinstance(subscriber_id, str) or not subscriber_id: raise ValueError("subscriber_id must be a non-empty string") challenge_value = generate_webhook_challenge_value() if challenge is None else challenge if not isinstance(challenge_value, str) or not challenge_value: raise ValueError("challenge must be a non-empty string") return { "type": "webhook.challenge", "challenge": challenge_value, "account_id": account_id, "subscriber_id": subscriber_id, }Build the durable
notification_configs[]challenge payload. async def detect_publisher_properties_divergence(agent_url: str,
*,
directory_url: str,
sample_size: int | None = 200,
max_concurrency: int = 20,
timeout: float = 30.0,
client: httpx.AsyncClient | None = None) ‑> list[PublisherDivergence]-
Expand source code
async def detect_publisher_properties_divergence( agent_url: str, *, directory_url: str, sample_size: int | None = 200, max_concurrency: int = 20, timeout: float = 30.0, client: httpx.AsyncClient | None = None, ) -> DivergenceReport: """Compare directory's inline resolution against per-publisher federated fetches. For each publisher the directory lists under ``agent_url``, fetches that publisher's own ``adagents.json`` and compares the property set against the directory's claim. Returns only publishers where the two paths disagree (or where the child fetch failed). Always requests ``include=["properties"]`` from the directory so the full ``(publisher_domain, property_id)`` set-diff lights up on directories that support adcp#4894. Against older directories that return only ``properties_authorized`` counts, falls back to count- comparison; ``missing_in_inline`` / ``missing_in_federated`` are None in that fallback path. Per adcp#4827 §Resolution-paths, the federated result is authoritative when the two paths disagree. Args: agent_url: agent to check. directory_url: AAO directory base URL (HTTPS only — same SSRF gate as :func:`fetch_agent_authorizations_from_directory`). sample_size: cap the sweep at N publishers (drawn from the first page of directory results). None opts into a full sweep across all pages — only do this for small networks. Default 200 keeps the divergence sweep bounded by default. max_concurrency: semaphore-capped concurrent federated fetches. Default 20 — caps the burst against publisher origins. timeout: per-request timeout (directory + child fetches). client: optional shared ``httpx.AsyncClient``. Returns: :data:`DivergenceReport` (``list[PublisherDivergence]``). Empty list = no divergence detected. Note in count-only fallback mode, an empty list means counts agree but set-equality is not guaranteed. """ own_client = client is None http = client or httpx.AsyncClient() try: collected: list[DirectoryPublisherEntry] = [] cursor: str | None = None seen_cursors: set[str] = set() page_count = 0 while True: page = await fetch_agent_authorizations_from_directory( agent_url, directory_url=directory_url, cursor=cursor, include=["properties"], timeout=timeout, client=http, ) page_count += 1 collected.extend(page.publishers) if sample_size is not None and len(collected) >= sample_size: collected = collected[:sample_size] break cursor = page.next_cursor if not cursor: break if cursor in seen_cursors: raise AdagentsValidationError( f"Directory page cursor {cursor!r} repeated — refusing to loop forever." ) seen_cursors.add(cursor) if page_count >= MAX_DIRECTORY_PAGES: raise AdagentsValidationError( f"Directory pagination exceeded {MAX_DIRECTORY_PAGES} pages — aborting sweep." ) # Dedupe by publisher_domain before fan-out: a hostile directory # returning N rows for the same publisher would otherwise amplify # into N concurrent fetches against a single victim host. First # occurrence wins (deterministic) — conflicting property_ids / # properties_authorized across duplicates are dropped here; the # directory's behavior is itself a divergence signal for ops. seen_domains: set[str] = set() deduped: list[DirectoryPublisherEntry] = [] for entry in collected: if entry.publisher_domain in seen_domains: continue seen_domains.add(entry.publisher_domain) deduped.append(entry) collected = deduped # Emit a one-shot warning when the entire sample comes back without # property_ids[]. In count-only mode, same-count substitutions are # undetectable — adopters should pin include=["properties"] support # on directories that offer it. if collected and all(e.property_ids is None for e in collected): logger.warning( "AAO directory %s did not return property_ids[] on any publisher " "entry — falling back to count-only divergence detection. Same-count " "substitutions are undetectable in this mode. Upgrade the directory " "or pin include=['properties'] support.", directory_url, ) sem = asyncio.Semaphore(max_concurrency) async def _probe(entry: DirectoryPublisherEntry) -> PublisherDivergence | None: async with sem: try: data = await fetch_adagents( entry.publisher_domain, timeout=timeout, client=http ) federated_props = get_properties_by_agent(data, agent_url) # Falsy/empty property_id is silently dropped: upstream # schema requires a non-empty string, so an empty value # is a structural violation that belongs in # validate_adagents, not a divergence signal. Federated # properties with valid IDs only. federated_ids = { str(p.get("property_id")) for p in federated_props if p.get("property_id") } except ( AdagentsNotFoundError, AdagentsValidationError, AdagentsTimeoutError, httpx.HTTPError, OSError, ValueError, ) as exc: return PublisherDivergence( publisher_domain=entry.publisher_domain, directory_properties_authorized=entry.properties_authorized, federated_properties_found=0, missing_in_inline=None, missing_in_federated=None, child_fetch_error=str(exc), ) if entry.property_ids is not None: # Full set-diff path (adcp#4894). dir_ids = set(entry.property_ids) missing_in_inline = sorted(federated_ids - dir_ids) missing_in_federated = sorted(dir_ids - federated_ids) if not missing_in_inline and not missing_in_federated: return None return PublisherDivergence( publisher_domain=entry.publisher_domain, directory_properties_authorized=entry.properties_authorized, federated_properties_found=len(federated_ids), missing_in_inline=missing_in_inline, missing_in_federated=missing_in_federated, ) # Count-only fallback (older directories). if len(federated_ids) == entry.properties_authorized: return None return PublisherDivergence( publisher_domain=entry.publisher_domain, directory_properties_authorized=entry.properties_authorized, federated_properties_found=len(federated_ids), missing_in_inline=None, missing_in_federated=None, ) probes = await asyncio.gather(*[_probe(e) for e in collected]) finally: if own_client: await http.aclose() return [p for p in probes if p is not None]Compare directory's inline resolution against per-publisher federated fetches.
For each publisher the directory lists under
agent_url, fetches that publisher's ownadagents.jsonand compares the property set against the directory's claim. Returns only publishers where the two paths disagree (or where the child fetch failed).Always requests
include=["properties"]from the directory so the full(publisher_domain, property_id)set-diff lights up on directories that support adcp#4894. Against older directories that return onlyproperties_authorizedcounts, falls back to count- comparison;missing_in_inline/missing_in_federatedare None in that fallback path.Per adcp#4827 §Resolution-paths, the federated result is authoritative when the two paths disagree.
Args
agent_url- agent to check.
directory_url- AAO directory base URL (HTTPS only — same SSRF
gate as :func:
fetch_agent_authorizations_from_directory()). sample_size- cap the sweep at N publishers (drawn from the first page of directory results). None opts into a full sweep across all pages — only do this for small networks. Default 200 keeps the divergence sweep bounded by default.
max_concurrency- semaphore-capped concurrent federated fetches. Default 20 — caps the burst against publisher origins.
timeout- per-request timeout (directory + child fetches).
client- optional shared
httpx.AsyncClient.
Returns
:data:
DivergenceReport(list[PublisherDivergence]). Empty list = no divergence detected. Note in count-only fallback mode, an empty list means counts agree but set-equality is not guaranteed. 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 FalseCheck 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]) ‑> dict[str, typing.Any] | None-
Expand source code
def extract_webhook_result_data(webhook_payload: dict[str, Any]) -> dict[str, Any] | 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, canceled, rejected) - 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: dict[str, Any] containing the extracted AdCP response data, or None if no result is 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(dict[str, Any], data["response"]) return cast(dict[str, Any], 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(dict[str, Any], data["response"]) return cast(dict[str, Any], data) return None # MCP format: result field directly return cast(dict[str, Any] | 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, canceled, rejected) - 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
dict[str, Any] containing the extracted AdCP response data, or None if no result is 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_123Extract 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_456Extract 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"]) 50Handle 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. Discovery order: 1. ``https://{publisher}/.well-known/adagents.json`` (direct). 2. ``authoritative_location`` redirect, if the direct response is a URL reference. 3. RFC 4175 ads.txt MANAGERDOMAIN fallback, on direct 404 only: fetches ``https://{publisher}/ads.txt`` for a ``MANAGERDOMAIN=`` directive and, if present, tries ``https://{manager}/.well-known/adagents.json``. The fallback is one-hop only. If the manager domain also 404s, this raises :class:`AdagentsNotFoundError` for the original publisher — not a silent pass. Args: publisher_domain: Domain hosting the adagents.json file. timeout: Request timeout in seconds. user_agent: User-Agent header for HTTP request. client: Optional httpx.AsyncClient for connection pooling. If provided, caller is responsible for client lifecycle. If None, a new client is created for this request. Returns: Parsed adagents.json data (resolved via authoritative_location or ads.txt MANAGERDOMAIN if applicable). Raises: AdagentsNotFoundError: If adagents.json was not found via any discovery path. AdagentsAccessBlockedError: If the publisher's CDN returns HTTP 403 with ``cf-mitigated: challenge`` (Cloudflare bot-management block). Subclass of ``AdagentsValidationError``. AdagentsValidationError: If JSON is invalid, malformed, or redirects exceed maximum depth or form a loop. AdagentsTimeoutError: If request times out. Notes: For production use with multiple requests, pass a shared httpx.AsyncClient to enable connection pooling. Callers who need to know which discovery path produced the data (direct, authoritative_location, or ads_txt_managerdomain) should call :func:`validate_adagents_domain` instead. ``fetch_adagents`` performs only minimal structural checks. To report per-entry schema violations (e.g., bare entries missing ``authorization_type``) without raising, pass the returned data to :func:`validate_adagents_structure`. """ publisher_domain = _validate_publisher_domain(publisher_domain) try: data, *_ = await _resolve_direct(publisher_domain, timeout, user_agent, client) return data except AdagentsNotFoundError: manager_data = await _try_managerdomain_fallback( publisher_domain, timeout, user_agent, client ) if manager_data is not None: return manager_data raiseFetch and parse adagents.json from publisher domain.
Discovery order:
https://{publisher}/.well-known/adagents.json(direct).authoritative_locationredirect, if the direct response is a URL reference.- RFC 4175 ads.txt MANAGERDOMAIN fallback, on direct 404 only:
fetches
https://{publisher}/ads.txtfor aMANAGERDOMAIN=directive and, if present, trieshttps://{manager}/.well-known/adagents.json.
The fallback is one-hop only. If the manager domain also 404s, this raises :class:
AdagentsNotFoundErrorfor the original publisher — not a silent pass.Args
publisher_domain- Domain hosting the adagents.json file.
timeout- Request timeout in seconds.
user_agent- User-Agent header for HTTP request.
client- Optional httpx.AsyncClient for connection pooling. If provided, caller is responsible for client lifecycle. If None, a new client is created for this request.
Returns
Parsed adagents.json data (resolved via authoritative_location or ads.txt MANAGERDOMAIN if applicable).
Raises
AdagentsNotFoundError- If adagents.json was not found via any discovery path.
AdagentsAccessBlockedError- If the publisher's CDN returns HTTP
403 with
cf-mitigated: challenge(Cloudflare bot-management block). Subclass ofAdagentsValidationError. AdagentsValidationError- If JSON is invalid, malformed, or redirects exceed maximum depth or form a loop.
AdagentsTimeoutError- If request times out.
Notes
For production use with multiple requests, pass a shared httpx.AsyncClient to enable connection pooling.
Callers who need to know which discovery path produced the data (direct, authoritative_location, or ads_txt_managerdomain) should call :func:
validate_adagents_domain()instead.fetch_adagents()performs only minimal structural checks. To report per-entry schema violations (e.g., bare entries missingauthorization_type) without raising, pass the returned data to :func:validate_adagents_structure(). async def fetch_adagents_with_cache(publisher_domain: str,
cache_entry: AdagentsCacheEntry | None = None,
timeout: float = 10.0,
user_agent: str = 'AdCP-Client/1.0',
client: httpx.AsyncClient | None = None) ‑> AdagentsFetchResult-
Expand source code
async def fetch_adagents_with_cache( publisher_domain: str, cache_entry: AdagentsCacheEntry | None = None, timeout: float = 10.0, user_agent: str = "AdCP-Client/1.0", client: httpx.AsyncClient | None = None, ) -> AdagentsFetchResult: """Fetch with conditional refresh — returns body plus refreshed validators. Pass the previous fetch's :class:`AdagentsCacheEntry` to send ``If-None-Match`` / ``If-Modified-Since`` on the next fetch. A 304 from the publisher is treated as a successful refresh: the cached ``body`` is returned with ``not_modified=True``, satisfying the 7-day cache window described in adcp#4504. The first hop (``/.well-known/adagents.json``) is capped at 5 MiB; a dereferenced ``authoritative_location`` file is capped at 20 MiB. Both caps fail closed — oversized responses raise :class:`AdagentsValidationError` rather than truncate. Does NOT perform the ads.txt ``managerdomain`` fallback; the fallback is best-effort discovery, not cache-aware refresh, and bypassing it on 304 keeps the path simple. Callers that need both behaviors should compose this helper with :func:`validate_adagents_domain`. """ publisher_domain = _validate_publisher_domain(publisher_domain) data, discovery, etag, last_modified, not_modified = await _resolve_direct( publisher_domain, timeout, user_agent, client, cache_entry=cache_entry ) return AdagentsFetchResult( data=data, discovery_method=discovery, etag=etag, last_modified=last_modified, not_modified=not_modified, )Fetch with conditional refresh — returns body plus refreshed validators.
Pass the previous fetch's :class:
AdagentsCacheEntryto sendIf-None-Match/If-Modified-Sinceon the next fetch. A 304 from the publisher is treated as a successful refresh: the cachedbodyis returned withnot_modified=True, satisfying the 7-day cache window described in adcp#4504.The first hop (
/.well-known/adagents.json) is capped at 5 MiB; a dereferencedauthoritative_locationfile is capped at 20 MiB. Both caps fail closed — oversized responses raise :class:AdagentsValidationErrorrather than truncate.Does NOT perform the ads.txt
managerdomainfallback; the fallback is best-effort discovery, not cache-aware refresh, and bypassing it on 304 keeps the path simple. Callers that need both behaviors should compose this helper with :func:validate_adagents_domain(). -
Expand source code
async def fetch_agent_authorizations( agent_url: str, publisher_domains: list[str], timeout: float = 10.0, client: httpx.AsyncClient | None = None, ) -> dict[str, AuthorizationContext]: """Fetch authorization contexts by checking publisher adagents.json files. This function discovers what publishers have authorized your agent by fetching their adagents.json files from the .well-known directory and extracting the properties your agent can access. This is the "pull" approach - you query publishers to see if they've authorized you. Args: agent_url: URL of your sales agent publisher_domains: List of publisher domains to check (e.g., ["nytimes.com", "wsj.com"]) timeout: Request timeout in seconds for each fetch client: Optional httpx.AsyncClient for connection pooling Returns: Dictionary mapping publisher domain to AuthorizationContext. Only includes domains where the agent is authorized. Example: >>> # "Pull" approach - check what publishers have authorized you >>> contexts = await fetch_agent_authorizations( ... "https://our-sales-agent.com", ... ["nytimes.com", "wsj.com", "cnn.com"] ... ) >>> for domain, ctx in contexts.items(): ... print(f"{domain}:") ... print(f" Property IDs: {ctx.property_ids}") ... print(f" Tags: {ctx.property_tags}") Notes: - Silently skips domains where adagents.json is not found or invalid - Only returns domains where the agent is explicitly authorized - For production use with many domains, pass a shared httpx.AsyncClient to enable connection pooling """ import asyncio # Create tasks to fetch all adagents.json files in parallel async def fetch_authorization_for_domain( domain: str, ) -> tuple[str, AuthorizationContext | None]: """Fetch authorization context for a single domain.""" try: adagents_data = await fetch_adagents(domain, timeout=timeout, client=client) # Check if agent is authorized if not verify_agent_authorization(adagents_data, agent_url): return (domain, None) # Get properties for this agent properties = get_properties_by_agent(adagents_data, agent_url) # Create authorization context return (domain, AuthorizationContext(properties)) except (AdagentsNotFoundError, AdagentsValidationError, AdagentsTimeoutError): # Silently skip domains with missing or invalid adagents.json. # AdagentsAccessBlockedError (AdagentsValidationError subclass) is # intentionally swallowed: a bot-blocked domain is treated as # authorization-unavailable, same as a missing file. return (domain, None) # Fetch all domains in parallel tasks = [fetch_authorization_for_domain(domain) for domain in publisher_domains] results = await asyncio.gather(*tasks) # Build result dictionary, filtering out None values return {domain: ctx for domain, ctx in results if ctx is not None}Fetch authorization contexts by checking publisher adagents.json files.
This function discovers what publishers have authorized your agent by fetching their adagents.json files from the .well-known directory and extracting the properties your agent can access.
This is the "pull" approach - you query publishers to see if they've authorized you.
Args
agent_url- URL of your sales agent
publisher_domains- List of publisher domains to check (e.g., ["nytimes.com", "wsj.com"])
timeout- Request timeout in seconds for each fetch
client- Optional httpx.AsyncClient for connection pooling
Returns
Dictionary mapping publisher domain to AuthorizationContext. Only includes domains where the agent is authorized.
Example
>>> # "Pull" approach - check what publishers have authorized you >>> contexts = await fetch_agent_authorizations( ... "https://our-sales-agent.com", ... ["nytimes.com", "wsj.com", "cnn.com"] ... ) >>> for domain, ctx in contexts.items(): ... print(f"{domain}:") ... print(f" Property IDs: {ctx.property_ids}") ... print(f" Tags: {ctx.property_tags}")Notes
- Silently skips domains where adagents.json is not found or invalid
- Only returns domains where the agent is explicitly authorized
- For production use with many domains, pass a shared httpx.AsyncClient to enable connection pooling
-
Expand source code
async def fetch_agent_authorizations_from_directory( agent_url: str, *, directory_url: str, since: str | None = None, cursor: str | None = None, include: list[str] | None = None, timeout: float = 10.0, client: httpx.AsyncClient | None = None, ) -> AgentAuthorizationsDirectoryResult: """Query an AAO directory for publishers that authorize ``agent_url``. Calls ``GET {directory_url}/v1/agents/{agent_url}/publishers`` per the AAO inverse-lookup contract (adcp#4823 / #4828) and returns the parsed response. The directory's answer is *discovery*, not authorization: callers should still verify each returned ``publisher_domain`` via :func:`fetch_adagents` before treating an edge as trusted. Args: agent_url: The agent whose publisher authorizations are being queried. Passed verbatim in the path; the directory echoes back a canonicalized form on the response. directory_url: HTTPS base URL of the AAO directory (e.g. ``"https://aao.example.com"``). The ``/v1/agents/...`` path is appended; pass the directory's root, not a request-specific path. since: Optional RFC 3339 timestamp from a prior ``directory_indexed_at`` — passed through as ``?since=...`` to limit the result to edges that changed since that point. cursor: Optional opaque pagination cursor from a prior response's ``next_cursor`` — passed through as ``?cursor=...`` to fetch the next page. include: Optional list of expansion keys per the AAO directory API spec (adcp#4894). Each value is emitted as a separate ``?include=<value>`` query parameter (repeated-key form, not comma-joined). Pass ``["properties"]`` against directories that support it to receive per-publisher ``property_ids[]`` on each row, enabling full set-diff against the publisher's own adagents.json. Directories that don't support a given expansion key simply omit the corresponding fields from the response; callers should treat absence as count-only mode. timeout: Request timeout in seconds. client: Optional shared ``httpx.AsyncClient`` for connection pooling. Caller owns the client lifecycle. Returns: :class:`AgentAuthorizationsDirectoryResult`. On 404 from the directory the function returns a result with ``publishers=[]`` and ``directory_indexed_at=None`` — directories MUST be allowed to answer "I do not index this agent" without callers needing to branch on exception type. Raises: AdagentsValidationError: If ``directory_url`` is malformed, the response status is non-200/non-404, the body is not valid JSON, or the body does not match the directory result schema. AdagentsTimeoutError: If the request times out. Notes: - ``directory_url`` is gated through the same SSRF protection (HTTPS only, DNS pre-check, private/reserved address ban) as publisher-side fetches. - Response bodies are capped at 5 MiB. Bulk responses paginate via ``next_cursor``; pass that value as ``cursor`` on the next call. """ if not isinstance(agent_url, str) or not agent_url: raise AdagentsValidationError("agent_url must be a non-empty string") if not isinstance(directory_url, str) or not directory_url: raise AdagentsValidationError("directory_url must be a non-empty string") base = directory_url.rstrip("/") if not base.startswith("https://"): raise AdagentsValidationError(f"directory_url must be an HTTPS URL, got: {directory_url!r}") _validate_redirect_url(f"{base}/v1/agents/_/publishers") request_url = f"{base}/v1/agents/{quote(agent_url, safe='')}/publishers" query_pairs: list[tuple[str, str]] = [] if since is not None: query_pairs.append(("since", since)) if cursor is not None: query_pairs.append(("cursor", cursor)) if include: # Repeated-key form per docs/aao/directory-api.mdx (style: form, # explode: true). Comma-joined NOT accepted by spec-conformant # directories. for value in include: query_pairs.append(("include", value)) if query_pairs: query_string = "&".join(f"{quote(k, safe='')}={quote(v, safe='')}" for k, v in query_pairs) request_url = f"{request_url}?{query_string}" parsed = urlparse(request_url) await _dns_validate_host( parsed.hostname or "", parsed.port or (443 if parsed.scheme == "https" else 80) ) headers = {"User-Agent": "AdCP-Client/1.0", "Accept": "application/json"} # SDK-owned client is pinned to the validated IP (see _fetch_adagents_url). # A failed resolve/SSRF check raises AdagentsValidationError, which # propagates past the httpx handlers below — the correct fail-closed # outcome (we do not convert it into an empty result). try: if client is not None: body, status_code, _ = await _stream_capped( client, request_url, headers, timeout, MAX_DIRECTORY_PAGE_BYTES ) else: async with _owned_pinned_client(request_url, timeout) as new_client: body, status_code, _ = await _stream_capped( new_client, request_url, headers, timeout, MAX_DIRECTORY_PAGE_BYTES ) except httpx.TimeoutException as e: raise AdagentsTimeoutError(parsed.netloc, timeout) from e except httpx.RequestError as e: raise AdagentsValidationError(f"Failed to fetch agent-publishers directory: {e}") from e if status_code == 404: # Per adcp#4828, a directory that has not indexed this agent # answers 404. Surface as an empty result so callers don't need # to special-case the exception path for "no edges" — the # protocol is intentionally permissive here. return AgentAuthorizationsDirectoryResult( agent_url=agent_url, directory_indexed_at=None, publishers=[], next_cursor=None, ) if status_code != 200: raise AdagentsValidationError(f"Agent-publishers directory returned HTTP {status_code}") try: data = json.loads(body) except json.JSONDecodeError as e: raise AdagentsValidationError( f"Invalid JSON in agent-publishers directory response: {str(e)[:200]}" ) from e try: return AgentAuthorizationsDirectoryResult.model_validate(data) except Exception as e: # pydantic.ValidationError + any coercion failure raise AdagentsValidationError( f"Agent-publishers directory response failed schema validation: {e}" ) from eQuery an AAO directory for publishers that authorize
agent_url.Calls
GET {directory_url}/v1/agents/{agent_url}/publishersper the AAO inverse-lookup contract (adcp#4823 / #4828) and returns the parsed response. The directory's answer is discovery, not authorization: callers should still verify each returnedpublisher_domainvia :func:fetch_adagents()before treating an edge as trusted.Args
agent_url- The agent whose publisher authorizations are being queried. Passed verbatim in the path; the directory echoes back a canonicalized form on the response.
directory_url- HTTPS base URL of the AAO directory
(e.g.
"https://aao.example.com"). The/v1/agents/...path is appended; pass the directory's root, not a request-specific path. since- Optional RFC 3339 timestamp from a prior
directory_indexed_at— passed through as?since=...to limit the result to edges that changed since that point. cursor- Optional opaque pagination cursor from a prior response's
next_cursor— passed through as?cursor=...to fetch the next page. include- Optional list of expansion keys per the AAO directory
API spec (adcp#4894). Each value is emitted as a separate
?include=<value>query parameter (repeated-key form, not comma-joined). Pass["properties"]against directories that support it to receive per-publisherproperty_ids[]on each row, enabling full set-diff against the publisher's own adagents.json. Directories that don't support a given expansion key simply omit the corresponding fields from the response; callers should treat absence as count-only mode. timeout- Request timeout in seconds.
client- Optional shared
httpx.AsyncClientfor connection pooling. Caller owns the client lifecycle.
Returns
:class:
AgentAuthorizationsDirectoryResult. On 404 from the directory the function returns a result withpublishers=[]anddirectory_indexed_at=None— directories MUST be allowed to answer "I do not index this agent" without callers needing to branch on exception type.Raises
AdagentsValidationError- If
directory_urlis malformed, the response status is non-200/non-404, the body is not valid JSON, or the body does not match the directory result schema. AdagentsTimeoutError- If the request times out.
Notes
directory_urlis gated through the same SSRF protection (HTTPS only, DNS pre-check, private/reserved address ban) as publisher-side fetches.- Response bodies are capped at 5 MiB. Bulk responses paginate
via
next_cursor; pass that value ascursoron the next call.
def filter_revoked_selectors(selectors: list[dict[str, Any]], revoked_domains: set[str]) ‑> list[dict[str, typing.Any]]-
Expand source code
def filter_revoked_selectors( selectors: list[dict[str, Any]], revoked_domains: set[str], ) -> list[dict[str, Any]]: """Strip selectors whose ``publisher_domain`` is revoked. Apply this AFTER the compact-form fan-out so each remaining selector addresses exactly one publisher, then drop any whose domain is in ``revoked_domains``. Revocation takes precedence over every other listing of that domain in the file (selectors, top-level properties, etc.) per adcp#4504. """ if not revoked_domains: return selectors return [s for s in selectors if s.get("publisher_domain") not in revoked_domains]Strip selectors whose
publisher_domainis revoked.Apply this AFTER the compact-form fan-out so each remaining selector addresses exactly one publisher, then drop any whose domain is in
revoked_domains. Revocation takes precedence over every other listing of that domain in the file (selectors, top-level properties, etc.) per adcp#4504. def format_is_supported(requested: str | FormatReferenceStructuredObject | Mapping[str, Any],
supported: str | FormatReferenceStructuredObject | Mapping[str, Any],
*,
default_agent_url: str = 'https://creative.adcontextprotocol.org') ‑> bool-
Expand source code
def format_is_supported( requested: str | FormatId | Mapping[str, Any], supported: str | FormatId | Mapping[str, Any], *, default_agent_url: str = CANONICAL_CREATIVE_AGENT_URL, ) -> bool: """Return true when ``requested`` is acceptable for ``supported``. This is intentionally stricter than :func:`formats_are_equivalent`. A broad supported format such as ``display_image`` accepts a specific request such as ``display_image`` 300x250, but a fixed supported product format requires the request to provide and match every fixed parameter (``width``, ``height``, and ``duration_ms``). """ req = upgrade_legacy_format_id(requested, default_agent_url=default_agent_url) sup = upgrade_legacy_format_id(supported, default_agent_url=default_agent_url) if not formats_are_equivalent(req, sup, default_agent_url=default_agent_url): return False for field in ("width", "height", "duration_ms"): supported_value = getattr(sup, field) if supported_value is None: continue if getattr(req, field) != supported_value: return False return TrueReturn true when
requestedis acceptable forsupported.This is intentionally stricter than :func:
formats_are_equivalent(). A broad supported format such asdisplay_imageaccepts a specific request such asdisplay_image300x250, but a fixed supported product format requires the request to provide and match every fixed parameter (width,height, andduration_ms). def formats_are_equivalent(a: str | FormatReferenceStructuredObject | Mapping[str, Any],
b: str | FormatReferenceStructuredObject | Mapping[str, Any],
*,
default_agent_url: str = 'https://creative.adcontextprotocol.org') ‑> bool-
Expand source code
def formats_are_equivalent( a: str | FormatId | Mapping[str, Any], b: str | FormatId | Mapping[str, Any], *, default_agent_url: str = CANONICAL_CREATIVE_AGENT_URL, ) -> bool: """Return true when two format IDs identify the same canonical family. Both inputs are first passed through :func:`upgrade_legacy_format_id`. Declared parameters must not conflict, but an omitted parameter on either side is treated as unspecified rather than a mismatch. Use :func:`format_is_supported` for product/capability gating where a supported fixed size or duration requires the request to state that value. """ left = upgrade_legacy_format_id(a, default_agent_url=default_agent_url) right = upgrade_legacy_format_id(b, default_agent_url=default_agent_url) if canonicalize_agent_url(left.agent_url) != canonicalize_agent_url(right.agent_url): return False if left.id != right.id: return False for field in ("width", "height", "duration_ms"): left_value = getattr(left, field) right_value = getattr(right, field) if left_value is not None and right_value is not None and left_value != right_value: return False return TrueReturn true when two format IDs identify the same canonical family.
Both inputs are first passed through :func:
upgrade_legacy_format_id(). Declared parameters must not conflict, but an omitted parameter on either side is treated as unspecified rather than a mismatch. Use :func:format_is_supported()for product/capability gating where a supported fixed size or duration requires the request to state that value. def generate_webhook_challenge_value() ‑> str-
Expand source code
def generate_webhook_challenge_value() -> str: """Generate an opaque random value for a proof-of-control challenge.""" return f"wch_{secrets.token_urlsafe(32)}"Generate an opaque random value for a proof-of-control challenge.
def generate_webhook_idempotency_key() ‑> str-
Expand source code
def generate_webhook_idempotency_key() -> str: """Generate a cryptographically random idempotency_key for a webhook event. Returns a UUID v4 prefixed with ``whk_`` — matches the example format in ``webhooks.mdx`` and stays within the spec's length + charset bounds (``^[A-Za-z0-9_.:-]{16,255}$``). Publishers MUST generate this once per distinct event and reuse the same value when retrying delivery. Do NOT call this function again on retry — it would mint a fresh UUID and defeat the dedup contract. """ return f"whk_{uuid.uuid4()}"Generate a cryptographically random idempotency_key for a webhook event.
Returns a UUID v4 prefixed with
whk_— matches the example format inwebhooks.mdxand stays within the spec's length + charset bounds (^[A-Za-z0-9_.:-]{16,255}$).Publishers MUST generate this once per distinct event and reuse the same value when retrying delivery. Do NOT call this function again on retry — it would mint a fresh UUID and defeat the dedup contract.
def get_adcp_signed_headers_for_webhook(headers: dict[str, Any],
secret: str,
timestamp: str | int | None,
payload: dict[str, Any] | AdCPBaseModel) ‑> dict[str, typing.Any]-
Expand source code
def get_adcp_signed_headers_for_webhook( headers: dict[str, Any], secret: str, timestamp: str | int | None, payload: dict[str, Any] | AdCPBaseModel, ) -> dict[str, Any]: """ Generate AdCP-compliant signed headers for webhook delivery. This function creates a cryptographic signature that proves the webhook came from an authorized agent and protects against replay attacks by including a timestamp in the signed message. The function adds two headers to the provided headers dict: - X-AdCP-Signature: HMAC-SHA256 signature in format "sha256=<hex_digest>" - X-AdCP-Timestamp: Unix timestamp in seconds The signing algorithm: 1. Constructs message as "{timestamp}.{json_payload}" 2. JSON-serializes payload with default separators (matches wire format from json= kwarg) 3. UTF-8 encodes the message 4. HMAC-SHA256 signs with the shared secret 5. Hex-encodes and prefixes with "sha256=" Args: headers: Existing headers dictionary to add signature headers to secret: Shared secret key for HMAC signing timestamp: Unix timestamp in seconds (str or int). If None, uses current time. payload: Webhook payload (dict or Pydantic model - will be JSON-serialized) Returns: The modified headers dictionary with signature headers added Examples: Sign and send an MCP webhook: >>> import time >>> from adcp.webhooks import create_mcp_webhook_payload >>> from adcp.webhooks import get_adcp_signed_headers_for_webhook >>> >>> payload = create_mcp_webhook_payload( ... task_id="task_123", ... status="completed", ... task_type="create_media_buy", ... result={"media_buy_id": "mb_1"}, ... ) >>> headers = {"Content-Type": "application/json"} >>> signed_headers = get_adcp_signed_headers_for_webhook( ... headers, secret="my-webhook-secret", timestamp=str(int(time.time())), ... payload=payload, ... ) >>> >>> # Send webhook with signed headers >>> import httpx >>> response = await httpx.post( ... webhook_url, ... json=payload, ... headers=signed_headers ... ) Headers will contain: >>> print(signed_headers) { "Content-Type": "application/json", "X-AdCP-Signature": "sha256=a1b2c3...", "X-AdCP-Timestamp": "1773185740" } """ signature_headers, _body_bytes = _compute_legacy_signature( secret=secret, timestamp=timestamp, payload=payload ) headers.update(signature_headers) return headersGenerate AdCP-compliant signed headers for webhook delivery.
This function creates a cryptographic signature that proves the webhook came from an authorized agent and protects against replay attacks by including a timestamp in the signed message.
The function adds two headers to the provided headers dict: - X-AdCP-Signature: HMAC-SHA256 signature in format "sha256=
" - X-AdCP-Timestamp: Unix timestamp in seconds The signing algorithm: 1. Constructs message as "{timestamp}.{json_payload}" 2. JSON-serializes payload with default separators (matches wire format from json= kwarg) 3. UTF-8 encodes the message 4. HMAC-SHA256 signs with the shared secret 5. Hex-encodes and prefixes with "sha256="
Args
headers- Existing headers dictionary to add signature headers to
secret- Shared secret key for HMAC signing
timestamp- Unix timestamp in seconds (str or int). If None, uses current time.
payload- Webhook payload (dict or Pydantic model - will be JSON-serialized)
Returns
The modified headers dictionary with signature headers added
Examples
Sign and send an MCP webhook:
>>> import time >>> from adcp.webhooks import create_mcp_webhook_payload >>> from adcp.webhooks import get_adcp_signed_headers_for_webhook >>> >>> payload = create_mcp_webhook_payload( ... task_id="task_123", ... status="completed", ... task_type="create_media_buy", ... result={"media_buy_id": "mb_1"}, ... ) >>> headers = {"Content-Type": "application/json"} >>> signed_headers = get_adcp_signed_headers_for_webhook( ... headers, secret="my-webhook-secret", timestamp=str(int(time.time())), ... payload=payload, ... ) >>> >>> # Send webhook with signed headers >>> import httpx >>> response = await httpx.post( ... webhook_url, ... json=payload, ... headers=signed_headers ... )Headers will contain:
>>> print(signed_headers) { "Content-Type": "application/json", "X-AdCP-Signature": "sha256=a1b2c3...", "X-AdCP-Timestamp": "1773185740" } def get_adcp_version() ‑> str-
Expand source code
def get_adcp_version() -> str: """Return the AdCP *spec* version (legacy name). .. deprecated:: 4.1 Kept for backwards compatibility with pre-4.1 callers. Prefer :func:`get_adcp_spec_version` (spec version) or :func:`get_adcp_sdk_version` / :attr:`adcp.__version__` (SDK package version) — the split disambiguates what the caller actually wants at the call site. """ import warnings warnings.warn( "get_adcp_version() is deprecated; use get_adcp_spec_version() " "for the AdCP spec version or get_adcp_sdk_version() / " "adcp.__version__ for the SDK package version.", DeprecationWarning, stacklevel=2, ) return get_adcp_spec_version()Return the AdCP spec version (legacy name).
Deprecated since version: 4.1
Kept for backwards compatibility with pre-4.1 callers. Prefer :func:
get_adcp_spec_version(spec version) or :func:get_adcp_sdk_version/ :attr:adcp.__version__(SDK package version) — the split disambiguates what the caller actually wants at the call site. def get_all_properties(adagents_data: dict[str, Any]) ‑> list[dict[str, typing.Any]]-
Expand source code
def get_all_properties(adagents_data: dict[str, Any]) -> list[dict[str, Any]]: """Extract all properties from adagents.json data. Handles all authorization types: inline_properties, property_ids, property_tags, and publisher_properties. For ``publisher_properties`` selectors whose target ``publisher_domain`` is NOT present inline in this file's top-level ``properties[]`` array, this function returns no properties for that selector. Federated fallback (fetching the child publisher's own adagents.json to resolve the selector remotely) is out of scope here and lives in :func:`fetch_agent_authorizations_from_directory` and :func:`detect_publisher_properties_divergence` from companion PR #752. Wire-only authorization checks that assume federated resolution will under-authorize against managed-network parent files that only inline a subset of their child domains. Args: adagents_data: Parsed adagents.json data Returns: List of all properties across all authorized agents, with agent_url added Raises: AdagentsValidationError: If adagents_data is malformed """ if not isinstance(adagents_data, dict): raise AdagentsValidationError("adagents_data must be a dictionary") authorized_agents = adagents_data.get("authorized_agents") if not isinstance(authorized_agents, list): raise AdagentsValidationError("adagents.json must have 'authorized_agents' array") top_level_properties = adagents_data.get("properties", []) if not isinstance(top_level_properties, list): top_level_properties = [] revoked = _get_revoked_publisher_domains(adagents_data) revoked_top_level = [ p for p in top_level_properties if not ( isinstance(p, dict) and isinstance(p.get("publisher_domain"), str) and p["publisher_domain"] in revoked ) ] # Build the domain index once per file — _resolve_agent_properties is # called per-agent, and at cafemedia scale (thousands of properties × # multiple agents) rebuilding it inside each call is O(agents × N). domain_index = _build_domain_index(revoked_top_level) properties = [] for agent in authorized_agents: if not isinstance(agent, dict): continue agent_url = agent.get("url", "") if not agent_url: continue # revoked_top_level pre-filters revoked domains from the per-domain # index, so inline resolution honors revocation transparently. agent_properties = _resolve_agent_properties(agent, revoked_top_level, domain_index) for prop in agent_properties: prop_with_agent = {**prop, "agent_url": agent_url} properties.append(prop_with_agent) return propertiesExtract all properties from adagents.json data.
Handles all authorization types: inline_properties, property_ids, property_tags, and publisher_properties.
For
publisher_propertiesselectors whose targetpublisher_domainis NOT present inline in this file's top-levelproperties[]array, this function returns no properties for that selector. Federated fallback (fetching the child publisher's own adagents.json to resolve the selector remotely) is out of scope here and lives in :func:fetch_agent_authorizations_from_directory()and :func:detect_publisher_properties_divergence()from companion PR #752. Wire-only authorization checks that assume federated resolution will under-authorize against managed-network parent files that only inline a subset of their child domains.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
-
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 tagsExtract all unique tags from properties in adagents.json data.
Args
adagents_data- Parsed adagents.json data
Returns
Set of all unique tags across all properties
Raises
AdagentsValidationError- If adagents_data is malformed
def get_asset_count(format: Format) ‑> int-
Expand source code
def get_asset_count(format: Format) -> int: """Get the count of assets in a format (for display purposes). Args: format: The Format object Returns: Number of assets, or 0 if none defined """ return len(get_format_assets(format))Get the count of assets in a format (for display purposes).
Args
format- The Format object
Returns
Number of assets, or 0 if none defined
def get_format_assets(format: Format) ‑> list[FormatAsset]-
Expand source code
def get_format_assets(format: Format) -> list[FormatAsset]: """Get assets from a Format. Returns the list of assets from the format's `assets` field. Returns empty list if no assets are defined (flexible format with no assets). Args: format: The Format object from list_creative_formats response Returns: List of assets Example: ```python formats = await agent.simple.list_creative_formats() for format in formats.formats: assets = get_format_assets(format) print(f"{format.name} has {len(assets)} assets") ``` """ if format.assets and len(format.assets) > 0: return list(format.assets) return []Get assets from a Format.
Returns the list of assets from the format's
assetsfield. Returns empty list if no assets are defined (flexible format with no assets).Args
format- The Format object from list_creative_formats response
Returns
List of assets
Example
formats = await agent.simple.list_creative_formats() for format in formats.formats: assets = get_format_assets(format) print(f"{format.name} has {len(assets)} assets") def get_individual_assets(format: Format) ‑> list[FormatAsset]-
Expand source code
def get_individual_assets(format: Format) -> list[FormatAsset]: """Get individual assets (not repeatable groups) from a Format. Args: format: The Format object Returns: List of individual assets (item_type='individual') """ return [asset for asset in get_format_assets(format) if _get_item_type(asset) == "individual"]Get individual assets (not repeatable groups) from a Format.
Args
format- The Format object
Returns
List of individual assets (item_type='individual')
def get_optional_assets(format: Format) ‑> list[FormatAsset]-
Expand source code
def get_optional_assets(format: Format) -> list[FormatAsset]: """Get only optional assets from a Format. Note: When using deprecated `assets_required`, this will always return empty since assets_required only contained required assets. Args: format: The Format object Returns: List of optional assets only Example: ```python optional_assets = get_optional_assets(format) print(f"Can optionally provide {len(optional_assets)} additional assets") ``` """ return [asset for asset in get_format_assets(format) if not _is_required(asset)]Get only optional assets from a Format.
Note: When using deprecated
assets_required, this will always return empty since assets_required only contained required assets.Args
format- The Format object
Returns
List of optional assets only
Example
optional_assets = get_optional_assets(format) print(f"Can optionally provide {len(optional_assets)} additional assets") def get_properties_by_agent(adagents_data: dict[str, Any], agent_url: str) ‑> list[dict[str, typing.Any]]-
Expand source code
def get_properties_by_agent(adagents_data: dict[str, Any], agent_url: str) -> list[dict[str, Any]]: """Get all properties authorized for a specific agent. Handles all authorization types per the AdCP specification: - inline_properties: Properties defined directly in the agent's properties array - property_ids: Filter top-level properties by property_id - property_tags: Filter top-level properties by tags - publisher_properties: Inline-resolved properties from cross-publisher selectors (resolved from the parent file's top-level properties[] array per adcp#4827) For ``publisher_properties`` selectors whose target ``publisher_domain`` is NOT present inline in this file's top-level ``properties[]`` array, this function returns no properties for that selector. Federated fallback (fetching the child publisher's own adagents.json to resolve the selector remotely) is out of scope here and lives in :func:`fetch_agent_authorizations_from_directory` and :func:`detect_publisher_properties_divergence` from companion PR #752. Wire-only authorization checks that assume federated resolution will under-authorize against managed-network parent files that only inline a subset of their child domains. 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 """ return _resolve_properties_for_agent(adagents_data, agent_url, permissive_bare_top_level=False)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: Inline-resolved properties from cross-publisher selectors (resolved from the parent file's top-level properties[] array per adcp#4827)
For
publisher_propertiesselectors whose targetpublisher_domainis NOT present inline in this file's top-levelproperties[]array, this function returns no properties for that selector. Federated fallback (fetching the child publisher's own adagents.json to resolve the selector remotely) is out of scope here and lives in :func:fetch_agent_authorizations_from_directory()and :func:detect_publisher_properties_divergence()from companion PR #752. Wire-only authorization checks that assume federated resolution will under-authorize against managed-network parent files that only inline a subset of their child domains.Args
adagents_data- Parsed adagents.json data
agent_url- URL of the agent to filter by
Returns
List of properties for the specified agent (empty if agent not found)
Raises
AdagentsValidationError- If adagents_data is malformed
def get_repeatable_groups(format: Format) ‑> list[FormatAsset]-
Expand source code
def get_repeatable_groups(format: Format) -> list[FormatAsset]: """Get repeatable asset groups from a Format. Args: format: The Format object Returns: List of repeatable asset groups (item_type='repeatable_group') """ return [ asset for asset in get_format_assets(format) if _get_item_type(asset) == "repeatable_group" ]Get repeatable asset groups from a Format.
Args
format- The Format object
Returns
List of repeatable asset groups (item_type='repeatable_group')
def get_required_assets(format: Format) ‑> list[FormatAsset]-
Expand source code
def get_required_assets(format: Format) -> list[FormatAsset]: """Get only required assets from a Format. Args: format: The Format object Returns: List of required assets only Example: ```python required_assets = get_required_assets(format) print(f"Must provide {len(required_assets)} assets") ``` """ return [asset for asset in get_format_assets(format) if _is_required(asset)]Get only required assets from a Format.
Args
format- The Format object
Returns
List of required assets only
Example
required_assets = get_required_assets(format) print(f"Must provide {len(required_assets)} assets") def has_assets(format: Format) ‑> bool-
Expand source code
def has_assets(format: Format) -> bool: """Check if a format has any assets defined. Args: format: The Format object Returns: True if format has assets, False otherwise """ return get_asset_count(format) > 0Check if a format has any assets defined.
Args
format- The Format object
Returns
True if format has assets, False otherwise
def identifiers_match(property_identifiers: list[dict[str, str]],
agent_identifiers: list[dict[str, str]]) ‑> bool-
Expand source code
def identifiers_match( property_identifiers: list[dict[str, str]], agent_identifiers: list[dict[str, str]], ) -> bool: """Check if any property identifier matches agent's authorized identifiers. Args: property_identifiers: Identifiers from property (e.g., [{"type": "domain", "value": "cnn.com"}]) agent_identifiers: Identifiers from adagents.json Returns: True if any identifier matches Notes: - Domain identifiers use AdCP domain matching rules - Other identifiers (bundle_id, roku_store_id, etc.) require exact match """ for prop_id in property_identifiers: prop_type = prop_id.get("type", "") prop_value = prop_id.get("value", "") for agent_id in agent_identifiers: agent_type = agent_id.get("type", "") agent_value = agent_id.get("value", "") # Type must match if prop_type != agent_type: continue # Domain identifiers use special matching rules if prop_type == "domain": if domain_matches(prop_value, agent_value): return True else: # Other identifier types require exact match if prop_value == agent_value: return True return FalseCheck if any property identifier matches agent's authorized identifiers.
Args
property_identifiers- Identifiers from property (e.g., [{"type": "domain", "value": "cnn.com"}])
agent_identifiers- Identifiers from adagents.json
Returns
True if any identifier matches
Notes
- Domain identifiers use AdCP domain matching rules
- Other identifiers (bundle_id, roku_store_id, etc.) require exact match
def normalize_assets_required(assets_required: list[Any]) ‑> list[typing.Any]-
Expand source code
def normalize_assets_required(assets_required: list[Any]) -> list[FormatAsset]: """Convert deprecated assets_required to new assets format. .. deprecated:: 3.2.0 The ``assets_required`` field was removed in ADCP 3.0.0-beta.2. This function will be removed in a future version. All assets in assets_required are required by definition (that's why they were in that array). The new `assets` field has an explicit `required: boolean` to allow both required AND optional assets. Args: assets_required: The deprecated assets_required array Returns: Normalized assets as Pydantic models with explicit required=True """ warnings.warn( "normalize_assets_required() is deprecated. " "The assets_required field was removed in ADCP 3.0.0-beta.2. " "This function will be removed in a future version.", DeprecationWarning, stacklevel=2, ) normalized: list[FormatAsset] = [] for asset in assets_required: # Get asset data as dict if isinstance(asset, dict): asset_dict = asset else: asset_dict = asset.model_dump() if hasattr(asset, "model_dump") else dict(asset) # Map old fields to new schema format mapped = {**asset_dict, "required": True} # Ensure asset_id is present (map from asset_group_id if needed) if "asset_group_id" in mapped and "asset_id" not in mapped: mapped["asset_id"] = mapped.pop("asset_group_id") # Remove fields that don't exist in the new schema for old_field in ("min_count", "max_count", "assets"): mapped.pop(old_field, None) # Use AssetsModel (individual asset type) normalized.append(AssetsModel(**mapped)) return normalizedConvert deprecated assets_required to new assets format.
Deprecated since version: 3.2.0
The
assets_requiredfield was removed in ADCP 3.0.0-beta.2. This function will be removed in a future version.All assets in assets_required are required by definition (that's why they were in that array). The new
assetsfield has an explicitrequired: booleanto allow both required AND optional assets.Args
assets_required- The deprecated assets_required array
Returns
Normalized assets as Pydantic models with explicit required=True
def resolve_properties_for_agent(adagents_data: dict[str, Any],
agent_url: str,
*,
mode: PropertyResolutionMode = 'strict') ‑> list[dict[str, typing.Any]]-
Expand source code
def resolve_properties_for_agent( adagents_data: dict[str, Any], agent_url: str, *, mode: PropertyResolutionMode = "strict", ) -> list[dict[str, Any]]: """Resolve properties for an agent with an explicit strict/permissive mode. ``mode="strict"`` is identical to :func:`get_properties_by_agent` and only honors schema-conformant authorization selectors plus the historical inline ``properties`` legacy shape. ``mode="permissive"`` keeps every strict selector behavior unchanged, but treats one exact matching bare ``authorized_agents`` entry (``{"url": ..., "authorized_for": ...}``) as authorizing the file's top-level ``properties[]``. This is for operational binding of non-conformant publisher files that list an agent URL without an ``authorization_type`` or selector. If the agent is not listed, has any explicit or unknown selector field, or has multiple same-URL entries, the resolver still returns the strict result. Args: adagents_data: Parsed adagents.json data agent_url: URL of the agent to filter by mode: ``"strict"`` for spec-conformant resolution, ``"permissive"`` to opt into bare-entry top-level property fallback. Returns: List of properties for the specified agent. Raises: AdagentsValidationError: If adagents_data is malformed ValueError: If mode is not ``"strict"`` or ``"permissive"`` """ if mode == "strict": return _resolve_properties_for_agent( adagents_data, agent_url, permissive_bare_top_level=False, ) if mode == "permissive": return _resolve_properties_for_agent( adagents_data, agent_url, permissive_bare_top_level=True, ) raise ValueError("mode must be 'strict' or 'permissive'")Resolve properties for an agent with an explicit strict/permissive mode.
mode="strict"is identical to :func:get_properties_by_agent()and only honors schema-conformant authorization selectors plus the historical inlinepropertieslegacy shape.mode="permissive"keeps every strict selector behavior unchanged, but treats one exact matching bareauthorized_agentsentry ({"url": ..., "authorized_for": ...}) as authorizing the file's top-levelproperties[]. This is for operational binding of non-conformant publisher files that list an agent URL without anauthorization_typeor selector. If the agent is not listed, has any explicit or unknown selector field, or has multiple same-URL entries, the resolver still returns the strict result.Args
adagents_data- Parsed adagents.json data
agent_url- URL of the agent to filter by
mode"strict"for spec-conformant resolution,"permissive"to opt into bare-entry top-level property fallback.
Returns
List of properties for the specified agent.
Raises
AdagentsValidationError- If adagents_data is malformed
ValueError- If mode is not
"strict"or"permissive"
def sign_legacy_webhook(secret: str,
payload: dict[str, Any] | AdCPBaseModel,
*,
timestamp: str | int | None = None,
headers: dict[str, Any] | None = None) ‑> tuple[dict[str, str], bytes]-
Expand source code
def sign_legacy_webhook( secret: str, payload: dict[str, Any] | AdCPBaseModel, *, timestamp: str | int | None = None, headers: dict[str, Any] | None = None, ) -> tuple[dict[str, str], bytes]: """Return ``(signed_headers, body_bytes)`` for a legacy HMAC webhook. Byte-equality between signature input and HTTP body is guaranteed — callers POST ``content=body_bytes`` instead of ``json=payload``, so the separator-drift trap that caused silent 401s in every spaced-vs-compact interop is structurally impossible here. This is a lower-level companion to :func:`deliver` for callers who need to own the HTTP transport themselves (custom auth, pre-configured ``httpx.AsyncClient``, non-httpx clients). For the one-shot "send a webhook" path, prefer :func:`deliver`. The returned ``body_bytes`` use compact separators (``","``/``":"``) matching the canonical on-wire form pinned by adcontextprotocol/adcp#2478. Example: >>> signed, body = sign_legacy_webhook("shared-secret", payload) >>> headers = {**signed, "Content-Type": "application/json"} >>> await client.post(url, content=body, headers=headers) """ signature_headers, body_bytes = _compute_legacy_signature( secret=secret, timestamp=timestamp, payload=payload ) if headers is not None: merged = {str(k): str(v) for k, v in headers.items()} merged.update(signature_headers) return merged, body_bytes return signature_headers, body_bytesReturn
(signed_headers, body_bytes)for a legacy HMAC webhook.Byte-equality between signature input and HTTP body is guaranteed — callers POST
content=body_bytesinstead ofjson=payload, so the separator-drift trap that caused silent 401s in every spaced-vs-compact interop is structurally impossible here.This is a lower-level companion to :func:
deliverfor callers who need to own the HTTP transport themselves (custom auth, pre-configuredhttpx.AsyncClient, non-httpx clients). For the one-shot "send a webhook" path, prefer :func:deliver.The returned
body_bytesuse compact separators (","/":") matching the canonical on-wire form pinned by adcontextprotocol/adcp#2478.Example
>>> signed, body = sign_legacy_webhook("shared-secret", payload) >>> headers = {**signed, "Content-Type": "application/json"} >>> await client.post(url, content=body, headers=headers) def sign_webhook(*,
method: str,
url: str,
headers: Mapping[str, str],
body: bytes,
private_key: PrivateKey,
key_id: str,
alg: str,
created: int | None = None,
expires_in_seconds: int = 300,
nonce: str | None = None,
label: str = 'sig1') ‑> SignedHeaders-
Expand source code
def sign_webhook( *, method: str, url: str, headers: Mapping[str, str], body: bytes, private_key: PrivateKey, key_id: str, alg: str, created: int | None = None, expires_in_seconds: int = DEFAULT_EXPIRES_IN_SECONDS, nonce: str | None = None, label: str = SIG_LABEL_DEFAULT, ) -> SignedHeaders: """Sign an outgoing webhook POST per adcp/webhook-signing/v1. ``cover_content_digest=True`` and ``tag=WEBHOOK_TAG`` are pinned. The caller attaches ``SignedHeaders.as_dict()`` to the outgoing HTTP request. The ``method`` is normally ``"POST"`` for webhook delivery; passed through unchanged so callers signing a retried ``PUT`` or variant delivery verb are not forced into an extra translation. See also: :class:`adcp.webhooks.WebhookSender` — higher-level one-call helper that builds the payload, signs, and POSTs in a single call. Prefer it unless you need to own the HTTP transport yourself. """ return sign_request( method=method, url=url, headers=headers, body=body, private_key=private_key, key_id=key_id, alg=alg, cover_content_digest=True, created=created, expires_in_seconds=expires_in_seconds, nonce=nonce, tag=WEBHOOK_TAG, label=label, )Sign an outgoing webhook POST per adcp/webhook-signing/v1.
cover_content_digest=Trueandtag=WEBHOOK_TAGare pinned. The caller attachesSignedHeaders.as_dict()to the outgoing HTTP request.The
methodis normally"POST"for webhook delivery; passed through unchanged so callers signing a retriedPUTor variant delivery verb are not forced into an extra translation.See also: :class:
WebhookSender— higher-level one-call helper that builds the payload, signs, and POSTs in a single call. Prefer it unless you need to own the HTTP transport yourself. def to_wire_dict(payload: AdCPBaseModel | Task | TaskStatusUpdateEvent | Mapping[str, Any]) ‑> dict[str, typing.Any]-
Expand source code
def to_wire_dict( payload: AdCPBaseModel | Task | TaskStatusUpdateEvent | Mapping[str, Any], ) -> dict[str, Any]: """Serialize any AdCP webhook payload to a JSON-ready dict. Single seam for adopters that accept "any AdCP webhook payload" — a sender wrapping :func:`create_a2a_webhook_payload` and :func:`create_mcp_webhook_payload` would otherwise have to write per-shape dispatch (``isinstance`` checks, ``MessageToDict`` for protobuf, ``model_dump`` for Pydantic, passthrough for dict). Brittle: a future a2a-sdk that swaps protobuf for a Pydantic façade silently changes which branch runs, and adopters duplicate the dispatch in every send path. Use this helper instead — the dispatch lives here. Behaviour by input shape: * a2a ``Task`` / ``TaskStatusUpdateEvent`` (protobuf, a2a-sdk 1.0+) → ``MessageToDict(..., preserving_proto_field_name=False)`` so JSON keys match the A2A wire spec (camelCase: ``id``, ``contextId``, ``artifactId``). Enum values are normalized from the 1.0 protobuf form (``TASK_STATE_COMPLETED``, ``ROLE_AGENT``) to the 0.3-spec lowercase form (``completed``, ``agent``) so 0.3 buyer receivers keep parsing. * Any Pydantic model (``McpWebhookPayload``, future Pydantic façades, :class:`AdCPBaseModel` subclasses) → ``model_dump(mode="json", exclude_none=True)``. * ``Mapping`` → coerced to ``dict``. Legacy adopter passthrough for callers that build the wire dict by hand. Raises: TypeError: payload is none of the above. """ if isinstance(payload, (Task, TaskStatusUpdateEvent)): data = MessageToDict(payload, preserving_proto_field_name=False) _normalize_a2a_task_state_to_v03(data) return data if hasattr(payload, "model_dump"): model = cast(AdCPBaseModel, payload) return model.model_dump(mode="json", exclude_none=True) if isinstance(payload, Mapping): return dict(payload) raise TypeError( f"Unsupported webhook payload type {type(payload).__name__}: expected " "a2a Task / TaskStatusUpdateEvent (protobuf), an AdCP Pydantic model " "(e.g. McpWebhookPayload), or a Mapping[str, Any]." )Serialize any AdCP webhook payload to a JSON-ready dict.
Single seam for adopters that accept "any AdCP webhook payload" — a sender wrapping :func:
create_a2a_webhook_payload()and :func:create_mcp_webhook_payload()would otherwise have to write per-shape dispatch (isinstancechecks,MessageToDictfor protobuf,model_dumpfor Pydantic, passthrough for dict). Brittle: a future a2a-sdk that swaps protobuf for a Pydantic façade silently changes which branch runs, and adopters duplicate the dispatch in every send path. Use this helper instead — the dispatch lives here.Behaviour by input shape:
- a2a
Task/TaskStatusUpdateEvent(protobuf, a2a-sdk 1.0+) →MessageToDict(..., preserving_proto_field_name=False)so JSON keys match the A2A wire spec (camelCase:id,contextId,artifactId). Enum values are normalized from the 1.0 protobuf form (TASK_STATE_COMPLETED,ROLE_AGENT) to the 0.3-spec lowercase form (completed,agent) so 0.3 buyer receivers keep parsing. - Any Pydantic model (
McpWebhookPayload, future Pydantic façades, :class:AdCPBaseModelsubclasses) →model_dump(mode="json", exclude_none=True). Mapping→ coerced todict. Legacy adopter passthrough for callers that build the wire dict by hand.
Raises
TypeError- payload is none of the above.
- a2a
def upgrade_legacy_format_id(value: str | FormatReferenceStructuredObject | Mapping[str, Any],
*,
default_agent_url: str = 'https://creative.adcontextprotocol.org') ‑> adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject-
Expand source code
def upgrade_legacy_format_id( value: str | FormatId | Mapping[str, Any], *, default_agent_url: str = CANONICAL_CREATIVE_AGENT_URL, ) -> FormatId: """Return ``value`` as a canonical, parameterized ``FormatId`` when known. The current canonical upgrade maps legacy display size IDs such as ``display_300x250`` and ``display_300x250_image`` to ``display_image`` with ``width=300`` and ``height=250``. Unknown IDs are still returned as structured ``FormatId`` values so callers can compare them consistently. """ is_bare_legacy_id = isinstance(value, str) fid = _coerce_format_id(value, default_agent_url=default_agent_url) match = _DISPLAY_SIZE_RE.fullmatch(fid.id) if match is None: return fid default_fid = _coerce_format_id("__default__", default_agent_url=default_agent_url) if not is_bare_legacy_id and canonicalize_agent_url(fid.agent_url) != canonicalize_agent_url( default_fid.agent_url ): return fid return FormatId.model_validate( { "agent_url": str(fid.agent_url), "id": "display_image", "width": int(match.group("width")), "height": int(match.group("height")), "duration_ms": fid.duration_ms, } )Return
valueas a canonical, parameterizedFormatReferenceStructuredObjectwhen known.The current canonical upgrade maps legacy display size IDs such as
display_300x250anddisplay_300x250_imagetodisplay_imagewithwidth=300andheight=250. Unknown IDs are still returned as structuredFormatReferenceStructuredObjectvalues so callers can compare them consistently. def uses_deprecated_assets_field(format: Format) ‑> bool-
Expand source code
def uses_deprecated_assets_field(format: Format) -> bool: """Check if format uses deprecated assets_required field. .. deprecated:: 3.2.0 The ``assets_required`` field was removed in ADCP 3.0.0-beta.2. This function always returns False and will be removed in a future version. Args: format: The Format object Returns: Always False (deprecated field no longer exists) """ warnings.warn( "uses_deprecated_assets_field() is deprecated and always returns False. " "The assets_required field was removed in ADCP 3.0.0-beta.2. " "This function will be removed in a future version.", DeprecationWarning, stacklevel=2, ) return FalseCheck if format uses deprecated assets_required field.
Deprecated since version: 3.2.0
The
assets_requiredfield was removed in ADCP 3.0.0-beta.2. This function always returns False and will be removed in a future version.Args
format- The Format object
Returns
Always False (deprecated field no longer exists)
def validate_adagents(adagents: dict[str, Any]) ‑> None-
Expand source code
def validate_adagents(adagents: dict[str, Any]) -> None: """Validate an adagents.json structure. Args: adagents: The adagents.json dict Raises: ValidationError: If validation fails """ authorized_agents = adagents.get("authorized_agents") if isinstance(authorized_agents, list): for agent in authorized_agents: if isinstance(agent, dict): validate_agent_authorization(agent) revoked = adagents.get("revoked_publisher_domains") if revoked is not None: if not isinstance(revoked, list): raise ValidationError("'revoked_publisher_domains' must be an array") for entry in revoked: if not isinstance(entry, dict): raise ValidationError("revoked_publisher_domains entry must be an object") validate_revoked_publisher_domain_entry(entry)Validate an adagents.json structure.
Args
adagents- The adagents.json dict
Raises
ValidationError- If validation fails
async def validate_adagents_domain(publisher_domain: str,
timeout: float = 10.0,
user_agent: str = 'AdCP-Client/1.0',
client: httpx.AsyncClient | None = None) ‑> AdAgentsValidationResult-
Expand source code
async def validate_adagents_domain( publisher_domain: str, timeout: float = 10.0, user_agent: str = "AdCP-Client/1.0", client: httpx.AsyncClient | None = None, ) -> AdAgentsValidationResult: """Discover and validate a publisher's adagents.json with provenance. Mirrors :func:`fetch_adagents` discovery semantics but returns a typed :class:`AdAgentsValidationResult` exposing which path produced the data (``discovery_method``) and the manager domain used for the RFC 4175 fallback (``manager_domain``), if any. Errors are reported on the result rather than raised. A manager domain 404 is a terminal failure: ``valid`` is False and ``manager_domain`` is recorded for diagnostics. .. warning:: When ``discovery_method == 'ads_txt_managerdomain'`` the data came from the manager, not the publisher. Callers wiring this into authorization decisions must verify that the source publisher is explicitly named in the manager's adagents.json (e.g., via ``publisher_properties.publisher_domain`` on the relevant authorized_agents entry) before trusting an agent claim — otherwise a manager that lists agent A unconditionally implicitly authorizes A for every publisher pointing MANAGERDOMAIN at the manager. """ try: normalized = _validate_publisher_domain(publisher_domain) except AdagentsValidationError as e: return AdAgentsValidationResult( domain=publisher_domain, url="", errors=[str(e)], ) url = f"https://{normalized}/.well-known/adagents.json" try: data, discovery, *_ = await _resolve_direct(normalized, timeout, user_agent, client) return AdAgentsValidationResult( domain=normalized, url=url, discovery_method=discovery, data=data, valid=True, ) except AdagentsNotFoundError as direct_error: direct_error_msg = str(direct_error) except (AdagentsValidationError, AdagentsTimeoutError) as e: return AdAgentsValidationResult( domain=normalized, url=url, errors=[str(e)], ) managers = await _fetch_ads_txt_managerdomains(normalized, timeout, user_agent, client) if not managers: return AdAgentsValidationResult( domain=normalized, url=url, errors=[direct_error_msg], ) manager_domain = managers[-1] if manager_domain == normalized: return AdAgentsValidationResult( domain=normalized, url=url, errors=[ direct_error_msg, f"ads.txt managerdomain {manager_domain} points back to source publisher", ], ) manager_normalized = _ensure_safe_manager_domain(manager_domain) if manager_normalized is None: return AdAgentsValidationResult( domain=normalized, url=url, errors=[ direct_error_msg, f"ads.txt managerdomain {manager_domain!r} is malformed or " "targets a private/reserved address", ], ) try: manager_data, *_ = await _resolve_direct( manager_normalized, timeout, user_agent, client=None ) except AdagentsNotFoundError: return AdAgentsValidationResult( domain=normalized, url=url, discovery_method="ads_txt_managerdomain", manager_domain=manager_normalized, errors=[ direct_error_msg, f"manager domain {manager_normalized} did not serve adagents.json", ], ) except (AdagentsValidationError, AdagentsTimeoutError) as e: return AdAgentsValidationResult( domain=normalized, url=url, discovery_method="ads_txt_managerdomain", manager_domain=manager_normalized, errors=[direct_error_msg, str(e)], ) return AdAgentsValidationResult( domain=normalized, url=url, discovery_method="ads_txt_managerdomain", manager_domain=manager_normalized, data=manager_data, valid=True, )Discover and validate a publisher's adagents.json with provenance.
Mirrors :func:
fetch_adagents()discovery semantics but returns a typed :class:AdAgentsValidationResultexposing which path produced the data (discovery_method) and the manager domain used for the RFC 4175 fallback (manager_domain), if any.Errors are reported on the result rather than raised. A manager domain 404 is a terminal failure:
validis False andmanager_domainis recorded for diagnostics.Warning
When
discovery_method == 'ads_txt_managerdomain'the data came from the manager, not the publisher. Callers wiring this into authorization decisions must verify that the source publisher is explicitly named in the manager's adagents.json (e.g., viapublisher_properties.publisher_domainon the relevant authorized_agents entry) before trusting an agent claim — otherwise a manager that lists agent A unconditionally implicitly authorizes A for every publisher pointing MANAGERDOMAIN at the manager. def validate_adagents_structure(adagents_data: dict[str, Any]) ‑> AdagentsValidationReport-
Expand source code
def validate_adagents_structure(adagents_data: dict[str, Any]) -> AdagentsValidationReport: """Structurally validate a parsed adagents.json against the AdCP schema. Use this to distinguish a schema-invalid file from a valid file that doesn't list a particular agent. :func:`get_properties_by_agent` returns ``[]`` for both cases, which makes "publisher hasn't authorized us yet" indistinguishable from "publisher's file is structurally broken." This helper reports per-entry violations against the authoritative ``authorized_agents`` oneOf in the AdCP adagents.json schema. The two real-world failure modes this catches in production publisher files are: * **Bare entries** — ``{url, authorized_for}`` with no ``authorization_type``. The agent looks listed, but matches no schema variant, so the SDK treats the entry as authorizing nothing. * **Wrong selector for type** — e.g., ``{authorization_type: "property_ids", property_tags: [...]}``, where the discriminator and selector array disagree. Args: adagents_data: Parsed adagents.json (the dict returned by :func:`fetch_adagents` or loaded directly from JSON). Returns: :class:`AdagentsValidationReport`. ``schema_valid`` is True only when every entry in ``authorized_agents`` satisfies the schema. Raises: AdagentsValidationError: If ``adagents_data`` is not a dict, or ``authorized_agents`` is not a list. These are input-shape errors, not per-entry schema violations. Notes: * URL-reference variants (``authoritative_location`` form) have no inline ``authorized_agents`` array. They're reported with ``is_reference=True``, ``authorized_agents_count == 0``, and ``schema_valid=True``. Callers should follow the redirect (e.g., via :func:`fetch_adagents`, which resolves it automatically) and re-validate the resolved file. * The schema targets AdCP 3.0. Files written against 2.5 (no signal_ids / signal_tags variants) will flag those entries as ``unknown_authorization_type`` — correct for the 3.0 target, but worth knowing if you're validating mixed-version traffic. * Selector-array *item* patterns (e.g., the ``^[a-zA-Z0-9_-]+$`` constraint on each signal_id) are out of scope. This helper validates the discriminator + required selector array; it does not deep-validate selector contents. """ if not isinstance(adagents_data, dict): raise AdagentsValidationError("adagents_data must be a dictionary") authorized_agents = adagents_data.get("authorized_agents") if authorized_agents is None: # URL-reference variant: file points at an authoritative_location # rather than carrying an inline authorized_agents array. properties = adagents_data.get("properties", []) is_reference = isinstance(adagents_data.get("authoritative_location"), str) return AdagentsValidationReport( schema_valid=True, errors=[], authorized_agents_count=0, properties_count=len(properties) if isinstance(properties, list) else 0, is_reference=is_reference, ) if not isinstance(authorized_agents, list): raise AdagentsValidationError("'authorized_agents' must be an array") properties = adagents_data.get("properties", []) properties_count = len(properties) if isinstance(properties, list) else 0 errors: list[AdagentsEntryError] = [] if len(authorized_agents) == 0: # Inline variant requires minItems: 1 on authorized_agents. errors.append( AdagentsEntryError( index=-1, kind="empty_authorized_agents", message=( "adagents.json inline variant requires at least one entry " "in 'authorized_agents' (schema minItems: 1)" ), ) ) for index, entry in enumerate(authorized_agents): if not isinstance(entry, dict): errors.append( AdagentsEntryError( index=index, kind="not_an_object", message=f"authorized_agents[{index}] is not a JSON object", ) ) continue raw_url = entry.get("url") url = raw_url if isinstance(raw_url, str) and raw_url else None if url is None: errors.append( AdagentsEntryError( index=index, kind="missing_url", message=f"authorized_agents[{index}] is missing required 'url'", ) ) authorized_for = entry.get("authorized_for") if not isinstance(authorized_for, str) or not authorized_for: errors.append( AdagentsEntryError( index=index, kind="missing_authorized_for", message=( f"authorized_agents[{index}] is missing required " "'authorized_for' description (string, minLength 1)" ), url=url, ) ) authorization_type = entry.get("authorization_type") if authorization_type is None: errors.append( AdagentsEntryError( index=index, kind="missing_authorization_type", message=( f"authorized_agents[{index}] is missing required " "'authorization_type' discriminator (expected one of: " f"{', '.join(sorted(_AUTHORIZATION_TYPE_TO_SELECTOR))})" ), url=url, ) ) continue if authorization_type not in _AUTHORIZATION_TYPE_TO_SELECTOR: errors.append( AdagentsEntryError( index=index, kind="unknown_authorization_type", message=( f"authorized_agents[{index}] has unknown " f"authorization_type={authorization_type!r} " f"(expected one of: " f"{', '.join(sorted(_AUTHORIZATION_TYPE_TO_SELECTOR))})" ), url=url, ) ) continue required_selector = _AUTHORIZATION_TYPE_TO_SELECTOR[authorization_type] selector_value = entry.get(required_selector) if not isinstance(selector_value, list) or len(selector_value) == 0: errors.append( AdagentsEntryError( index=index, kind="missing_selector_for_type", message=( f"authorized_agents[{index}] has " f"authorization_type={authorization_type!r} but is " f"missing required non-empty {required_selector!r} array" ), url=url, ) ) return AdagentsValidationReport( schema_valid=not errors, errors=errors, authorized_agents_count=len(authorized_agents), properties_count=properties_count, )Structurally validate a parsed adagents.json against the AdCP schema.
Use this to distinguish a schema-invalid file from a valid file that doesn't list a particular agent. :func:
get_properties_by_agent()returns[]for both cases, which makes "publisher hasn't authorized us yet" indistinguishable from "publisher's file is structurally broken." This helper reports per-entry violations against the authoritativeauthorized_agentsoneOf in the AdCP adagents.json schema.The two real-world failure modes this catches in production publisher files are:
- Bare entries —
{url, authorized_for}with noauthorization_type. The agent looks listed, but matches no schema variant, so the SDK treats the entry as authorizing nothing. - Wrong selector for type — e.g.,
{authorization_type: "property_ids", property_tags: [...]}, where the discriminator and selector array disagree.
Args
adagents_data- Parsed adagents.json (the dict returned by
:func:
fetch_adagents()or loaded directly from JSON).
Returns
:class:
AdagentsValidationReport.schema_validis True only when every entry inauthorized_agentssatisfies the schema.Raises
AdagentsValidationError- If
adagents_datais not a dict, orauthorized_agentsis not a list. These are input-shape errors, not per-entry schema violations.
Notes
- URL-reference variants (
authoritative_locationform) have no inlineauthorized_agentsarray. They're reported withis_reference=True,authorized_agents_count == 0, andschema_valid=True. Callers should follow the redirect (e.g., via :func:fetch_adagents(), which resolves it automatically) and re-validate the resolved file. - The schema targets AdCP 3.0. Files written against 2.5 (no
signal_ids / signal_tags variants) will flag those entries as
unknown_authorization_type— correct for the 3.0 target, but worth knowing if you're validating mixed-version traffic. - Selector-array item patterns (e.g., the
^[a-zA-Z0-9_-]+$constraint on each signal_id) are out of scope. This helper validates the discriminator + required selector array; it does not deep-validate selector contents.
- Bare entries —
-
Expand source code
def validate_agent_authorization(agent: dict[str, Any]) -> None: """Validate agent authorization discriminated union. AdCP v2.4.0+ uses discriminated unions with authorization_type discriminator: - authorization_type: "property_ids" requires property_ids - authorization_type: "property_tags" requires property_tags - authorization_type: "inline_properties" requires properties - authorization_type: "publisher_properties" requires publisher_properties For backward compatibility, also validates the old mutual exclusivity constraint. Args: agent: An agent dict from adagents.json Raises: ValidationError: If discriminator or field constraints are violated """ authorization_type = agent.get("authorization_type") auth_fields = ["properties", "property_ids", "property_tags", "publisher_properties"] present_fields = [field for field in auth_fields if field in agent and agent[field] is not None] # If authorization_type discriminator is present, validate discriminated union if authorization_type: if authorization_type == "property_ids" and "property_ids" not in present_fields: raise ValidationError( "Agent with authorization_type='property_ids' must have property_ids" ) elif authorization_type == "property_tags" and "property_tags" not in present_fields: raise ValidationError( "Agent with authorization_type='property_tags' must have property_tags" ) elif authorization_type == "inline_properties" and "properties" not in present_fields: raise ValidationError( "Agent with authorization_type='inline_properties' must have properties" ) elif ( authorization_type == "publisher_properties" and "publisher_properties" not in present_fields ): raise ValidationError( "Agent with authorization_type='publisher_properties' " "must have publisher_properties" ) elif authorization_type not in ( "property_ids", "property_tags", "inline_properties", "publisher_properties", ): raise ValidationError(f"Agent has invalid authorization_type: {authorization_type}") # Validate mutual exclusivity (for both old and new formats) if len(present_fields) > 1: raise ValidationError( f"Agent authorization cannot have multiple fields: {', '.join(present_fields)}. " f"Only one of {', '.join(auth_fields)} is allowed." ) if len(present_fields) == 0: raise ValidationError( f"Agent authorization must have exactly one of: {', '.join(auth_fields)}." ) # If using publisher_properties, validate each item if "publisher_properties" in present_fields: for pub_prop in agent["publisher_properties"]: validate_publisher_properties_item(pub_prop)Validate agent authorization discriminated union.
AdCP v2.4.0+ uses discriminated unions with authorization_type discriminator: - authorization_type: "property_ids" requires property_ids - authorization_type: "property_tags" requires property_tags - authorization_type: "inline_properties" requires properties - authorization_type: "publisher_properties" requires publisher_properties
For backward compatibility, also validates the old mutual exclusivity constraint.
Args
agent- An agent dict from adagents.json
Raises
ValidationError- If discriminator or field constraints are violated
def validate_capabilities(handler: Any, capabilities: GetAdcpCapabilitiesResponse) ‑> list[str]-
Expand source code
def validate_capabilities( handler: Any, capabilities: GetAdcpCapabilitiesResponse, ) -> list[str]: """Check that a handler implements the methods required by its declared features. Compares the features declared in a capabilities response against the handler's method implementations. Returns warnings for features that are declared but whose corresponding handler methods are not overridden from the base class. This is a development-time check — call it at startup to catch misconfigurations. Args: handler: An ADCPHandler instance (or any object with handler methods). capabilities: The capabilities response the handler will serve. Returns: List of warning strings. Empty if everything is consistent. """ # Late import to avoid circular dependency: server.base imports from adcp.types # which may transitively import from this module. from adcp.server.base import ADCPHandler resolver = FeatureResolver(capabilities) warnings: list[str] = [] for feature, handler_methods in FEATURE_HANDLER_MAP.items(): if not resolver.supports(feature): continue for method_name in handler_methods: if not hasattr(handler, method_name): warnings.append( f"Feature '{feature}' is declared but handler has no " f"'{method_name}' method" ) continue # Walk MRO to check if any class between the leaf and ADCPHandler # overrides the method (handles mixin / intermediate-class patterns). if isinstance(handler, ADCPHandler): overridden = any( method_name in cls.__dict__ for cls in type(handler).__mro__ if cls is not ADCPHandler and not issubclass(ADCPHandler, cls) ) if not overridden: warnings.append( f"Feature '{feature}' is declared but '{method_name}' " f"is not overridden from ADCPHandler" ) return warningsCheck that a handler implements the methods required by its declared features.
Compares the features declared in a capabilities response against the handler's method implementations. Returns warnings for features that are declared but whose corresponding handler methods are not overridden from the base class.
This is a development-time check — call it at startup to catch misconfigurations.
Args
handler- An ADCPHandler instance (or any object with handler methods).
capabilities- The capabilities response the handler will serve.
Returns
List of warning strings. Empty if everything is consistent.
def validate_product(product: dict[str, Any]) ‑> None-
Expand source code
def validate_product(product: dict[str, Any]) -> None: """Validate a Product object. Args: product: Product dict Raises: ValidationError: If validation fails """ if "publisher_properties" in product and product["publisher_properties"]: for item in product["publisher_properties"]: validate_publisher_properties_item(item) def validate_publisher_properties_item(item: Any) ‑> None-
Expand source code
def validate_publisher_properties_item(item: Any) -> None: """Validate a single ``publisher_properties[]`` entry. Accepts either a raw ``dict`` (the wire form) or a parsed Pydantic model instance (``PublisherPropertySelector1`` / ``…2`` / ``…3``). For Pydantic instances the model is coerced via ``.model_dump(exclude_none=False)`` and the same checks apply. Two XORs are enforced per the publisher-property-selector JSON Schema (adcp#4504): * Selector XOR: exactly one of ``property_ids`` / ``property_tags`` is present for ``by_id`` / ``by_tag`` (``all`` requires neither). * Publisher XOR: exactly one of ``publisher_domain`` (singular) or ``publisher_domains`` (compact array) is present — both or neither both fail. ``publisher_domains`` is NOT allowed on ``selection_type='by_id'`` since property IDs are publisher-scoped; callers wanting per-publisher ID sets must use one entry per publisher. Why the Pydantic input form matters: ``datamodel-code-generator`` cannot translate the JSON Schema's ``allOf[not[required[both]]] + anyOf[required[either]]`` construct into Pydantic field constraints, so the typed surface (selector 1/3 direct instantiation) is laxer than the schema. Consumers parsing via Pydantic should call this helper post-construction to close the gap. Args: item: A single item from publisher_properties array — either a ``dict`` or a Pydantic ``BaseModel`` instance. Raises: ValidationError: If discriminator or field constraints are violated """ if hasattr(item, "model_dump"): item = item.model_dump(exclude_none=False) if not isinstance(item, dict): raise ValidationError( "publisher_properties item must be a dict or a Pydantic model " f"instance, got {type(item).__name__}" ) 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 has_publisher_domain = "publisher_domain" in item and item["publisher_domain"] is not None publisher_domains = item.get("publisher_domains") has_publisher_domains = publisher_domains is not None 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 ("all", "by_id", "by_tag"): raise ValidationError( f"publisher_properties item has invalid selection_type: {selection_type}" ) 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." ) # selection_type='all' carries neither selector array; older callers # without the discriminator must still provide one of the two. if selection_type not in ("all",) and 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." ) if has_publisher_domain and has_publisher_domains: raise ValidationError( "publisher_properties item cannot have both publisher_domain and " "publisher_domains. These fields are mutually exclusive (XOR)." ) if not has_publisher_domain and not has_publisher_domains: raise ValidationError( "publisher_properties item must have exactly one of publisher_domain " "or publisher_domains." ) if has_publisher_domains and selection_type == "by_id": # by_id is single-publisher only — property IDs are publisher-scoped, # so fanning the same ID set across multiple publishers is meaningless. raise ValidationError( "publisher_properties item with selection_type='by_id' cannot use " "publisher_domains[]; property IDs are publisher-scoped. Use one " "entry per publisher with publisher_domain." ) if has_publisher_domains: if not isinstance(publisher_domains, list) or len(publisher_domains) == 0: raise ValidationError( "publisher_properties item publisher_domains must be a non-empty array" ) if any(not isinstance(d, str) or not d for d in publisher_domains): raise ValidationError( "publisher_properties item publisher_domains entries must be non-empty strings" ) if len(set(publisher_domains)) != len(publisher_domains): raise ValidationError( "publisher_properties item publisher_domains entries must be unique" )Validate a single
publisher_properties[]entry.Accepts either a raw
dict(the wire form) or a parsed Pydantic model instance (PublisherPropertySelector1/…2/…3). For Pydantic instances the model is coerced via.model_dump(exclude_none=False)and the same checks apply.Two XORs are enforced per the publisher-property-selector JSON Schema (adcp#4504):
- Selector XOR: exactly one of
property_ids/property_tagsis present forby_id/by_tag(allrequires neither). - Publisher XOR: exactly one of
publisher_domain(singular) orpublisher_domains(compact array) is present — both or neither both fail.publisher_domainsis NOT allowed onselection_type='by_id'since property IDs are publisher-scoped; callers wanting per-publisher ID sets must use one entry per publisher.
Why the Pydantic input form matters:
datamodel-code-generatorcannot translate the JSON Schema'sallOf[not[required[both]]] + anyOf[required[either]]construct into Pydantic field constraints, so the typed surface (selector 1/3 direct instantiation) is laxer than the schema. Consumers parsing via Pydantic should call this helper post-construction to close the gap.Args
item- A single item from publisher_properties array — either a
dictor a PydanticBaseModelinstance.
Raises
ValidationError- If discriminator or field constraints are violated
- Selector XOR: exactly one of
def validate_webhook_challenge_response(response: bytes | Mapping[str, Any],
*,
challenge: str,
field: str | None = None,
url: str | None = None) ‑> str-
Expand source code
def validate_webhook_challenge_response( response: bytes | Mapping[str, Any], *, challenge: str, field: str | None = None, url: str | None = None, ) -> str: """Validate that a receiver echoed the challenge value. Receivers may respond with either ``{"challenge": "<value>"}`` or ``{"token": "<value>"}``. The return value is the field that matched. """ try: if isinstance(response, bytes): decoded = json.loads(response.decode("utf-8")) else: decoded = dict(response) except (UnicodeDecodeError, json.JSONDecodeError, TypeError, ValueError) as exc: raise WebhookChallengeError( "webhook challenge response must be a JSON object", reason="invalid_json", field=field, url=url, ) from exc if not isinstance(decoded, Mapping): raise WebhookChallengeError( "webhook challenge response must be a JSON object", reason="invalid_json", field=field, url=url, ) for key in ("challenge", "token"): value = decoded.get(key) if value == challenge: return key if "challenge" in decoded or "token" in decoded: reason = "challenge_mismatch" message = "webhook challenge response did not echo the expected value" else: reason = "missing_echo" message = "webhook challenge response must include 'challenge' or 'token'" raise WebhookChallengeError(message, reason=reason, field=field, url=url)Validate that a receiver echoed the challenge value.
Receivers may respond with either
{"challenge": "<value>"}or{"token": "<value>"}. The return value is the field that matched. -
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 FalseCheck 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). `is_retryable` defaults to ``False`` (inherited). Per the AdCP 3.0.4 prose tightening, `AUTH_REQUIRED` covers two sub-cases: credentials missing (correctable — supply credentials and retry) and credentials presented but rejected (terminal — re-presenting creates SSO retry-storm patterns). Defaulting to non-retryable is the safe biased-toward-the-dangerous-case choice; callers handling the missing-credentials case should retry only after attaching credentials, not on a timer. The 3.1 line splits this into `AUTH_MISSING` and `AUTH_INVALID`. """ 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).
is_retryabledefaults toFalse(inherited). Per the AdCP 3.0.4 prose tightening,AUTH_REQUIREDcovers two sub-cases: credentials missing (correctable — supply credentials and retry) and credentials presented but rejected (terminal — re-presenting creates SSO retry-storm patterns). Defaulting to non-retryable is the safe biased-toward-the-dangerous-case choice; callers handling the missing-credentials case should retry only after attaching credentials, not on a timer. The 3.1 line splits this intoAUTH_MISSINGandAUTH_INVALID.Initialize authentication error.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class ADCPClient (agent_config: AgentConfig,
webhook_url_template: str | None = None,
webhook_secret: str | None = None,
on_activity: Callable[[Activity], None] | None = None,
webhook_timestamp_tolerance: int = 300,
capabilities_ttl: float = 3600.0,
validate_features: bool = False,
strict_idempotency: bool = False,
signing: SigningConfig | None = None,
context_id: str | None = None,
validation: ValidationHookConfig | None = None,
force_a2a_version: str | None = None,
adcp_version: str | None = None,
server_version: str | None = None)-
Expand source code
class ADCPClient: """Client for interacting with a single AdCP agent.""" def __init__( self, agent_config: AgentConfig, webhook_url_template: str | None = None, webhook_secret: str | None = None, on_activity: Callable[[Activity], None] | None = None, webhook_timestamp_tolerance: int = 300, capabilities_ttl: float = 3600.0, validate_features: bool = False, strict_idempotency: bool = False, signing: SigningConfig | None = None, context_id: str | None = None, validation: ValidationHookConfig | None = None, force_a2a_version: str | None = None, adcp_version: str | None = None, server_version: str | None = None, ): """ Initialize ADCP client for a single agent. Args: agent_config: Agent configuration webhook_url_template: Template for webhook URLs with {agent_id}, {task_type}, {operation_id} webhook_secret: Secret for webhook signature verification on_activity: Callback for activity events webhook_timestamp_tolerance: Maximum age (in seconds) for webhook timestamps. Webhooks with timestamps older than this or more than this far in the future are rejected. Defaults to 300 (5 minutes). capabilities_ttl: Time-to-live in seconds for cached capabilities (default: 1 hour) validate_features: When True, automatically check that the seller supports required features before making task calls (e.g., sync_audiences requires audience_targeting). Requires capabilities to have been fetched first. strict_idempotency: When True, verify the seller declared ``adcp.idempotency.replay_ttl_seconds`` in capabilities before any mutating call. Fetches capabilities lazily on first use. Raises ``IdempotencyUnsupportedError`` if the declaration is missing — sellers that don't declare it provide no retry-safety guarantee per AdCP #2315. Defaults to False for backward compatibility. signing: Optional RFC 9421 request-signing config. When provided, the client automatically attaches ``Signature`` / ``Signature-Input`` / ``Content-Digest`` headers to operations the seller's ``request_signing`` capability lists in ``required_for``, ``warn_for``, or ``supported_for``. The seller's ``covers_content_digest`` policy determines whether the body is bound to the signature. Generate a key with ``adcp-keygen`` and publish the public JWK at your ``jwks_uri``. Supported on both A2A and MCP (``mcp_transport="streamable_http"``); SSE-transport MCP logs a warning and falls through unsigned. validation: Schema-driven validation modes for outgoing requests and incoming responses against the bundled AdCP JSON schemas. Defaults (matching the TS port): requests in ``warn`` mode (drift logged but not blocked — partial payloads in error-path tests still work) and responses in ``strict`` mode (agent drift fails the task). ``ADCP_VALIDATION_MODE=strict|warn|off`` overrides both sides at call time (matches the TS port); ``ADCP_ENV`` set to ``production`` / ``prod`` flips only the response default to ``warn``. Generic ``ENV`` / ``ENVIRONMENT`` / ``PYTHON_ENV`` are deliberately ignored — they collide with unrelated tooling. Storyboards and compliance runners that want hard-stop enforcement everywhere pass ``validation=ValidationHookConfig(requests="strict", responses="strict")``; high-throughput callers can set either side to ``"off"`` to skip the validator entirely with zero overhead. context_id: A2A-only. Seed the A2A conversation context. Pass a previously-returned ``context_id`` to resume a session across process restarts, or a self-assigned UUID to name the session with your own correlation key (the ADK server honors buyer-proposed ids). If omitted, the server mints one on the first message and this client auto-retains it for subsequent calls. Read the current value via ``client.context_id``; call ``client.reset_context()`` to start a fresh conversation. Rule of thumb: one ``ADCPClient`` per A2A conversation — if a buyer has multiple concurrent briefs with the same agent, construct one client per brief rather than sharing. For HITL flows that can span a process restart mid-task, use ``checkpoint()`` / ``from_checkpoint()`` instead of persisting ``context_id`` alone — full resume state is both ``context_id`` AND ``active_task_id``. Raises ``TypeError`` if passed with a non-A2A protocol. force_a2a_version: A2A-only. Pin the **A2A transport version** (e.g. ``"0.3"``, ``"1.0"``) by filtering the peer's advertised ``supported_interfaces`` to entries whose ``protocol_version`` matches. Not for AdCP protocol pinning — see ``adcp_version`` for that. Intended for tests or for forcing a 0.3-speaking path against a dual-advertising peer. Raises :class:`ADCPConnectionError` on the first call if no advertised interface matches. ``None`` (default) lets the SDK's ``ClientFactory`` pick the most capable transport the peer supports. Use :attr:`a2a_protocol_versions` to probe what a peer advertises before pinning. Raises ``TypeError`` if passed with a non-A2A protocol. adcp_version: AdCP protocol release this client speaks (release-precision string, e.g. ``"3.0"``, ``"3.1"``, ``"3.1-beta"``). Stripe-style per-instance pin: the value is sent as ``adcp_version`` on every outbound request once Stage 3 wires it through the validation hooks; today (Stage 2), it's plumbing only — stored on the instance and exposed via :meth:`get_adcp_version`, with no wire impact yet. ``None`` (default) resolves to the SDK's compile-time pin (``ADCP_VERSION`` packaged with the wheel). Cross-major pins raise :class:`ConfigurationError` at construction; install the SDK major that targets your wire version instead. Patch-precision strings (``"3.0.1"``) and build metadata (``"3.0.1+canary"``) are accepted at construction but normalized to release-precision before wire emission per the spec — patches and build metadata are not part of the negotiation contract. ``get_adcp_version()`` returns the normalized form. Caller-supplied ``adcp_version`` on a per-call params dict wins over the constructor pin: the enricher is the default, not an override. Once Stage 3 threads schema selection through, this becomes a supported per-call override; today it's plumbing-level only. Migration from ``adcp_major_version`` (legacy integer wire field): generated request types still expose ``adcp_major_version: int | None`` from the pre-#3493 schema. Both fields will coexist on the wire through 3.x; servers prefer the new ``adcp_version`` when both are present. Stop populating ``adcp_major_version`` on request models once your seller advertises 3.1 in ``supported_versions``. server_version: AdCP wire shape the *seller* speaks. Most adopters leave this ``None`` — the SDK assumes a v3 seller and the seller's ``/.well-known/agent-card.json`` is the canonical source of truth once a probe-and-cache path lands. Pin explicitly when: * You're talking to a known-legacy seller (e.g. ``server_version="2.5"``). The SDK emits a :class:`DeprecationWarning` at construction — outbound translation is **not** yet wired (Stage 7 full will add it), so a legacy pin today is a signal the SDK acknowledges but cannot act on. Adopters whose sellers still speak pre-3.0 should either upgrade the seller or wait for the inverse-translator release. * You want telemetry to attribute outbound traffic to a specific server-side version regardless of what the seller advertises. Retrieve the current value via :meth:`get_server_version`. """ self._adcp_version: str = resolve_adcp_version(adcp_version) self._server_version: str | None = _resolve_server_version(server_version) self.agent_config = agent_config self.webhook_url_template = webhook_url_template self.webhook_secret = webhook_secret self.on_activity = on_activity self.webhook_timestamp_tolerance = webhook_timestamp_tolerance self.capabilities_ttl = capabilities_ttl self.validate_features = validate_features self.strict_idempotency = strict_idempotency self.signing = signing # Capabilities cache self._capabilities: GetAdcpCapabilitiesResponse | None = None self._feature_resolver: FeatureResolver | None = None self._capabilities_fetched_at: float | None = None self._idempotency_capability_verified: bool = False # Unique per-instance token so use_idempotency_key scopes to this # client and does not bleed to siblings (AdCP #2315 cross-seller risk). from uuid import uuid4 as _uuid4 self._idempotency_client_token: str = _uuid4().hex if force_a2a_version is not None and agent_config.protocol != Protocol.A2A: raise TypeError( f"force_a2a_version is only supported for A2A protocol; " f"got {agent_config.protocol}" ) # Initialize protocol adapter self.adapter: ProtocolAdapter if agent_config.protocol == Protocol.A2A: self.adapter = A2AAdapter(agent_config, force_a2a_version=force_a2a_version) elif agent_config.protocol == Protocol.MCP: self.adapter = MCPAdapter(agent_config) else: raise ValueError(f"Unsupported protocol: {agent_config.protocol}") self.adapter.idempotency_client_token = self._idempotency_client_token if strict_idempotency: self.adapter.idempotency_capability_check = self._ensure_idempotency_capability if signing is not None: self.adapter.signing_request_hook = self._sign_outgoing_request # Apply schema validation modes (default: requests=warn, responses=strict # in dev/test, warn in production — see ``ValidationHookConfig`` docs). self.adapter.configure_validation(validation) # Auto-inject the per-instance ``adcp_version`` pin into every # outbound request envelope. Caller-supplied values on the # request object win — the enricher is the default, not an # override — so per-call overrides remain available once the # generated request types declare the field. _pinned_version = self._adcp_version def _inject_adcp_version(params: dict[str, Any]) -> dict[str, Any]: return {"adcp_version": _pinned_version, **params} self.adapter.envelope_enricher = _inject_adcp_version if context_id: # Empty string is treated as "not provided" — callers using # ``context_id=os.getenv("...") or ""`` patterns shouldn't # silently seed an empty id on the wire. if not isinstance(self.adapter, A2AAdapter): raise TypeError( f"context_id is only supported for A2A protocol; " f"got {agent_config.protocol}" ) self.adapter.set_context_id(context_id) # Initialize simple API accessor (lazy import to avoid circular dependency) from adcp.simple import SimpleAPI self.simple = SimpleAPI(self) def get_adcp_version(self) -> str: """Return the AdCP protocol release this client is pinned to. Resolved at construction from the ``adcp_version`` kwarg, with fallback to the SDK's compile-time pin (``ADCP_VERSION`` packaged with the wheel) when the caller didn't pin explicitly. Same value across the client's lifetime — the pin is per-instance, not per-call. See ``__init__``'s ``adcp_version`` parameter for the full semantics, including the cross-major fence and the Stage 2 vs Stage 3 distinction (today the pin is plumbing only; Stage 3 threads it through schema/validator selection). """ return self._adcp_version def get_server_version(self) -> str | None: """Return the seller's AdCP wire-shape version, or ``None``. ``None`` means the SDK is assuming a current-major seller (the default). Returns a release-precision string (``"3.0"``, ``"3.1"``, ``"2.5"``) when the adopter pinned via the ``server_version`` constructor arg or — once the agent-card probe lands — when the SDK detected the seller's version from its agent-card. See ``__init__``'s ``server_version`` parameter for what legacy pins mean today (signal only; outbound translation ships in Stage 7-full). """ return self._server_version @property def context_id(self) -> str | None: """Current A2A conversation context_id. Reads the context_id currently associated with this client: the value assigned by the A2A server (auto-captured from the most recent response) or the one seeded via the constructor or ``reset_context()``. Returns ``None`` before the first A2A call in a fresh conversation, or for clients on non-A2A protocols — reads are lenient across protocols so generic code can probe ``if client.context_id: ...`` safely. Writes (constructor kwarg, ``reset_context``) raise on non-A2A because the operation has no meaning there. Not safe for concurrent calls on the same client — the adapter mutates this on every response. Rule of thumb: one ADCPClient per A2A conversation. For simple completed-task resume, persist this value and pass it to ``ADCPClient(context_id=...)``. For HITL flows that may restart mid-``input-required``, use ``checkpoint()`` / ``from_checkpoint()`` — full resume state is both this id AND ``active_task_id``. """ if isinstance(self.adapter, A2AAdapter): return self.adapter.context_id return None @property def active_task_id(self) -> str | None: """A2A task_id the next send must echo to resume the same task. Set when the last A2A response was non-terminal (``input-required``, ``working``, ``submitted``, ``auth-required``). The adapter echoes this id on the next outbound message so the server resumes the same task. Clears automatically when the task reaches a terminal state. Full resume state is *both* ``context_id`` and ``active_task_id`` — persist both (or use ``checkpoint()``) to survive a process restart mid-HITL without orphaning the task. Returns ``None`` for non-A2A clients. """ if isinstance(self.adapter, A2AAdapter): return self.adapter.active_task_id return None @property def a2a_protocol_versions(self) -> list[str] | None: """A2A ``protocol_version`` strings the peer advertises, sorted. Lazily populated after the first operation that fetches the peer's ``AgentCard`` (``fetch_capabilities``, ``list_tools``, ``get_agent_info``, or any skill-call). Returns ``None`` before the card has been fetched so callers can distinguish "not yet known" from "peer advertises nothing" (empty list). Returns ``None`` for non-A2A clients. Useful for probing which wire version a peer speaks — buyers running alongside both 0.3-era and 1.0-era agents can use this to confirm what they're talking to. """ if isinstance(self.adapter, A2AAdapter): return self.adapter.a2a_protocol_versions return None def reset_context(self, context_id: str | None = None) -> None: """Start a new A2A conversation on this client. Passing ``None`` (default) clears the current context so the server mints a fresh one on the next call. Passing a string uses it as the new conversation id — useful for resuming a specific prior session or for naming the conversation with your own correlation key. Note: some servers (notably ADK) rewrite client-supplied ids into their own session format; the client auto-adopts the rewritten id on the next response. Also clears any active_task_id — starting a new conversation discards any in-flight task on the old one. Raises ``TypeError`` when called on a non-A2A client. """ if not isinstance(self.adapter, A2AAdapter): raise TypeError( f"reset_context is only supported for A2A protocol; " f"got {self.agent_config.protocol}" ) self.adapter.set_context_id(context_id) def checkpoint(self) -> Checkpoint: """Return the minimal state needed to resume this A2A session. Full resume for HITL / multi-turn flows requires *both* ``context_id`` (which conversation) AND ``active_task_id`` (which in-flight task to echo). Persisting only ``context_id`` reconnects to the right conversation but orphans the pending task server-side — the next send starts a new task under the same context, and the original ``input-required`` task is abandoned. The returned dict also carries ``agent_id`` so a later ``from_checkpoint`` call against a different ``AgentConfig`` fails loudly instead of sending one agent's session ids to another. Pair with ``ADCPClient.from_checkpoint(agent_config, state)``. Returns a fully-populated ``Checkpoint`` on non-A2A clients with ``context_id``/``active_task_id`` set to ``None``, so generic persist-and-restore code can call this without branching on protocol. """ return Checkpoint( agent_id=self.agent_config.id, context_id=self.context_id, active_task_id=self.active_task_id, ) @classmethod def from_checkpoint( cls, agent_config: AgentConfig, state: Checkpoint, **kwargs: Any, ) -> ADCPClient: """Rehydrate an ADCPClient from a prior ``checkpoint()``. Restores both ``context_id`` and ``active_task_id`` so a process restart mid-``input-required`` can resume the same task, not orphan it. Accepts the same keyword arguments as ``__init__`` (signing, strict_idempotency, etc.) — the checkpoint only carries session-resume state; operational config is re-supplied by the caller. Raises ``ValueError`` if the checkpoint's ``agent_id`` doesn't match ``agent_config.id`` — a checkpoint minted for Agent A must not be restored onto Agent B, or the client will leak Agent A's opaque session ids to Agent B on the next message. Raises ``TypeError`` on a non-A2A ``agent_config`` if the checkpoint carries a non-empty ``context_id`` or ``active_task_id`` — session-resume state on a protocol that doesn't support it would be silently dropped, masking bugs. An empty/absent checkpoint round-trips cleanly on any protocol. """ saved_agent_id = state.get("agent_id") if state else None if saved_agent_id and saved_agent_id != agent_config.id: raise ValueError( f"checkpoint was minted for agent {saved_agent_id!r}, " f"cannot restore against {agent_config.id!r}" ) context_id = state.get("context_id") if state else None active_task_id = state.get("active_task_id") if state else None if active_task_id and agent_config.protocol != Protocol.A2A: raise TypeError( f"active_task_id in checkpoint is only supported for A2A " f"protocol; got {agent_config.protocol}" ) client = cls(agent_config, context_id=context_id, **kwargs) if active_task_id and isinstance(client.adapter, A2AAdapter): client.adapter._restore_active_task_id(active_task_id) return client @classmethod def from_mcp_client( cls, client: ClientSession, *, agent_id: str | None = None, validation: ValidationHookConfig | None = None, capabilities_ttl: float = 3600.0, validate_features: bool = False, strict_idempotency: bool = False, ) -> ADCPClient: """Create an ADCPClient wrapping a pre-connected MCP ClientSession. Parity with JS ``AgentClient.fromMCPClient()`` (v5.19.0). The primary use case is compliance test fleets that wire a full ``ADCPClient`` against an in-process MCP server without standing up a loopback HTTP server. Warning: The returned client's ``close()`` and ``async with`` ``__aexit__`` are **no-ops** — the caller owns the injected session and is responsible for closing it. Code that relies on ``async with ADCPClient.from_mcp_client(...) as c:`` to clean up the session will leak the session. Webhook delivery and ``on_activity`` callbacks are **not wired** on the in-process path — there is no HTTP transport for the seller to call back through. Don't pass these to the factory (they're absent from the signature on purpose). If the injected session has not been initialized (``await session.initialize()``), the first tool call surfaces as an opaque MCP protocol error in ``TaskResult.error``. The factory does not initialize for you — verify before calling. **Session lifecycle:** the caller owns the session — ``close()`` and ``async with`` exit on the returned client are no-ops. Use your own ``AsyncExitStack`` to scope both the transport and the client:: import contextlib from mcp import ClientSession from mcp.shared.memory import create_client_server_memory_streams async with contextlib.AsyncExitStack() as stack: (c_read, c_write), (s_read, s_write) = await stack.enter_async_context( create_client_server_memory_streams() ) # wire your in-process server to (s_read, s_write) here session = await stack.enter_async_context( ClientSession(c_read, c_write) ) await session.initialize() # close() is a no-op on injected sessions; no stack.enter_async_context needed. adcp_client = ADCPClient.from_mcp_client(session, agent_id="test-seller") result = await adcp_client.get_products(GetProductsRequest(...)) Note: Request signing is not supported on the injected-session path — the signing hook is wired into the HTTP transport layer that is bypassed here. ``signing=`` is intentionally absent from this factory's parameters. Args: client: A pre-connected ``mcp.ClientSession`` whose ``initialize()`` has already been awaited. agent_id: Identifier for the wrapped agent used in log messages and error objects. Defaults to a unique ``in-process-XXXXXXXX`` token; set this explicitly when running multiple in-process agents concurrently so log lines are distinguishable. validation: Schema-validation modes (same as ``__init__``). strict_idempotency: Verify seller declared idempotency support before each mutating call (same as ``__init__``). validate_features: Gate tool calls on fetched capability declarations (same as ``__init__``). capabilities_ttl: TTL for the capability cache in seconds (same as ``__init__``). Returns: A fully configured ``ADCPClient`` backed by the injected session. """ effective_id = agent_id if agent_id is not None else f"in-process-{uuid4().hex[:8]}" config = AgentConfig( id=effective_id, # RFC 2606 .invalid TLD — passes the http:// validator, guaranteed # not to route to a real host. Self-documenting in error messages. agent_uri="http://in-process.invalid", protocol=Protocol.MCP, ) instance = cls( config, validation=validation, strict_idempotency=strict_idempotency, validate_features=validate_features, capabilities_ttl=capabilities_ttl, ) if not isinstance(instance.adapter, MCPAdapter): raise RuntimeError( # pragma: no cover "from_mcp_client: expected MCPAdapter but got " f"{type(instance.adapter).__name__}" ) instance.adapter._inject_session(client) return instance async def _ensure_idempotency_capability(self) -> None: """Verify the seller positively declares idempotency support in capabilities. Called before every mutating request when ``strict_idempotency=True``. Fetches capabilities on first invocation; subsequent calls are no-ops once the declaration has been observed. Raises ``IdempotencyUnsupportedError`` when ``adcp.idempotency`` is missing, declares ``supported=False`` (seller does not dedupe — naive retry would double-process), or declares ``supported=True`` without a ``replay_ttl_seconds`` window. Sets ``_idempotency_capability_verified = True`` BEFORE calling ``fetch_capabilities`` so any recursive dispatch through the adapter terminates (``get_adcp_capabilities`` is non-mutating, so it would short-circuit anyway — but this guard protects against future refactors that might add it to the mutating set). """ from adcp.exceptions import IdempotencyUnsupportedError if self._idempotency_capability_verified: return self._idempotency_capability_verified = True try: caps = await self.fetch_capabilities() adcp_info = getattr(caps, "adcp", None) idempotency_info = getattr(adcp_info, "idempotency", None) if adcp_info else None if idempotency_info is None: raise IdempotencyUnsupportedError( agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, reason="seller did not declare adcp.idempotency", ) supported = getattr(idempotency_info, "supported", None) if supported is False: raise IdempotencyUnsupportedError( agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, reason="seller declared adcp.idempotency.supported=false", ) ttl = getattr(idempotency_info, "replay_ttl_seconds", None) if ttl is None: raise IdempotencyUnsupportedError( agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, reason=( "seller declared adcp.idempotency.supported=true but omitted " "replay_ttl_seconds" ), ) except Exception: self._idempotency_capability_verified = False raise async def _sign_outgoing_request(self, request: httpx.Request) -> None: """httpx request event hook that attaches RFC 9421 signature headers. Installed on the protocol adapter's httpx client when a ``SigningConfig`` was passed to ``ADCPClient``. Consults the seller's advertised ``request_signing`` capability and signs only the operations the seller listed in ``required_for``, ``warn_for``, or ``supported_for`` — other requests (including the agent-card fetch and ``get_adcp_capabilities`` itself) pass through unsigned. The ``covers_content_digest`` tri-state determines whether the body is bound to the signature. """ if self.signing is None: return operation = _signing_current_operation.get() # Unset ContextVar → out-of-band call (agent-card fetch, session # initialize, etc). Skip without fetching capabilities. # # get_adcp_capabilities → bootstrap carve-out: signing it would # require capabilities we don't have yet, and if a pathological # seller listed this op in its own required_for we'd recurse. # Keep this check narrow — only operations strictly required to # *obtain* capabilities belong here. Today that's just # get_adcp_capabilities. A future adapter that adds another # capabilities-precondition op MUST extend this guard. if operation is None or operation == "get_adcp_capabilities": return caps = await self.fetch_capabilities() req_signing = getattr(caps, "request_signing", None) # Detect and surface a malformed seller config: supported=False is # "signatures are ignored", but populating required_for alongside # it is contradictory. The classifier correctly skips (matches # verifier behavior) but the silent downgrade hides a config bug # that will bite pilots. if ( req_signing is not None and not req_signing.supported and (req_signing.required_for or req_signing.warn_for) ): logger.warning( "Seller %s advertises request_signing.supported=false but " "populates required_for/warn_for — treating as unsupported " "per spec. Verify the seller's capability advertisement.", self.agent_config.id, ) decision = operation_needs_signing(req_signing, operation) if decision == "skip": return covers_policy: str | None = None if req_signing is not None and req_signing.covers_content_digest is not None: covers_policy = req_signing.covers_content_digest.value if covers_policy == "forbidden": cover_digest = False elif covers_policy == "required": cover_digest = True else: # "either" or absent — signer's choice; default stricter. cover_digest = True body = request.content signed = sign_request( method=request.method, url=str(request.url), headers=dict(request.headers), body=body, private_key=self.signing.private_key, key_id=self.signing.key_id, alg=self.signing.alg, cover_content_digest=cover_digest, tag=self.signing.tag, ) # pop-then-set ensures our signed values are authoritative even if # another hook or earlier layer added a same-named header. httpx # headers are a case-insensitive MultiDict, so a naive assignment # could leave a duplicate value in a different case. for header_name, header_value in signed.as_dict().items(): request.headers.pop(header_name, None) request.headers[header_name] = header_value def get_webhook_url(self, task_type: str, operation_id: str) -> str: """Generate webhook URL for a task.""" if not self.webhook_url_template: raise ValueError("webhook_url_template not configured") return self.webhook_url_template.format( agent_id=self.agent_config.id, task_type=task_type, operation_id=operation_id, ) def _emit_activity(self, activity: Activity) -> None: """Emit activity event.""" if self.on_activity: self.on_activity(activity) @contextlib.contextmanager def use_idempotency_key(self, key: str) -> Iterator[str]: """Pin an ``idempotency_key`` for the next mutating call on THIS client. Use when you've persisted a key (e.g., in a buyer-side database) and want the SDK to send that exact key on resume or retry across process restarts. The key is validated against ``^[A-Za-z0-9_.:-]{16,255}$`` on entry; a ``ValueError`` is raised for malformed keys. Scope rules: * **Single-use within scope.** The first mutating call inside the ``with`` block consumes the pinned key; a second mutating call falls through to a fresh UUID. This protects against ``asyncio.gather`` siblings accidentally sharing the key (which would trigger ``IDEMPOTENCY_CONFLICT`` or silently duplicate work). If you need to retry, wrap each attempt in its own ``with`` block. * **Client-scoped.** The pinned key applies only to calls on THIS client. A mutating call on a sibling ``ADCPClient`` inside the same ``with`` block generates a fresh key and emits a ``UserWarning`` — keys must be unique per (seller, request) pair (AdCP #2315). * **No nesting.** Nested ``use_idempotency_key`` on the same client raises ``RuntimeError``. Example:: with client.use_idempotency_key(campaign.stored_key): result = await client.create_media_buy(request) """ from adcp import _idempotency _idempotency.validate_key(key) token = self._idempotency_client_token if token in _idempotency._scoped_keys: raise RuntimeError( "use_idempotency_key is already active on this client; " "nested usage is not supported." ) _idempotency._scoped_keys[token] = key try: yield key finally: _idempotency._scoped_keys.pop(token, None) # ======================================================================== # Capability Validation # ======================================================================== @property def capabilities(self) -> GetAdcpCapabilitiesResponse | None: """Return cached capabilities, or None if not yet fetched.""" return self._capabilities @property def feature_resolver(self) -> FeatureResolver | None: """Return the FeatureResolver for cached capabilities, or None.""" return self._feature_resolver async def fetch_capabilities(self) -> GetAdcpCapabilitiesResponse: """Fetch capabilities, using cache if still valid. Returns: The seller's capabilities response. """ if self._capabilities is not None and self._capabilities_fetched_at is not None: elapsed = time.monotonic() - self._capabilities_fetched_at if elapsed < self.capabilities_ttl: return self._capabilities return await self.refresh_capabilities() async def refresh_capabilities(self) -> GetAdcpCapabilitiesResponse: """Fetch capabilities from the seller, bypassing cache. On strict-schema validation failure the raw response is inspected with ``looks_like_v3_capabilities``: if the agent is structurally v3-shaped, a wire-shape bug is surfaced loudly with the original validation error rather than silently downgrading to v2 (the v2 fallback would then ask for v2.5 schemas, which aren't shipped — one missing field would cascade into "AdCP schema data for version v2.5 not found"). Genuinely non-v3 responses still fall through to the transport-error path. Returns: The seller's capabilities response. Raises: ADCPError: On transport failure, or when the response is v3-shaped but fails schema validation. The error message explicitly references v3 in the latter case so the underlying wire-shape bug doesn't get blamed on a v2.5-schema cascade. """ result = await self.get_adcp_capabilities(GetAdcpCapabilitiesRequest()) if result.success and result.data is not None: self._capabilities = result.data self._feature_resolver = FeatureResolver(result.data) self._capabilities_fetched_at = time.monotonic() return self._capabilities # The typed call discards the raw payload on parse failure (only the # error string survives). Distinguish parse-failure (worth shape- # checking) from transport-failure (no data ever arrived) by the # error prefix produced by ProtocolAdapter._parse_response. Only on # parse-failure do we re-fetch the raw dict from the adapter to # inspect its shape; transport failures fall straight through to # the original error path. raw_data: Any = None is_parse_failure = result.error is not None and result.error.startswith( "Failed to parse response:" ) if is_parse_failure: raw_result = await self.adapter.get_adcp_capabilities( GetAdcpCapabilitiesRequest().model_dump(mode="json", exclude_none=True) ) raw_data = raw_result.data if isinstance(raw_data, list) and len(raw_data) == 1 and isinstance(raw_data[0], dict): # MCP content array — unwrap a single-item content envelope # so the heuristic sees the same shape the parser would. raw_data = raw_data[0] if looks_like_v3_capabilities(raw_data): logger.warning( "[AdCP] Agent %r returned a get_adcp_capabilities response that " "failed validation, but the response is structurally v3-shaped. " "The agent has a wire-shape bug — that's the thing to fix. " "(has_error=%s, has_data=%s)", self.agent_config.id, bool(result.error), raw_data is not None, ) raise ADCPError( f"v3 capabilities response from agent {self.agent_config.id!r} " f"failed schema validation: {result.error or result.message}. " f"The response is structurally v3-shaped (carries `adcp`, " f"`supported_protocols`, or a v3 protocol block) — fix the " f"agent's wire shape rather than downgrading to v2.", agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, ) raise ADCPError( f"Failed to fetch capabilities: {result.error or result.message}", agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, ) def _ensure_resolver(self) -> FeatureResolver: """Return the FeatureResolver, raising if capabilities haven't been fetched.""" if self._feature_resolver is None: raise ADCPError( "Cannot check feature support: capabilities have not been fetched. " "Call fetch_capabilities() first.", agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, ) return self._feature_resolver def supports(self, feature: str) -> bool: """Check if the seller supports a feature. Supports multiple feature namespaces: - Protocol support: ``supports("media_buy")`` checks ``supported_protocols`` - Extension support: ``supports("ext:scope3")`` checks ``extensions_supported`` - Targeting: ``supports("targeting.geo_countries")`` checks ``media_buy.execution.targeting`` - Media buy features: ``supports("audience_targeting")`` checks ``media_buy.features`` - Signals features: ``supports("catalog_signals")`` checks ``signals.features`` Args: feature: Feature identifier to check. Returns: True if the seller declares the feature as supported. Raises: ADCPError: If capabilities have not been fetched yet. """ return self._ensure_resolver().supports(feature) def require(self, *features: str) -> None: """Assert that the seller supports all listed features. Args: *features: Feature identifiers to require. Raises: ADCPFeatureUnsupportedError: If any features are not supported. ADCPError: If capabilities have not been fetched yet. """ self._ensure_resolver().require( *features, agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, ) def _validate_task_features(self, task_name: str) -> None: """Check feature requirements for a task if validate_features is enabled. Returns without checking if validate_features is False or capabilities haven't been fetched yet (logs a warning in the latter case). """ if not self.validate_features: return if self._feature_resolver is None: logger.warning( "validate_features is enabled but capabilities have not been fetched. " "Call fetch_capabilities() to enable feature validation." ) return required_feature = TASK_FEATURE_MAP.get(task_name) if required_feature is None: return self.require(required_feature) async def get_products( self, request: GetProductsRequest, fetch_previews: bool = False, preview_output_format: str = "url", creative_agent_client: ADCPClient | None = None, ) -> TaskResult[GetProductsResponse]: """ Get advertising products. Args: request: Request parameters fetch_previews: If True, generate preview URLs for each product's formats (uses batch API for 5-10x performance improvement) preview_output_format: "url" for iframe URLs (default), "html" for direct embedding (2-3x faster, no iframe overhead) creative_agent_client: Client for creative agent (required if fetch_previews=True) Returns: TaskResult containing GetProductsResponse with optional preview URLs in metadata Raises: ValueError: If fetch_previews=True but creative_agent_client is not provided """ if fetch_previews and not creative_agent_client: raise ValueError("creative_agent_client is required when fetch_previews=True") operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_products", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_products(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_products", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) result: TaskResult[GetProductsResponse] = self.adapter._parse_response( raw_result, GetProductsResponse ) if ( fetch_previews and result.success and result.data and result.data.products and creative_agent_client ): from adcp.utils.preview_cache import add_preview_urls_to_products products_with_previews = await add_preview_urls_to_products( result.data.products, creative_agent_client, use_batch=True, output_format=preview_output_format, ) result.metadata = result.metadata or {} result.metadata["products_with_previews"] = products_with_previews return result async def list_creative_formats( self, request: ListCreativeFormatsRequest, fetch_previews: bool = False, preview_output_format: str = "url", ) -> TaskResult[ListCreativeFormatsResponse]: """ List supported creative formats. Args: request: Request parameters fetch_previews: If True, generate preview URLs for each format using sample manifests (uses batch API for 5-10x performance improvement) preview_output_format: "url" for iframe URLs (default), "html" for direct embedding (2-3x faster, no iframe overhead) Returns: TaskResult containing ListCreativeFormatsResponse with optional preview URLs in metadata """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_creative_formats", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_creative_formats(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_creative_formats", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) result: TaskResult[ListCreativeFormatsResponse] = self.adapter._parse_response( raw_result, ListCreativeFormatsResponse ) if fetch_previews and result.success and result.data: from adcp.utils.preview_cache import add_preview_urls_to_formats formats_with_previews = await add_preview_urls_to_formats( result.data.formats, self, use_batch=True, output_format=preview_output_format, ) result.metadata = result.metadata or {} result.metadata["formats_with_previews"] = formats_with_previews return result async def preview_creative( self, request: PreviewCreativeRequest, ) -> TaskResult[PreviewCreativeResponse]: """ Generate preview of a creative manifest. Args: request: Request parameters Returns: TaskResult containing PreviewCreativeResponse with preview URLs """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="preview_creative", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.preview_creative(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="preview_creative", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, PreviewCreativeResponse) async def sync_creatives( self, request: SyncCreativesRequest, ) -> TaskResult[SyncCreativesResponse]: """ Sync Creatives. Args: request: Request parameters Returns: TaskResult containing SyncCreativesResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_creatives", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_creatives(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_creatives", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncCreativesResponse) async def list_creatives( self, request: ListCreativesRequest, ) -> TaskResult[ListCreativesResponse]: """ List Creatives. Args: request: Request parameters Returns: TaskResult containing ListCreativesResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_creatives", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_creatives(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_creatives", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListCreativesResponse) async def get_media_buy_delivery( self, request: GetMediaBuyDeliveryRequest, ) -> TaskResult[GetMediaBuyDeliveryResponse]: """ Get Media Buy Delivery. Args: request: Request parameters Returns: TaskResult containing GetMediaBuyDeliveryResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buy_delivery", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_media_buy_delivery(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buy_delivery", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetMediaBuyDeliveryResponse) async def get_media_buys( self, request: GetMediaBuysRequest, ) -> TaskResult[GetMediaBuysResponse]: """ Get Media Buys. Args: request: Request parameters Returns: TaskResult containing GetMediaBuysResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) if params.get("include_webhook_activity") is False: params.pop("include_webhook_activity") if params.get("webhook_activity_limit") == 50: params.pop("webhook_activity_limit") self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buys", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_media_buys(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buys", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetMediaBuysResponse) async def get_signals( self, request: GetSignalsRequest, ) -> TaskResult[GetSignalsResponse]: """ Get Signals. Args: request: Request parameters Returns: TaskResult containing GetSignalsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_signals", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_signals(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_signals", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetSignalsResponse) async def activate_signal( self, request: ActivateSignalRequest, ) -> TaskResult[ActivateSignalResponse]: """ Activate Signal. Args: request: Request parameters Returns: TaskResult containing ActivateSignalResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="activate_signal", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.activate_signal(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="activate_signal", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ActivateSignalResponse) async def provide_performance_feedback( self, request: ProvidePerformanceFeedbackRequest, ) -> TaskResult[ProvidePerformanceFeedbackResponse]: """ Provide Performance Feedback. Args: request: Request parameters Returns: TaskResult containing ProvidePerformanceFeedbackResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="provide_performance_feedback", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.provide_performance_feedback(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="provide_performance_feedback", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ProvidePerformanceFeedbackResponse) async def create_media_buy( self, request: CreateMediaBuyRequest, ) -> TaskResult[CreateMediaBuyResponse]: """ Create a new media buy reservation. Requests the agent to reserve inventory for a campaign. The agent returns a media_buy_id that tracks this reservation and can be used for updates. Args: request: Media buy creation parameters including: - brand: Brand reference; resolved from brand.json or the registry at execution - packages: List of package requests specifying desired inventory - publisher_properties: Target properties for ad placement - budget: Optional budget constraints - start_date/end_date: Campaign flight dates Returns: TaskResult containing CreateMediaBuyResponse with: - media_buy_id: Unique identifier for this reservation - status: Current state of the media buy - packages: Confirmed package details - Additional platform-specific metadata Example: >>> from adcp import ADCPClient, CreateMediaBuyRequest, BrandReference >>> client = ADCPClient(agent_config) >>> request = CreateMediaBuyRequest( ... brand=BrandReference(domain="acme.com"), ... packages=[package_request], ... publisher_properties=properties, ... ) >>> result = await client.create_media_buy(request) >>> if result.success: ... media_buy_id = result.data.media_buy_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_media_buy", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.create_media_buy(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_media_buy", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CreateMediaBuyResponse) async def update_media_buy( self, request: UpdateMediaBuyRequest, ) -> TaskResult[UpdateMediaBuyResponse]: """ Update an existing media buy reservation. Modifies a previously created media buy by updating packages or publisher properties. The update operation uses discriminated unions to specify what to change - either package details or targeting properties. Args: request: Media buy update parameters including: - media_buy_id: Identifier from create_media_buy response - updates: Discriminated union specifying update type: * UpdateMediaBuyPackagesRequest: Modify package selections * UpdateMediaBuyPropertiesRequest: Change targeting properties Returns: TaskResult containing UpdateMediaBuyResponse with: - media_buy_id: The updated media buy identifier - status: Updated state of the media buy - packages: Updated package configurations - Additional platform-specific metadata Example: >>> from adcp import ADCPClient, UpdateMediaBuyPackagesRequest >>> client = ADCPClient(agent_config) >>> request = UpdateMediaBuyPackagesRequest( ... media_buy_id="mb_123", ... packages=[updated_package] ... ) >>> result = await client.update_media_buy(request) >>> if result.success: ... updated_packages = result.data.packages """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_media_buy", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_media_buy(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_media_buy", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdateMediaBuyResponse) async def build_creative( self, request: BuildCreativeRequest, ) -> TaskResult[BuildCreativeResponse]: """ Generate production-ready creative assets. Requests the creative agent to build final deliverable assets in the target format (e.g., VAST, DAAST, HTML5). This is typically called after previewing and approving a creative manifest. Args: request: Creative build parameters including: - manifest: Creative manifest with brand info and content - target_format_id: Desired output format identifier - inputs: Optional user-provided inputs for template variables - deployment: Platform or agent deployment configuration Returns: TaskResult containing BuildCreativeResponse with: - assets: Production-ready creative files (URLs or inline content) - format_id: The generated format identifier - manifest: The creative manifest used for generation - metadata: Additional platform-specific details Example: >>> from adcp import ADCPClient, BuildCreativeRequest >>> client = ADCPClient(agent_config) >>> request = BuildCreativeRequest( ... manifest=creative_manifest, ... target_format_id="vast_2.0", ... inputs={"duration": 30} ... ) >>> result = await client.build_creative(request) >>> if result.success: ... vast_url = result.data.assets[0].url """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="build_creative", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.build_creative(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="build_creative", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, BuildCreativeResponse) async def list_accounts( self, request: ListAccountsRequest, ) -> TaskResult[ListAccountsResponse]: """ List Accounts. Args: request: Request parameters Returns: TaskResult containing ListAccountsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_accounts", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_accounts(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_accounts", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListAccountsResponse) async def sync_accounts( self, request: SyncAccountsRequest, ) -> TaskResult[SyncAccountsResponse]: """ Sync Accounts. Args: request: Request parameters Returns: TaskResult containing SyncAccountsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_accounts", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_accounts(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_accounts", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncAccountsResponse) async def get_account_financials( self, request: GetAccountFinancialsRequest, ) -> TaskResult[GetAccountFinancialsResponse]: """ Get Account Financials. Args: request: Request parameters Returns: TaskResult containing GetAccountFinancialsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_account_financials", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_account_financials(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_account_financials", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetAccountFinancialsResponse) async def report_usage( self, request: ReportUsageRequest, ) -> TaskResult[ReportUsageResponse]: """ Report Usage. Args: request: Request parameters Returns: TaskResult containing ReportUsageResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="report_usage", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.report_usage(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="report_usage", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ReportUsageResponse) async def log_event( self, request: LogEventRequest, ) -> TaskResult[LogEventResponse]: """ Log Event. Args: request: Request parameters Returns: TaskResult containing LogEventResponse """ self._validate_task_features("log_event") operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="log_event", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.log_event(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="log_event", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, LogEventResponse) async def sync_event_sources( self, request: SyncEventSourcesRequest, ) -> TaskResult[SyncEventSourcesResponse]: """ Sync Event Sources. Args: request: Request parameters Returns: TaskResult containing SyncEventSourcesResponse """ self._validate_task_features("sync_event_sources") operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_event_sources", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_event_sources(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_event_sources", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncEventSourcesResponse) async def sync_audiences( self, request: SyncAudiencesRequest, ) -> TaskResult[SyncAudiencesResponse]: """ Sync Audiences. Args: request: Request parameters Returns: TaskResult containing SyncAudiencesResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_audiences", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_audiences(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_audiences", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncAudiencesResponse) async def sync_catalogs( self, request: SyncCatalogsRequest, ) -> TaskResult[SyncCatalogsResponse]: """ Sync Catalogs. Args: request: Request parameters Returns: TaskResult containing SyncCatalogsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_catalogs", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_catalogs(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_catalogs", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncCatalogsResponse) async def get_creative_delivery( self, request: GetCreativeDeliveryRequest, ) -> TaskResult[GetCreativeDeliveryResponse]: """ Get Creative Delivery. Args: request: Request parameters Returns: TaskResult containing GetCreativeDeliveryResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_creative_delivery", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_creative_delivery(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_creative_delivery", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetCreativeDeliveryResponse) async def list_transformers( self, request: ListTransformersRequest, ) -> TaskResult[ListTransformersResponse]: """ List Creative Transformers. Args: request: Request parameters Returns: TaskResult containing ListTransformersResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_transformers", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_transformers(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_transformers", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListTransformersResponse) # ======================================================================== # V3 Protocol Methods - Protocol Discovery # ======================================================================== async def get_adcp_capabilities( self, request: GetAdcpCapabilitiesRequest, ) -> TaskResult[GetAdcpCapabilitiesResponse]: """ Get AdCP capabilities from the agent. Queries the agent's supported AdCP features, protocol versions, and domain-specific capabilities (media_buy, signals, sponsored_intelligence). Args: request: Request parameters including optional protocol filters Returns: TaskResult containing GetAdcpCapabilitiesResponse with: - adcp: Core protocol version information - supported_protocols: List of supported domain protocols - media_buy: Media buy capabilities (if supported) - sponsored_intelligence: SI capabilities (if supported) - signals: Signals capabilities (if supported) """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_adcp_capabilities", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_adcp_capabilities(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_adcp_capabilities", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetAdcpCapabilitiesResponse) async def get_task_status( self, request: GetTaskStatusRequest, ) -> TaskResult[GetTaskStatusResponse]: """ Get Task Status. Args: request: Request parameters Returns: TaskResult containing GetTaskStatusResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_task_status", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_task_status(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_task_status", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetTaskStatusResponse) async def list_tasks( self, request: ListTasksRequest, ) -> TaskResult[ListTasksResponse]: """ List Tasks. Args: request: Request parameters Returns: TaskResult containing ListTasksResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_tasks", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_tasks(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_tasks", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListTasksResponse) # ======================================================================== # V3 Protocol Methods - Content Standards # ======================================================================== async def create_content_standards( self, request: CreateContentStandardsRequest, ) -> TaskResult[CreateContentStandardsResponse]: """ Create a new content standards configuration. Defines acceptable content contexts for ad placement using natural language policy and optional calibration exemplars. Args: request: Request parameters including policy and scope Returns: TaskResult containing CreateContentStandardsResponse with standards_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_content_standards", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.create_content_standards(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_content_standards", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CreateContentStandardsResponse) async def get_content_standards( self, request: GetContentStandardsRequest, ) -> TaskResult[GetContentStandardsResponse]: """ Get a content standards configuration by ID. Args: request: Request parameters including standards_id Returns: TaskResult containing GetContentStandardsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_content_standards", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_content_standards(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_content_standards", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetContentStandardsResponse) async def list_content_standards( self, request: ListContentStandardsRequest, ) -> TaskResult[ListContentStandardsResponse]: """ List content standards configurations. Args: request: Request parameters including optional filters Returns: TaskResult containing ListContentStandardsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_content_standards", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_content_standards(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_content_standards", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListContentStandardsResponse) async def update_content_standards( self, request: UpdateContentStandardsRequest, ) -> TaskResult[UpdateContentStandardsResponse]: """ Update a content standards configuration. Args: request: Request parameters including standards_id and updates Returns: TaskResult containing UpdateContentStandardsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_content_standards", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_content_standards(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_content_standards", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdateContentStandardsResponse) async def calibrate_content( self, request: CalibrateContentRequest, ) -> TaskResult[CalibrateContentResponse]: """ Calibrate content against standards. Evaluates content (artifact or URL) against configured standards to determine suitability for ad placement. Args: request: Request parameters including content to evaluate Returns: TaskResult containing CalibrateContentResponse with verdict """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="calibrate_content", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.calibrate_content(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="calibrate_content", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CalibrateContentResponse) async def validate_content_delivery( self, request: ValidateContentDeliveryRequest, ) -> TaskResult[ValidateContentDeliveryResponse]: """ Validate content delivery against standards. Validates that ad delivery records comply with content standards. Args: request: Request parameters including delivery records Returns: TaskResult containing ValidateContentDeliveryResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="validate_content_delivery", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.validate_content_delivery(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="validate_content_delivery", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ValidateContentDeliveryResponse) async def get_media_buy_artifacts( self, request: GetMediaBuyArtifactsRequest, ) -> TaskResult[GetMediaBuyArtifactsResponse]: """ Get artifacts associated with a media buy. Retrieves content artifacts where ads were delivered for a media buy. Args: request: Request parameters including media_buy_id Returns: TaskResult containing GetMediaBuyArtifactsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buy_artifacts", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_media_buy_artifacts(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buy_artifacts", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetMediaBuyArtifactsResponse) # ======================================================================== # V3 Protocol Methods - Sponsored Intelligence # ======================================================================== async def si_get_offering( self, request: SiGetOfferingRequest, ) -> TaskResult[SiGetOfferingResponse]: """ Get sponsored intelligence offering. Retrieves product/service offerings that can be presented in a sponsored intelligence session. Args: request: Request parameters including brand context Returns: TaskResult containing SiGetOfferingResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_get_offering", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.si_get_offering(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_get_offering", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SiGetOfferingResponse) async def si_initiate_session( self, request: SiInitiateSessionRequest, ) -> TaskResult[SiInitiateSessionResponse]: """ Initiate a sponsored intelligence session. Starts a conversational brand experience session with a user. Args: request: Request parameters including identity and context Returns: TaskResult containing SiInitiateSessionResponse with session_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_initiate_session", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.si_initiate_session(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_initiate_session", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SiInitiateSessionResponse) async def si_send_message( self, request: SiSendMessageRequest, ) -> TaskResult[SiSendMessageResponse]: """ Send a message in a sponsored intelligence session. Continues the conversation in an active SI session. Args: request: Request parameters including session_id and message Returns: TaskResult containing SiSendMessageResponse with brand response """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_send_message", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.si_send_message(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_send_message", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SiSendMessageResponse) async def si_terminate_session( self, request: SiTerminateSessionRequest, ) -> TaskResult[SiTerminateSessionResponse]: """ Terminate a sponsored intelligence session. Ends an active SI session, optionally with follow-up actions. Args: request: Request parameters including session_id and termination context Returns: TaskResult containing SiTerminateSessionResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_terminate_session", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.si_terminate_session(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_terminate_session", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SiTerminateSessionResponse) # ======================================================================== # V3 Governance Methods # ======================================================================== async def get_creative_features( self, request: GetCreativeFeaturesRequest, ) -> TaskResult[GetCreativeFeaturesResponse]: """Evaluate governance features for a creative manifest.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_creative_features", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_creative_features(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_creative_features", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetCreativeFeaturesResponse) async def sync_plans( self, request: SyncPlansRequest, ) -> TaskResult[SyncPlansResponse]: """Sync campaign governance plans to the governance agent.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_plans", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_plans(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_plans", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncPlansResponse) async def check_governance( self, request: CheckGovernanceRequest, ) -> TaskResult[CheckGovernanceResponse]: """Check a proposed or committed action against campaign governance.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="check_governance", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.check_governance(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="check_governance", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CheckGovernanceResponse) async def report_plan_outcome( self, request: ReportPlanOutcomeRequest, ) -> TaskResult[ReportPlanOutcomeResponse]: """Report the outcome of a governed action to the governance agent.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="report_plan_outcome", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.report_plan_outcome(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="report_plan_outcome", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ReportPlanOutcomeResponse) async def get_plan_audit_logs( self, request: GetPlanAuditLogsRequest, ) -> TaskResult[GetPlanAuditLogsResponse]: """Retrieve governance state and audit logs for one or more plans.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_plan_audit_logs", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_plan_audit_logs(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_plan_audit_logs", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetPlanAuditLogsResponse) async def create_property_list( self, request: CreatePropertyListRequest, ) -> TaskResult[CreatePropertyListResponse]: """ Create a property list for governance filtering. Property lists define dynamic sets of properties based on filters, brand manifests, and feature requirements. Args: request: Request parameters for creating the property list Returns: TaskResult containing CreatePropertyListResponse with list_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_property_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.create_property_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_property_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CreatePropertyListResponse) async def get_property_list( self, request: GetPropertyListRequest, ) -> TaskResult[GetPropertyListResponse]: """ Get a property list with optional resolution. When resolve=true, returns the list of resolved property identifiers. Use this to get the actual properties that match the list's filters. Args: request: Request parameters including list_id and resolve flag Returns: TaskResult containing GetPropertyListResponse with identifiers """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_property_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_property_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_property_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetPropertyListResponse) async def list_property_lists( self, request: ListPropertyListsRequest, ) -> TaskResult[ListPropertyListsResponse]: """ List property lists owned by a principal. Retrieves metadata for all property lists, optionally filtered by principal or pagination parameters. Args: request: Request parameters with optional filtering Returns: TaskResult containing ListPropertyListsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_property_lists", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_property_lists(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_property_lists", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListPropertyListsResponse) async def update_property_list( self, request: UpdatePropertyListRequest, ) -> TaskResult[UpdatePropertyListResponse]: """ Update a property list. Modifies the filters, brand manifest, or other parameters of an existing property list. Args: request: Request parameters with list_id and updates Returns: TaskResult containing UpdatePropertyListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_property_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_property_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_property_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdatePropertyListResponse) async def delete_property_list( self, request: DeletePropertyListRequest, ) -> TaskResult[DeletePropertyListResponse]: """ Delete a property list. Removes a property list. Any active subscriptions to this list will be terminated. Args: request: Request parameters with list_id Returns: TaskResult containing DeletePropertyListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="delete_property_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.delete_property_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="delete_property_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, DeletePropertyListResponse) # ======================================================================== # V3 Protocol Methods - Governance (Collection Lists) # ======================================================================== async def create_collection_list( self, request: CreateCollectionListRequest, ) -> TaskResult[CreateCollectionListResponse]: """Create a collection list for governance filtering. Collection lists define dynamic sets of collections (properties, segments, etc.) that can be referenced by authorization rules and audience scoping. Args: request: Request parameters for creating the collection list Returns: TaskResult containing CreateCollectionListResponse with list_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_collection_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.create_collection_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_collection_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CreateCollectionListResponse) async def get_collection_list( self, request: GetCollectionListRequest, ) -> TaskResult[GetCollectionListResponse]: """Get a collection list with optional resolution. When resolve=true, returns the resolved members of the collection list. Args: request: Request parameters including list_id and resolve flag Returns: TaskResult containing GetCollectionListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_collection_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_collection_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_collection_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetCollectionListResponse) async def list_collection_lists( self, request: ListCollectionListsRequest, ) -> TaskResult[ListCollectionListsResponse]: """List collection lists owned by a principal. Args: request: Request parameters with optional filtering Returns: TaskResult containing ListCollectionListsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_collection_lists", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_collection_lists(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_collection_lists", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListCollectionListsResponse) async def update_collection_list( self, request: UpdateCollectionListRequest, ) -> TaskResult[UpdateCollectionListResponse]: """Update a collection list. Args: request: Request parameters with list_id and updates Returns: TaskResult containing UpdateCollectionListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_collection_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_collection_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_collection_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdateCollectionListResponse) async def delete_collection_list( self, request: DeleteCollectionListRequest, ) -> TaskResult[DeleteCollectionListResponse]: """Delete a collection list. Args: request: Request parameters with list_id Returns: TaskResult containing DeleteCollectionListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="delete_collection_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.delete_collection_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="delete_collection_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, DeleteCollectionListResponse) # ======================================================================== # V3 Protocol Methods - Governance (Sync Governance) # ======================================================================== async def sync_governance( self, request: SyncGovernanceRequest, ) -> TaskResult[SyncGovernanceResponse]: """Sync governance agents attached to an account. Attach, detach, or replace the set of governance agents that must be consulted for plan approval on an account. Args: request: Request parameters with account and governance agents Returns: TaskResult containing SyncGovernanceResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_governance", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_governance(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_governance", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncGovernanceResponse) # ======================================================================== # V3 Protocol Methods - Temporal Matching Protocol (TMP) # ======================================================================== async def context_match( self, request: ContextMatchRequest, ) -> TaskResult[ContextMatchResponse]: """Match ad context to buyer packages. Evaluates contextual signals for a publisher placement against the buyer's active packages and returns matching offers. Args: request: Context match request with placement, property, and optional artifact refs, context signals, and geo data. Returns: TaskResult containing ContextMatchResponse with offers. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True, by_alias=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="context_match", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.context_match(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="context_match", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ContextMatchResponse) async def identity_match( self, request: IdentityMatchRequest, ) -> TaskResult[IdentityMatchResponse]: """Match user identity for package eligibility. Evaluates a user identity token against all active packages for frequency capping and personalization. Args: request: Identity match request with user_token, uid_type, and package_ids. Returns: TaskResult containing IdentityMatchResponse with eligible_package_ids. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True, by_alias=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="identity_match", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.identity_match(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="identity_match", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, IdentityMatchResponse) # ======================================================================== # V3 Protocol Methods - Brand Rights # ======================================================================== async def get_brand_identity( self, request: GetBrandIdentityRequest, ) -> TaskResult[GetBrandIdentityResponse]: """Get brand identity information. Retrieves brand identity data including logos, colors, fonts, voice synthesis config, and rights availability. Args: request: Request with brand_id and optional fields filter. Returns: TaskResult containing GetBrandIdentityResponse. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_brand_identity", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_brand_identity(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_brand_identity", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetBrandIdentityResponse) async def get_rights( self, request: GetRightsRequest, ) -> TaskResult[GetRightsResponse]: """Get available rights for licensing. Searches for rights offerings using natural language query and filters by type, uses, countries, and buyer compatibility. Args: request: Request with query, uses, and optional filters. Returns: TaskResult containing GetRightsResponse with matched rights. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_rights", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_rights(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_rights", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetRightsResponse) async def acquire_rights( self, request: AcquireRightsRequest, ) -> TaskResult[AcquireRightsResponse]: """Acquire rights for brand content usage. Binding contractual request to license rights for a campaign. Returns credentials for generating rights-cleared content. Args: request: Request with rights_id, pricing_option_id, buyer, campaign, and revocation_webhook. Returns: TaskResult containing AcquireRightsResponse (acquired, pending_approval, rejected, or error). """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="acquire_rights", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.acquire_rights(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="acquire_rights", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, AcquireRightsResponse) async def update_rights( self, request: UpdateRightsRequest, ) -> TaskResult[UpdateRightsResponse]: """Update terms of an existing rights acquisition. Modifies a previously acquired rights record — typically to extend the ``end_date``, raise the ``impression_cap``, pause/unpause via ``paused``, or swap to a compatible ``pricing_option_id``. Partial update: pass only the fields you want to change. Failure modes (surface as ``TaskResult`` with ``success=False``): * Acquisition is expired or revoked — the seller rejects the update outright; mint a fresh ``acquire_rights`` instead. * ``pricing_option_id`` swap to an incompatible option — rejected; the new option's terms must be a strict superset / compatible with the original acquisition. * No partial-state mutations on rejection: the acquisition remains at its prior state when any field fails validation. Args: request: Request with ``rights_id`` and at least one mutable field (``end_date``, ``impression_cap``, ``paused``, or ``pricing_option_id``). Returns: TaskResult containing UpdateRightsResponse (updated or error). """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_rights", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_rights(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_rights", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdateRightsResponse) async def validate_input(self, request: Any) -> TaskResult[Any]: """Validate creative input against a format declaration.""" from adcp.types import _generated as gen params = request.model_dump(mode="json", exclude_none=True) raw_result = await self.adapter.validate_input(params) return self.adapter._parse_response(raw_result, gen.ValidateInputResponse) async def verify_brand_claim(self, request: Any) -> TaskResult[Any]: """Verify a single brand claim.""" from adcp.types import _generated as gen params = request.model_dump(mode="json", exclude_none=True) raw_result = await self.adapter.verify_brand_claim(params) return self.adapter._parse_response(raw_result, gen.VerifyBrandClaimResponse) async def verify_brand_claims(self, request: Any) -> TaskResult[Any]: """Verify multiple brand claims.""" from adcp.types import _generated as gen params = request.model_dump(mode="json", exclude_none=True) raw_result = await self.adapter.verify_brand_claims(params) return self.adapter._parse_response(raw_result, gen.VerifyBrandClaimsResponseBulk) # ======================================================================== # V3 Protocol Methods - Compliance # ======================================================================== async def comply_test_controller( self, request: ComplyTestControllerRequest, ) -> TaskResult[ComplyTestControllerResponse]: """Compliance test controller for sandbox testing. Enables sellers to simulate state transitions and delivery data in a sandbox environment for compliance testing. Args: request: Request specifying scenario and parameters. Returns: TaskResult containing ComplyTestControllerResponse. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="comply_test_controller", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.comply_test_controller(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="comply_test_controller", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ComplyTestControllerResponse) async def list_tools(self) -> list[str]: """ List available tools from the agent. Returns: List of tool names """ return await self.adapter.list_tools() async def get_info(self) -> dict[str, Any]: """ Get agent information including AdCP extension metadata. Returns agent card information including: - Agent name, description, version - Protocol type (mcp or a2a) - AdCP version (from extensions.adcp.adcp_version) - Supported protocols (from extensions.adcp.protocols_supported) - Available tools/skills Returns: Dictionary with agent metadata """ return await self.adapter.get_agent_info() async def close(self) -> None: """Close the adapter and clean up resources.""" if hasattr(self.adapter, "close"): logger.debug(f"Closing adapter for agent {self.agent_config.id}") await self.adapter.close() async def close_mcp_session(self, session_id: str | None = None) -> None: """Explicitly terminate a stateful MCP Streamable HTTP session. This sends ``DELETE`` to the configured MCP endpoint with the ``Mcp-Session-Id`` header. When ``session_id`` is omitted, the SDK-managed current session is closed. It is only valid for MCP agents using ``mcp_transport="streamable_http"``. """ if not isinstance(self.adapter, MCPAdapter): raise TypeError( "close_mcp_session is only supported for MCP clients; " f"got {self.agent_config.protocol}" ) await self.adapter.close_mcp_session(session_id) async def __aenter__(self) -> ADCPClient: """Async context manager entry.""" return self async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: """Async context manager exit.""" await self.close() def _verify_webhook_signature( self, payload: dict[str, Any], signature: str, timestamp: str, raw_body: bytes | str | None = None, ) -> bool: """ Verify HMAC-SHA256 signature of webhook payload. The verification algorithm matches get_adcp_signed_headers_for_webhook: 1. Constructs message as "{timestamp}.{raw_http_body_bytes}" 2. HMAC-SHA256 signs with the shared secret 3. Compares against the provided signature (with "sha256=" prefix stripped) using constant-time comparison. Per AdCP spec (adcontextprotocol/adcp#2478): verifiers MUST use the raw HTTP body bytes captured before any JSON parse; they SHOULD NOT re-serialize a parsed payload to reconstruct the signed bytes, because re-serialization silently fails against signers whose output differs in separator choice, key order, unicode escapes, or number formatting — masking signer bugs the verifier should surface. Callers that genuinely cannot capture raw bytes MUST fail closed. This implementation therefore rejects verification attempts that don't supply ``raw_body``. Capture it from your framework's pre-parse hook (FastAPI ``Request.body()``, Flask ``request.get_data(cache=True)``, aiohttp ``Request.read()``, Express ``express.raw()``). Args: payload: Parsed webhook payload dict (not used for signing; kept for signature parity with callers, but verification derives solely from ``raw_body``). signature: Signature to verify (with or without "sha256=" prefix) timestamp: Unix timestamp in seconds from X-AdCP-Timestamp header raw_body: Raw HTTP request body bytes as received on the wire, captured before any JSON parse. Required. Returns: True if signature is valid, False otherwise (including when ``raw_body`` is missing — fails closed per spec). """ if not self.webhook_secret: logger.warning("Webhook signature verification skipped: no webhook_secret configured") return True # Fail closed per adcontextprotocol/adcp#2478: verifiers that cannot # capture raw bytes MUST reject, surfacing the infrastructure gap # rather than silently reconstructing a signed body that may diverge # from the bytes the signer actually hashed. if raw_body is None: logger.error( "Webhook signature verification failed: raw_body is required. " "Capture the raw HTTP body pre-parse and pass it to " "handle_webhook(raw_body=...). See " "https://adcontextprotocol.org/docs/building/implementation/security" "#legacy-hmac-sha256-fallback-deprecated-removed-in-40" ) return False # Reject stale or future timestamps to prevent replay attacks try: ts = int(timestamp) except (ValueError, TypeError): return False now = int(time.time()) if abs(now - ts) > self.webhook_timestamp_tolerance: return False # Strip "sha256=" prefix if present if signature.startswith("sha256="): signature = signature[7:] payload_str = raw_body.decode("utf-8") if isinstance(raw_body, bytes) else raw_body # Construct signed message: timestamp.payload signed_message = f"{timestamp}.{payload_str}" # Generate expected signature expected_signature = hmac.new( self.webhook_secret.encode("utf-8"), signed_message.encode("utf-8"), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected_signature) def _parse_webhook_result( self, task_id: str, task_type: str, operation_id: str, status: GeneratedTaskStatus, result: Any, timestamp: datetime | str, message: str | None, context_id: str | None, ) -> TaskResult[AdcpAsyncResponseData]: """ Parse webhook data into typed TaskResult based on task_type. Args: task_id: Unique identifier for this task task_type: Task type from application routing (e.g., "get_products") operation_id: Operation identifier from application routing status: Current task status result: Task-specific payload (AdCP response data) timestamp: ISO 8601 timestamp when webhook was generated message: Human-readable summary of task state context_id: Session/conversation identifier Returns: TaskResult with task-specific typed response data Note: This method works with both MCP and A2A protocols by accepting protocol-agnostic parameters rather than protocol-specific objects. """ from adcp.utils.response_parser import parse_json_or_text # Map task types to their response types (using string literals, not enum) # Note: Some response types are Union types (e.g., ActivateSignalResponse = Success | Error) response_type_map: dict[str, type[BaseModel] | Any] = { # Core operations "get_products": GetProductsResponse, "list_creative_formats": ListCreativeFormatsResponse, "sync_creatives": SyncCreativesResponse, "list_creatives": ListCreativesResponse, "build_creative": BuildCreativeResponse, "preview_creative": PreviewCreativeResponse, "create_media_buy": CreateMediaBuyResponse, "update_media_buy": UpdateMediaBuyResponse, "get_media_buy_delivery": GetMediaBuyDeliveryResponse, "get_media_buys": GetMediaBuysResponse, "get_signals": GetSignalsResponse, "activate_signal": ActivateSignalResponse, "provide_performance_feedback": ProvidePerformanceFeedbackResponse, "report_usage": ReportUsageResponse, "get_account_financials": GetAccountFinancialsResponse, "list_accounts": ListAccountsResponse, "sync_accounts": SyncAccountsResponse, "log_event": LogEventResponse, "sync_event_sources": SyncEventSourcesResponse, "sync_audiences": SyncAudiencesResponse, "sync_catalogs": SyncCatalogsResponse, "get_creative_delivery": GetCreativeDeliveryResponse, # V3 Protocol Discovery "get_adcp_capabilities": GetAdcpCapabilitiesResponse, # V3 Content Standards "create_content_standards": CreateContentStandardsResponse, "get_content_standards": GetContentStandardsResponse, "list_content_standards": ListContentStandardsResponse, "update_content_standards": UpdateContentStandardsResponse, "calibrate_content": CalibrateContentResponse, "validate_content_delivery": ValidateContentDeliveryResponse, "get_media_buy_artifacts": GetMediaBuyArtifactsResponse, # V3 Sponsored Intelligence "si_get_offering": SiGetOfferingResponse, "si_initiate_session": SiInitiateSessionResponse, "si_send_message": SiSendMessageResponse, "si_terminate_session": SiTerminateSessionResponse, # V3 Governance "get_creative_features": GetCreativeFeaturesResponse, "sync_plans": SyncPlansResponse, "check_governance": CheckGovernanceResponse, "report_plan_outcome": ReportPlanOutcomeResponse, "get_plan_audit_logs": GetPlanAuditLogsResponse, "create_property_list": CreatePropertyListResponse, "get_property_list": GetPropertyListResponse, "list_property_lists": ListPropertyListsResponse, "update_property_list": UpdatePropertyListResponse, "delete_property_list": DeletePropertyListResponse, # TMP "context_match": ContextMatchResponse, "identity_match": IdentityMatchResponse, # Brand Rights "get_brand_identity": GetBrandIdentityResponse, "get_rights": GetRightsResponse, "acquire_rights": AcquireRightsResponse, "update_rights": UpdateRightsResponse, # Compliance "comply_test_controller": ComplyTestControllerResponse, } # Handle completed tasks with result parsing if status == GeneratedTaskStatus.completed and result is not None: response_type = response_type_map.get(task_type) if response_type: try: parsed_result: Any = parse_json_or_text(result, response_type) return TaskResult[AdcpAsyncResponseData]( status=TaskStatus.COMPLETED, data=parsed_result, success=True, metadata={ "task_id": task_id, "operation_id": operation_id, "timestamp": timestamp, "message": message, }, ) except ValueError as e: logger.warning(f"Failed to parse webhook result: {e}") # Fall through to untyped result # Handle failed, input-required, or unparseable results # Convert status to core TaskStatus enum status_map = { GeneratedTaskStatus.completed: TaskStatus.COMPLETED, GeneratedTaskStatus.submitted: TaskStatus.SUBMITTED, GeneratedTaskStatus.working: TaskStatus.WORKING, GeneratedTaskStatus.failed: TaskStatus.FAILED, GeneratedTaskStatus.input_required: TaskStatus.NEEDS_INPUT, } task_status = status_map.get(status, TaskStatus.FAILED) # Extract error message from result.errors if present error_message: str | None = None if result is not None and hasattr(result, "errors"): errors = getattr(result, "errors", None) if errors and len(errors) > 0: first_error = errors[0] if hasattr(first_error, "message"): error_message = first_error.message return TaskResult[AdcpAsyncResponseData]( status=task_status, data=result, success=status == GeneratedTaskStatus.completed, error=error_message, metadata={ "task_id": task_id, "operation_id": operation_id, "timestamp": timestamp, "message": message, "context_id": context_id, }, ) async def _handle_mcp_webhook( self, payload: dict[str, Any], task_type: str, operation_id: str, signature: str | None, timestamp: str | None = None, raw_body: bytes | str | None = None, ) -> TaskResult[AdcpAsyncResponseData]: """ Handle MCP webhook delivered via HTTP POST. Args: payload: Webhook payload dict task_type: Task type from application routing operation_id: Operation identifier from application routing signature: Optional HMAC-SHA256 signature for verification (X-AdCP-Signature header) timestamp: Optional Unix timestamp for signature verification (X-AdCP-Timestamp header) raw_body: Optional raw HTTP request body for signature verification Returns: TaskResult with parsed task-specific response data Raises: ADCPWebhookSignatureError: If signature verification fails ValidationError: If payload doesn't match McpWebhookPayload schema """ from adcp.types.generated_poc.core.mcp_webhook_payload import McpWebhookPayload # When a webhook_secret is configured, require signed webhooks if self.webhook_secret: if not signature or not timestamp: raise ADCPWebhookSignatureError( "Webhook signature and timestamp headers are required" ) if not self._verify_webhook_signature(payload, signature, timestamp, raw_body): logger.warning( f"Webhook signature verification failed for agent {self.agent_config.id}" ) raise ADCPWebhookSignatureError("Invalid webhook signature") # Validate and parse MCP webhook payload webhook = McpWebhookPayload.model_validate(payload) # Emit activity for monitoring self._emit_activity( Activity( type=ActivityType.WEBHOOK_RECEIVED, operation_id=operation_id, agent_id=self.agent_config.id, task_type=task_type, timestamp=datetime.now(timezone.utc).isoformat(), metadata={"payload": payload, "protocol": "mcp"}, ) ) # Extract fields and parse result return self._parse_webhook_result( task_id=webhook.task_id, task_type=task_type, operation_id=operation_id, status=webhook.status, result=webhook.result, timestamp=webhook.timestamp, message=webhook.message, context_id=webhook.context_id, ) async def _handle_a2a_webhook( self, payload: Task | TaskStatusUpdateEvent, task_type: str, operation_id: str ) -> TaskResult[AdcpAsyncResponseData]: """ Handle A2A webhook delivered through Task or TaskStatusUpdateEvent. Per A2A specification: - Terminated statuses (completed, failed): Payload is Task with artifacts[].parts[] - Intermediate statuses (working, input-required, submitted): Payload is TaskStatusUpdateEvent with status.message.parts[] Args: payload: A2A Task or TaskStatusUpdateEvent object task_type: Task type from application routing operation_id: Operation identifier from application routing Returns: TaskResult with parsed task-specific response data Note: Signature verification is NOT applicable for A2A webhooks as they arrive through authenticated A2A connections, not HTTP. """ from a2a import types as _pb from google.protobuf.json_format import MessageToDict as _MessageToDict def _a2a_part_data_dict(part: _pb.Part) -> Any: if part.WhichOneof("content") != "data": return None return _MessageToDict(part.data) def _a2a_part_text(part: _pb.Part) -> str | None: if part.WhichOneof("content") != "text": return None return part.text def _a2a_state_to_string(state_value: int) -> str: """Map ``TaskState`` int → spec string (``TASK_STATE_COMPLETED`` → ``completed``).""" name = _pb.TaskState.Name(state_value) if name.startswith("TASK_STATE_"): return name[len("TASK_STATE_") :].lower().replace("_", "-") return name.lower() def _a2a_timestamp(ts: Any) -> datetime | str: """Convert a proto Timestamp (or string) to datetime/ISO string.""" if ts is None: return datetime.now(timezone.utc) if isinstance(ts, str): return ts or datetime.now(timezone.utc) try: return cast(datetime, ts.ToDatetime().replace(tzinfo=timezone.utc)) except AttributeError: return datetime.now(timezone.utc) 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): task_id = payload.task_id context_id = payload.context_id or None has_status = payload.HasField("status") status_state = _a2a_state_to_string(payload.status.state) if has_status else "failed" timestamp = ( _a2a_timestamp(payload.status.timestamp) if has_status and payload.status.HasField("timestamp") else datetime.now(timezone.utc) ) if has_status and payload.status.HasField("message") and payload.status.message.parts: data_parts = [ d for d in (_a2a_part_data_dict(p) for p in payload.status.message.parts) if d is not None ] if data_parts: adcp_data = data_parts[-1] if isinstance(adcp_data, dict) and "response" in adcp_data: adcp_data = adcp_data["response"] for part in payload.status.message.parts: text = _a2a_part_text(part) if text is not None: text_message = text break else: task_id = payload.id context_id = payload.context_id or None has_status = payload.HasField("status") status_state = _a2a_state_to_string(payload.status.state) if has_status else "failed" timestamp = ( _a2a_timestamp(payload.status.timestamp) if has_status and payload.status.HasField("timestamp") else datetime.now(timezone.utc) ) if payload.artifacts: target_artifact = payload.artifacts[-1] if target_artifact.parts: data_parts = [ d for d in (_a2a_part_data_dict(p) for p in target_artifact.parts) if d is not None ] if data_parts: adcp_data = data_parts[-1] if isinstance(adcp_data, dict) and "response" in adcp_data: adcp_data = adcp_data["response"] for part in target_artifact.parts: text = _a2a_part_text(part) if text is not None: text_message = text break # Map A2A status.state to GeneratedTaskStatus enum status_map = { "completed": GeneratedTaskStatus.completed, "submitted": GeneratedTaskStatus.submitted, "working": GeneratedTaskStatus.working, "failed": GeneratedTaskStatus.failed, "input-required": GeneratedTaskStatus.input_required, "input_required": GeneratedTaskStatus.input_required, # Handle both formats } mapped_status = status_map.get(status_state, GeneratedTaskStatus.failed) # Emit activity for monitoring self._emit_activity( Activity( type=ActivityType.WEBHOOK_RECEIVED, operation_id=operation_id, agent_id=self.agent_config.id, task_type=task_type, timestamp=datetime.now(timezone.utc).isoformat(), metadata={ "task_id": task_id, "protocol": "a2a", "payload_type": ( "TaskStatusUpdateEvent" if isinstance(payload, TaskStatusUpdateEvent) else "Task" ), }, ) ) # Parse and return typed result by passing extracted fields directly return self._parse_webhook_result( task_id=task_id, task_type=task_type, operation_id=operation_id, status=mapped_status, result=adcp_data, timestamp=timestamp, message=text_message, context_id=context_id, ) async def handle_webhook( self, payload: dict[str, Any] | Task | TaskStatusUpdateEvent, task_type: str, operation_id: str, signature: str | None = None, timestamp: str | None = None, raw_body: bytes | str | None = None, ) -> TaskResult[AdcpAsyncResponseData]: """ Handle incoming webhook and return typed result. This method provides a unified interface for handling webhooks from both MCP and A2A protocols: - MCP Webhooks: HTTP POST with dict payload, optional HMAC signature - A2A Webhooks: Task or TaskStatusUpdateEvent objects based on status The method automatically detects the protocol type and routes to the appropriate handler. Both protocols return a consistent TaskResult structure with typed AdCP response data. Args: payload: Webhook payload - one of: - dict[str, Any]: MCP webhook payload from HTTP POST - Task: A2A webhook for terminated statuses (completed, failed) - TaskStatusUpdateEvent: A2A webhook for intermediate statuses (working, input-required, submitted) task_type: Task type from application routing (e.g., "get_products"). Applications should extract this from URL routing pattern: /webhook/{task_type}/{agent_id}/{operation_id} operation_id: Operation identifier from application routing. Used to correlate webhook notifications with original task submission. signature: Optional HMAC-SHA256 signature for MCP webhook verification (X-AdCP-Signature header). Ignored for A2A webhooks. timestamp: Optional Unix timestamp (seconds) for MCP webhook signature verification (X-AdCP-Timestamp header). Required when signature is provided. raw_body: Optional raw HTTP request body bytes for signature verification. When provided, used directly instead of re-serializing the payload, avoiding cross-language JSON serialization mismatches. Strongly recommended for production use. Returns: TaskResult with parsed task-specific response data. The structure is identical regardless of protocol. Raises: ADCPWebhookSignatureError: If MCP signature verification fails ValidationError: If MCP payload doesn't match WebhookPayload schema Note: task_type and operation_id were deprecated from the webhook payload per AdCP specification. Applications must extract these from URL routing and pass them explicitly. Examples: MCP webhook (HTTP endpoint): >>> @app.post("/webhook/{task_type}/{agent_id}/{operation_id}") >>> async def webhook_handler(task_type: str, operation_id: str, request: Request): >>> raw_body = await request.body() >>> payload = json.loads(raw_body) >>> signature = request.headers.get("X-AdCP-Signature") >>> timestamp = request.headers.get("X-AdCP-Timestamp") >>> result = await client.handle_webhook( >>> payload, task_type, operation_id, signature, timestamp, >>> raw_body=raw_body, >>> ) >>> if result.success: >>> print(f"Task completed: {result.data}") A2A webhook with Task (terminated status): >>> async def on_task_completed(task: Task): >>> # Extract task_type and operation_id from your app's task tracking >>> task_type = your_task_registry.get_type(task.id) >>> operation_id = your_task_registry.get_operation_id(task.id) >>> result = await client.handle_webhook( >>> task, task_type, operation_id >>> ) >>> if result.success: >>> print(f"Task completed: {result.data}") A2A webhook with TaskStatusUpdateEvent (intermediate status): >>> async def on_task_update(event: TaskStatusUpdateEvent): >>> # Extract task_type and operation_id from your app's task tracking >>> task_type = your_task_registry.get_type(event.task_id) >>> operation_id = your_task_registry.get_operation_id(event.task_id) >>> result = await client.handle_webhook( >>> event, task_type, operation_id >>> ) >>> if result.status == GeneratedTaskStatus.working: >>> print(f"Task still working: {result.metadata.get('message')}") """ # Detect protocol type and route to appropriate handler if isinstance(payload, (Task, TaskStatusUpdateEvent)): # A2A webhook (Task or TaskStatusUpdateEvent) return await self._handle_a2a_webhook(payload, task_type, operation_id) else: # MCP webhook (dict payload) return await self._handle_mcp_webhook( payload, task_type, operation_id, signature, timestamp, raw_body )Client for interacting with a single AdCP agent.
Initialize ADCP client for a single agent.
Args
agent_config- Agent configuration
webhook_url_template- Template for webhook URLs with {agent_id}, {task_type}, {operation_id}
webhook_secret- Secret for webhook signature verification
on_activity- Callback for activity events
webhook_timestamp_tolerance- Maximum age (in seconds) for webhook timestamps. Webhooks with timestamps older than this or more than this far in the future are rejected. Defaults to 300 (5 minutes).
capabilities_ttl- Time-to-live in seconds for cached capabilities (default: 1 hour)
validate_features- When True, automatically check that the seller supports required features before making task calls (e.g., sync_audiences requires audience_targeting). Requires capabilities to have been fetched first.
strict_idempotency- When True, verify the seller declared
adcp.idempotency.replay_ttl_secondsin capabilities before any mutating call. Fetches capabilities lazily on first use. RaisesIdempotencyUnsupportedErrorif the declaration is missing — sellers that don't declare it provide no retry-safety guarantee per AdCP #2315. Defaults to False for backward compatibility. signing- Optional RFC 9421 request-signing config. When provided,
the client automatically attaches
Signature/Signature-Input/Content-Digestheaders to operations the seller'srequest_signingcapability lists inrequired_for,warn_for, orsupported_for. The seller'scovers_content_digestpolicy determines whether the body is bound to the signature. Generate a key withadcp-keygenand publish the public JWK at yourjwks_uri. Supported on both A2A and MCP (mcp_transport="streamable_http"); SSE-transport MCP logs a warning and falls through unsigned. validation- Schema-driven validation modes for outgoing
requests and incoming responses against the bundled AdCP
JSON schemas. Defaults (matching the TS port): requests
in
warnmode (drift logged but not blocked — partial payloads in error-path tests still work) and responses instrictmode (agent drift fails the task).ADCP_VALIDATION_MODE=strict|warn|offoverrides both sides at call time (matches the TS port);ADCP_ENVset toproduction/prodflips only the response default towarn. GenericENV/ENVIRONMENT/PYTHON_ENVare deliberately ignored — they collide with unrelated tooling. Storyboards and compliance runners that want hard-stop enforcement everywhere passvalidation=ValidationHookConfig(requests="strict", responses="strict"); high-throughput callers can set either side to"off"to skip the validator entirely with zero overhead. context_id-
A2A-only. Seed the A2A conversation context. Pass a previously-returned
context_idto resume a session across process restarts, or a self-assigned UUID to name the session with your own correlation key (the ADK server honors buyer-proposed ids). If omitted, the server mints one on the first message and this client auto-retains it for subsequent calls. Read the current value viaclient.context_id; callclient.reset_context()to start a fresh conversation. Rule of thumb: oneADCPClientper A2A conversation — if a buyer has multiple concurrent briefs with the same agent, construct one client per brief rather than sharing.For HITL flows that can span a process restart mid-task, use
checkpoint()/from_checkpoint()instead of persistingcontext_idalone — full resume state is bothcontext_idANDactive_task_id.Raises
TypeErrorif passed with a non-A2A protocol. force_a2a_version-
A2A-only. Pin the A2A transport version (e.g.
"0.3","1.0") by filtering the peer's advertisedsupported_interfacesto entries whoseprotocol_versionmatches. Not for AdCP protocol pinning — seeadcp_versionfor that. Intended for tests or for forcing a 0.3-speaking path against a dual-advertising peer. Raises :class:ADCPConnectionErroron the first call if no advertised interface matches.None(default) lets the SDK'sClientFactorypick the most capable transport the peer supports. Use :attr:a2a_protocol_versionsto probe what a peer advertises before pinning.Raises
TypeErrorif passed with a non-A2A protocol. adcp_version-
AdCP protocol release this client speaks (release-precision string, e.g.
"3.0","3.1","3.1-beta"). Stripe-style per-instance pin: the value is sent asadcp_versionon every outbound request once Stage 3 wires it through the validation hooks; today (Stage 2), it's plumbing only — stored on the instance and exposed via :meth:get_adcp_version(), with no wire impact yet.None(default) resolves to the SDK's compile-time pin (ADCP_VERSIONpackaged with the wheel). Cross-major pins raise :class:ConfigurationErrorat construction; install the SDK major that targets your wire version instead. Patch-precision strings ("3.0.1") and build metadata ("3.0.1+canary") are accepted at construction but normalized to release-precision before wire emission per the spec — patches and build metadata are not part of the negotiation contract.get_adcp_version()returns the normalized form.Caller-supplied
adcp_versionon a per-call params dict wins over the constructor pin: the enricher is the default, not an override. Once Stage 3 threads schema selection through, this becomes a supported per-call override; today it's plumbing-level only.Migration from
adcp_major_version(legacy integer wire field): generated request types still exposeadcp_major_version: int | Nonefrom the pre-#3493 schema. Both fields will coexist on the wire through 3.x; servers prefer the newadcp_versionwhen both are present. Stop populatingadcp_major_versionon request models once your seller advertises 3.1 insupported_versions. server_version-
AdCP wire shape the seller speaks. Most adopters leave this
None— the SDK assumes a v3 seller and the seller's/.well-known/agent-card.jsonis the canonical source of truth once a probe-and-cache path lands.Pin explicitly when:
- You're talking to a known-legacy seller (e.g.
server_version="2.5"). The SDK emits a :class:DeprecationWarningat construction — outbound translation is not yet wired (Stage 7 full will add it), so a legacy pin today is a signal the SDK acknowledges but cannot act on. Adopters whose sellers still speak pre-3.0 should either upgrade the seller or wait for the inverse-translator release. - You want telemetry to attribute outbound traffic to a specific server-side version regardless of what the seller advertises.
Retrieve the current value via :meth:
get_server_version. - You're talking to a known-legacy seller (e.g.
Static methods
def from_checkpoint(agent_config: AgentConfig,
state: Checkpoint,
**kwargs: Any) ‑> ADCPClient-
Rehydrate an ADCPClient from a prior
checkpoint().Restores both
context_idandactive_task_idso a process restart mid-input-requiredcan resume the same task, not orphan it. Accepts the same keyword arguments as__init__(signing, strict_idempotency, etc.) — the checkpoint only carries session-resume state; operational config is re-supplied by the caller.Raises
ValueErrorif the checkpoint'sagent_iddoesn't matchagent_config.id— a checkpoint minted for Agent A must not be restored onto Agent B, or the client will leak Agent A's opaque session ids to Agent B on the next message.Raises
TypeErroron a non-A2Aagent_configif the checkpoint carries a non-emptycontext_idoractive_task_id— session-resume state on a protocol that doesn't support it would be silently dropped, masking bugs. An empty/absent checkpoint round-trips cleanly on any protocol. def from_mcp_client(client: ClientSession,
*,
agent_id: str | None = None,
validation: ValidationHookConfig | None = None,
capabilities_ttl: float = 3600.0,
validate_features: bool = False,
strict_idempotency: bool = False)-
Create an ADCPClient wrapping a pre-connected MCP ClientSession.
Parity with JS
AgentClient.fromMCPClient()(v5.19.0). The primary use case is compliance test fleets that wire a fullADCPClientagainst an in-process MCP server without standing up a loopback HTTP server.Warning
The returned client's
close()andasync with__aexit__are no-ops — the caller owns the injected session and is responsible for closing it. Code that relies onasync with ADCPClient.from_mcp_client(...) as c:to clean up the session will leak the session.Webhook delivery and
on_activitycallbacks are not wired on the in-process path — there is no HTTP transport for the seller to call back through. Don't pass these to the factory (they're absent from the signature on purpose).If the injected session has not been initialized (
await session.initialize()), the first tool call surfaces as an opaque MCP protocol error inTaskResult.error. The factory does not initialize for you — verify before calling.Session lifecycle: the caller owns the session —
close()andasync withexit on the returned client are no-ops. Use your ownAsyncExitStackto scope both the transport and the client::import contextlib from mcp import ClientSession from mcp.shared.memory import create_client_server_memory_streams async with contextlib.AsyncExitStack() as stack: (c_read, c_write), (s_read, s_write) = await stack.enter_async_context( create_client_server_memory_streams() ) # wire your in-process server to (s_read, s_write) here session = await stack.enter_async_context( ClientSession(c_read, c_write) ) await session.initialize() # close() is a no-op on injected sessions; no stack.enter_async_context needed. adcp_client = ADCPClient.from_mcp_client(session, agent_id="test-seller") result = await adcp_client.get_products(GetProductsRequest(...))Note
Request signing is not supported on the injected-session path — the signing hook is wired into the HTTP transport layer that is bypassed here.
signing=is intentionally absent from this factory's parameters.Args
client- A pre-connected
mcp.ClientSessionwhoseinitialize()has already been awaited. agent_id- Identifier for the wrapped agent used in log messages
and error objects. Defaults to a unique
in-process-XXXXXXXXtoken; set this explicitly when running multiple in-process agents concurrently so log lines are distinguishable. validation- Schema-validation modes (same as
__init__). strict_idempotency- Verify seller declared idempotency support
before each mutating call (same as
__init__). validate_features- Gate tool calls on fetched capability
declarations (same as
__init__). capabilities_ttl- TTL for the capability cache in seconds
(same as
__init__).
Returns
A fully configured
ADCPClientbacked by the injected session.
Instance variables
prop a2a_protocol_versions : list[str] | None-
Expand source code
@property def a2a_protocol_versions(self) -> list[str] | None: """A2A ``protocol_version`` strings the peer advertises, sorted. Lazily populated after the first operation that fetches the peer's ``AgentCard`` (``fetch_capabilities``, ``list_tools``, ``get_agent_info``, or any skill-call). Returns ``None`` before the card has been fetched so callers can distinguish "not yet known" from "peer advertises nothing" (empty list). Returns ``None`` for non-A2A clients. Useful for probing which wire version a peer speaks — buyers running alongside both 0.3-era and 1.0-era agents can use this to confirm what they're talking to. """ if isinstance(self.adapter, A2AAdapter): return self.adapter.a2a_protocol_versions return NoneA2A
protocol_versionstrings the peer advertises, sorted.Lazily populated after the first operation that fetches the peer's
AgentCard(fetch_capabilities,list_tools,get_agent_info, or any skill-call). ReturnsNonebefore the card has been fetched so callers can distinguish "not yet known" from "peer advertises nothing" (empty list). ReturnsNonefor non-A2A clients.Useful for probing which wire version a peer speaks — buyers running alongside both 0.3-era and 1.0-era agents can use this to confirm what they're talking to.
prop active_task_id : str | None-
Expand source code
@property def active_task_id(self) -> str | None: """A2A task_id the next send must echo to resume the same task. Set when the last A2A response was non-terminal (``input-required``, ``working``, ``submitted``, ``auth-required``). The adapter echoes this id on the next outbound message so the server resumes the same task. Clears automatically when the task reaches a terminal state. Full resume state is *both* ``context_id`` and ``active_task_id`` — persist both (or use ``checkpoint()``) to survive a process restart mid-HITL without orphaning the task. Returns ``None`` for non-A2A clients. """ if isinstance(self.adapter, A2AAdapter): return self.adapter.active_task_id return NoneA2A task_id the next send must echo to resume the same task.
Set when the last A2A response was non-terminal (
input-required,working,submitted,auth-required). The adapter echoes this id on the next outbound message so the server resumes the same task. Clears automatically when the task reaches a terminal state.Full resume state is both
context_idandactive_task_id— persist both (or usecheckpoint()) to survive a process restart mid-HITL without orphaning the task.Returns
Nonefor non-A2A clients. prop capabilities : GetAdcpCapabilitiesResponse | None-
Expand source code
@property def capabilities(self) -> GetAdcpCapabilitiesResponse | None: """Return cached capabilities, or None if not yet fetched.""" return self._capabilitiesReturn cached capabilities, or None if not yet fetched.
prop context_id : str | None-
Expand source code
@property def context_id(self) -> str | None: """Current A2A conversation context_id. Reads the context_id currently associated with this client: the value assigned by the A2A server (auto-captured from the most recent response) or the one seeded via the constructor or ``reset_context()``. Returns ``None`` before the first A2A call in a fresh conversation, or for clients on non-A2A protocols — reads are lenient across protocols so generic code can probe ``if client.context_id: ...`` safely. Writes (constructor kwarg, ``reset_context``) raise on non-A2A because the operation has no meaning there. Not safe for concurrent calls on the same client — the adapter mutates this on every response. Rule of thumb: one ADCPClient per A2A conversation. For simple completed-task resume, persist this value and pass it to ``ADCPClient(context_id=...)``. For HITL flows that may restart mid-``input-required``, use ``checkpoint()`` / ``from_checkpoint()`` — full resume state is both this id AND ``active_task_id``. """ if isinstance(self.adapter, A2AAdapter): return self.adapter.context_id return NoneCurrent A2A conversation context_id.
Reads the context_id currently associated with this client: the value assigned by the A2A server (auto-captured from the most recent response) or the one seeded via the constructor or
reset_context(). ReturnsNonebefore the first A2A call in a fresh conversation, or for clients on non-A2A protocols — reads are lenient across protocols so generic code can probeif client.context_id: ...safely. Writes (constructor kwarg,reset_context) raise on non-A2A because the operation has no meaning there.Not safe for concurrent calls on the same client — the adapter mutates this on every response. Rule of thumb: one ADCPClient per A2A conversation.
For simple completed-task resume, persist this value and pass it to
ADCPClient(context_id=...). For HITL flows that may restart mid-input-required, usecheckpoint()/from_checkpoint()— full resume state is both this id ANDactive_task_id. prop feature_resolver : FeatureResolver | None-
Expand source code
@property def feature_resolver(self) -> FeatureResolver | None: """Return the FeatureResolver for cached capabilities, or None.""" return self._feature_resolverReturn the FeatureResolver for cached capabilities, or None.
Methods
async def acquire_rights(self,
request: AcquireRightsRequest) ‑> TaskResult[Union[AcquireRightsResponse1, AcquireRightsResponse2, AcquireRightsResponse3, AcquireRightsResponse4]]-
Expand source code
async def acquire_rights( self, request: AcquireRightsRequest, ) -> TaskResult[AcquireRightsResponse]: """Acquire rights for brand content usage. Binding contractual request to license rights for a campaign. Returns credentials for generating rights-cleared content. Args: request: Request with rights_id, pricing_option_id, buyer, campaign, and revocation_webhook. Returns: TaskResult containing AcquireRightsResponse (acquired, pending_approval, rejected, or error). """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="acquire_rights", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.acquire_rights(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="acquire_rights", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, AcquireRightsResponse)Acquire rights for brand content usage.
Binding contractual request to license rights for a campaign. Returns credentials for generating rights-cleared content.
Args
request- Request with rights_id, pricing_option_id, buyer, campaign, and revocation_webhook.
Returns
TaskResult containing AcquireRightsResponse (acquired, pending_approval, rejected, or error).
async def activate_signal(self,
request: ActivateSignalRequest) ‑> TaskResult[Union[ActivateSignalResponse1, ActivateSignalResponse2]]-
Expand source code
async def activate_signal( self, request: ActivateSignalRequest, ) -> TaskResult[ActivateSignalResponse]: """ Activate Signal. Args: request: Request parameters Returns: TaskResult containing ActivateSignalResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="activate_signal", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.activate_signal(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="activate_signal", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ActivateSignalResponse)Activate Signal.
Args
request- Request parameters
Returns
TaskResult containing ActivateSignalResponse
async def build_creative(self,
request: BuildCreativeRequest) ‑> TaskResult[Union[BuildCreativeResponse1, BuildCreativeResponse2, BuildCreativeResponse3, BuildCreativeResponse4, BuildCreativeResponse5, BuildCreativeResponse6]]-
Expand source code
async def build_creative( self, request: BuildCreativeRequest, ) -> TaskResult[BuildCreativeResponse]: """ Generate production-ready creative assets. Requests the creative agent to build final deliverable assets in the target format (e.g., VAST, DAAST, HTML5). This is typically called after previewing and approving a creative manifest. Args: request: Creative build parameters including: - manifest: Creative manifest with brand info and content - target_format_id: Desired output format identifier - inputs: Optional user-provided inputs for template variables - deployment: Platform or agent deployment configuration Returns: TaskResult containing BuildCreativeResponse with: - assets: Production-ready creative files (URLs or inline content) - format_id: The generated format identifier - manifest: The creative manifest used for generation - metadata: Additional platform-specific details Example: >>> from adcp import ADCPClient, BuildCreativeRequest >>> client = ADCPClient(agent_config) >>> request = BuildCreativeRequest( ... manifest=creative_manifest, ... target_format_id="vast_2.0", ... inputs={"duration": 30} ... ) >>> result = await client.build_creative(request) >>> if result.success: ... vast_url = result.data.assets[0].url """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="build_creative", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.build_creative(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="build_creative", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, BuildCreativeResponse)Generate production-ready creative assets.
Requests the creative agent to build final deliverable assets in the target format (e.g., VAST, DAAST, HTML5). This is typically called after previewing and approving a creative manifest.
Args
request- Creative build parameters including: - manifest: Creative manifest with brand info and content - target_format_id: Desired output format identifier - inputs: Optional user-provided inputs for template variables - deployment: Platform or agent deployment configuration
Returns
TaskResult containing BuildCreativeResponse with: - assets: Production-ready creative files (URLs or inline content) - format_id: The generated format identifier - manifest: The creative manifest used for generation - metadata: Additional platform-specific details
Example
>>> from adcp import ADCPClient, BuildCreativeRequest >>> client = ADCPClient(agent_config) >>> request = BuildCreativeRequest( ... manifest=creative_manifest, ... target_format_id="vast_2.0", ... inputs={"duration": 30} ... ) >>> result = await client.build_creative(request) >>> if result.success: ... vast_url = result.data.assets[0].url async def calibrate_content(self, request: CalibrateContentRequest) ‑> TaskResult[Union[CalibrateContentResponse1, CalibrateContentResponse2]]-
Expand source code
async def calibrate_content( self, request: CalibrateContentRequest, ) -> TaskResult[CalibrateContentResponse]: """ Calibrate content against standards. Evaluates content (artifact or URL) against configured standards to determine suitability for ad placement. Args: request: Request parameters including content to evaluate Returns: TaskResult containing CalibrateContentResponse with verdict """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="calibrate_content", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.calibrate_content(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="calibrate_content", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CalibrateContentResponse)Calibrate content against standards.
Evaluates content (artifact or URL) against configured standards to determine suitability for ad placement.
Args
request- Request parameters including content to evaluate
Returns
TaskResult containing CalibrateContentResponse with verdict
async def check_governance(self,
request: CheckGovernanceRequest) ‑> TaskResult[CheckGovernanceResponse]-
Expand source code
async def check_governance( self, request: CheckGovernanceRequest, ) -> TaskResult[CheckGovernanceResponse]: """Check a proposed or committed action against campaign governance.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="check_governance", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.check_governance(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="check_governance", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CheckGovernanceResponse)Check a proposed or committed action against campaign governance.
def checkpoint(self) ‑> Checkpoint-
Expand source code
def checkpoint(self) -> Checkpoint: """Return the minimal state needed to resume this A2A session. Full resume for HITL / multi-turn flows requires *both* ``context_id`` (which conversation) AND ``active_task_id`` (which in-flight task to echo). Persisting only ``context_id`` reconnects to the right conversation but orphans the pending task server-side — the next send starts a new task under the same context, and the original ``input-required`` task is abandoned. The returned dict also carries ``agent_id`` so a later ``from_checkpoint`` call against a different ``AgentConfig`` fails loudly instead of sending one agent's session ids to another. Pair with ``ADCPClient.from_checkpoint(agent_config, state)``. Returns a fully-populated ``Checkpoint`` on non-A2A clients with ``context_id``/``active_task_id`` set to ``None``, so generic persist-and-restore code can call this without branching on protocol. """ return Checkpoint( agent_id=self.agent_config.id, context_id=self.context_id, active_task_id=self.active_task_id, )Return the minimal state needed to resume this A2A session.
Full resume for HITL / multi-turn flows requires both
context_id(which conversation) ANDactive_task_id(which in-flight task to echo). Persisting onlycontext_idreconnects to the right conversation but orphans the pending task server-side — the next send starts a new task under the same context, and the originalinput-requiredtask is abandoned.The returned dict also carries
agent_idso a laterfrom_checkpointcall against a differentAgentConfigfails loudly instead of sending one agent's session ids to another.Pair with
ADCPClient.from_checkpoint()(agent_config, state).Returns a fully-populated
Checkpointon non-A2A clients withcontext_id/active_task_idset toNone, so generic persist-and-restore code can call this without branching on protocol. 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 close_mcp_session(self, session_id: str | None = None) ‑> None-
Expand source code
async def close_mcp_session(self, session_id: str | None = None) -> None: """Explicitly terminate a stateful MCP Streamable HTTP session. This sends ``DELETE`` to the configured MCP endpoint with the ``Mcp-Session-Id`` header. When ``session_id`` is omitted, the SDK-managed current session is closed. It is only valid for MCP agents using ``mcp_transport="streamable_http"``. """ if not isinstance(self.adapter, MCPAdapter): raise TypeError( "close_mcp_session is only supported for MCP clients; " f"got {self.agent_config.protocol}" ) await self.adapter.close_mcp_session(session_id)Explicitly terminate a stateful MCP Streamable HTTP session.
This sends
DELETEto the configured MCP endpoint with theMcp-Session-Idheader. Whensession_idis omitted, the SDK-managed current session is closed. It is only valid for MCP agents usingmcp_transport="streamable_http". async def comply_test_controller(self,
request: ComplyTestControllerRequest) ‑> TaskResult[ComplyTestControllerResponse]-
Expand source code
async def comply_test_controller( self, request: ComplyTestControllerRequest, ) -> TaskResult[ComplyTestControllerResponse]: """Compliance test controller for sandbox testing. Enables sellers to simulate state transitions and delivery data in a sandbox environment for compliance testing. Args: request: Request specifying scenario and parameters. Returns: TaskResult containing ComplyTestControllerResponse. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="comply_test_controller", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.comply_test_controller(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="comply_test_controller", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ComplyTestControllerResponse)Compliance test controller for sandbox testing.
Enables sellers to simulate state transitions and delivery data in a sandbox environment for compliance testing.
Args
request- Request specifying scenario and parameters.
Returns
TaskResult containing ComplyTestControllerResponse.
async def context_match(self,
request: ContextMatchRequest) ‑> TaskResult[ContextMatchResponse]-
Expand source code
async def context_match( self, request: ContextMatchRequest, ) -> TaskResult[ContextMatchResponse]: """Match ad context to buyer packages. Evaluates contextual signals for a publisher placement against the buyer's active packages and returns matching offers. Args: request: Context match request with placement, property, and optional artifact refs, context signals, and geo data. Returns: TaskResult containing ContextMatchResponse with offers. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True, by_alias=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="context_match", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.context_match(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="context_match", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ContextMatchResponse)Match ad context to buyer packages.
Evaluates contextual signals for a publisher placement against the buyer's active packages and returns matching offers.
Args
request- Context match request with placement, property, and optional artifact refs, context signals, and geo data.
Returns
TaskResult containing ContextMatchResponse with offers.
async def create_collection_list(self, request: CreateCollectionListRequest) ‑> TaskResult[CreateCollectionListResponse]-
Expand source code
async def create_collection_list( self, request: CreateCollectionListRequest, ) -> TaskResult[CreateCollectionListResponse]: """Create a collection list for governance filtering. Collection lists define dynamic sets of collections (properties, segments, etc.) that can be referenced by authorization rules and audience scoping. Args: request: Request parameters for creating the collection list Returns: TaskResult containing CreateCollectionListResponse with list_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_collection_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.create_collection_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_collection_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CreateCollectionListResponse)Create a collection list for governance filtering.
Collection lists define dynamic sets of collections (properties, segments, etc.) that can be referenced by authorization rules and audience scoping.
Args
request- Request parameters for creating the collection list
Returns
TaskResult containing CreateCollectionListResponse with list_id
async def create_content_standards(self, request: CreateContentStandardsRequest) ‑> TaskResult[CreateContentStandardsResponse]-
Expand source code
async def create_content_standards( self, request: CreateContentStandardsRequest, ) -> TaskResult[CreateContentStandardsResponse]: """ Create a new content standards configuration. Defines acceptable content contexts for ad placement using natural language policy and optional calibration exemplars. Args: request: Request parameters including policy and scope Returns: TaskResult containing CreateContentStandardsResponse with standards_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_content_standards", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.create_content_standards(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_content_standards", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CreateContentStandardsResponse)Create a new content standards configuration.
Defines acceptable content contexts for ad placement using natural language policy and optional calibration exemplars.
Args
request- Request parameters including policy and scope
Returns
TaskResult containing CreateContentStandardsResponse with standards_id
async def create_media_buy(self,
request: CreateMediaBuyRequest) ‑> TaskResult[Union[CreateMediaBuyResponse1, CreateMediaBuyResponse2, CreateMediaBuyResponse3]]-
Expand source code
async def create_media_buy( self, request: CreateMediaBuyRequest, ) -> TaskResult[CreateMediaBuyResponse]: """ Create a new media buy reservation. Requests the agent to reserve inventory for a campaign. The agent returns a media_buy_id that tracks this reservation and can be used for updates. Args: request: Media buy creation parameters including: - brand: Brand reference; resolved from brand.json or the registry at execution - packages: List of package requests specifying desired inventory - publisher_properties: Target properties for ad placement - budget: Optional budget constraints - start_date/end_date: Campaign flight dates Returns: TaskResult containing CreateMediaBuyResponse with: - media_buy_id: Unique identifier for this reservation - status: Current state of the media buy - packages: Confirmed package details - Additional platform-specific metadata Example: >>> from adcp import ADCPClient, CreateMediaBuyRequest, BrandReference >>> client = ADCPClient(agent_config) >>> request = CreateMediaBuyRequest( ... brand=BrandReference(domain="acme.com"), ... packages=[package_request], ... publisher_properties=properties, ... ) >>> result = await client.create_media_buy(request) >>> if result.success: ... media_buy_id = result.data.media_buy_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_media_buy", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.create_media_buy(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_media_buy", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CreateMediaBuyResponse)Create a new media buy reservation.
Requests the agent to reserve inventory for a campaign. The agent returns a media_buy_id that tracks this reservation and can be used for updates.
Args
request- Media buy creation parameters including: - brand: Brand reference; resolved from brand.json or the registry at execution - packages: List of package requests specifying desired inventory - publisher_properties: Target properties for ad placement - budget: Optional budget constraints - start_date/end_date: Campaign flight dates
Returns
TaskResult containing CreateMediaBuyResponse with: - media_buy_id: Unique identifier for this reservation - status: Current state of the media buy - packages: Confirmed package details - Additional platform-specific metadata
Example
>>> from adcp import ADCPClient, CreateMediaBuyRequest, BrandReference >>> client = ADCPClient(agent_config) >>> request = CreateMediaBuyRequest( ... brand=BrandReference(domain="acme.com"), ... packages=[package_request], ... publisher_properties=properties, ... ) >>> result = await client.create_media_buy(request) >>> if result.success: ... media_buy_id = result.data.media_buy_id async def create_property_list(self, request: CreatePropertyListRequest) ‑> TaskResult[CreatePropertyListResponse]-
Expand source code
async def create_property_list( self, request: CreatePropertyListRequest, ) -> TaskResult[CreatePropertyListResponse]: """ Create a property list for governance filtering. Property lists define dynamic sets of properties based on filters, brand manifests, and feature requirements. Args: request: Request parameters for creating the property list Returns: TaskResult containing CreatePropertyListResponse with list_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_property_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.create_property_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="create_property_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, CreatePropertyListResponse)Create a property list for governance filtering.
Property lists define dynamic sets of properties based on filters, brand manifests, and feature requirements.
Args
request- Request parameters for creating the property list
Returns
TaskResult containing CreatePropertyListResponse with list_id
async def delete_collection_list(self, request: DeleteCollectionListRequest) ‑> TaskResult[DeleteCollectionListResponse]-
Expand source code
async def delete_collection_list( self, request: DeleteCollectionListRequest, ) -> TaskResult[DeleteCollectionListResponse]: """Delete a collection list. Args: request: Request parameters with list_id Returns: TaskResult containing DeleteCollectionListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="delete_collection_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.delete_collection_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="delete_collection_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, DeleteCollectionListResponse)Delete a collection list.
Args
request- Request parameters with list_id
Returns
TaskResult containing DeleteCollectionListResponse
async def delete_property_list(self, request: DeletePropertyListRequest) ‑> TaskResult[DeletePropertyListResponse]-
Expand source code
async def delete_property_list( self, request: DeletePropertyListRequest, ) -> TaskResult[DeletePropertyListResponse]: """ Delete a property list. Removes a property list. Any active subscriptions to this list will be terminated. Args: request: Request parameters with list_id Returns: TaskResult containing DeletePropertyListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="delete_property_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.delete_property_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="delete_property_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, DeletePropertyListResponse)Delete a property list.
Removes a property list. Any active subscriptions to this list will be terminated.
Args
request- Request parameters with list_id
Returns
TaskResult containing DeletePropertyListResponse
async def fetch_capabilities(self) ‑> adcp.types.generated_poc.protocol.get_adcp_capabilities_response.GetAdcpCapabilitiesResponse-
Expand source code
async def fetch_capabilities(self) -> GetAdcpCapabilitiesResponse: """Fetch capabilities, using cache if still valid. Returns: The seller's capabilities response. """ if self._capabilities is not None and self._capabilities_fetched_at is not None: elapsed = time.monotonic() - self._capabilities_fetched_at if elapsed < self.capabilities_ttl: return self._capabilities return await self.refresh_capabilities()Fetch capabilities, using cache if still valid.
Returns
The seller's capabilities response.
async def get_account_financials(self,
request: GetAccountFinancialsRequest) ‑> TaskResult[Union[GetAccountFinancialsResponse1, GetAccountFinancialsResponse2]]-
Expand source code
async def get_account_financials( self, request: GetAccountFinancialsRequest, ) -> TaskResult[GetAccountFinancialsResponse]: """ Get Account Financials. Args: request: Request parameters Returns: TaskResult containing GetAccountFinancialsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_account_financials", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_account_financials(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_account_financials", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetAccountFinancialsResponse)Get Account Financials.
Args
request- Request parameters
Returns
TaskResult containing GetAccountFinancialsResponse
async def get_adcp_capabilities(self, request: GetAdcpCapabilitiesRequest) ‑> TaskResult[GetAdcpCapabilitiesResponse]-
Expand source code
async def get_adcp_capabilities( self, request: GetAdcpCapabilitiesRequest, ) -> TaskResult[GetAdcpCapabilitiesResponse]: """ Get AdCP capabilities from the agent. Queries the agent's supported AdCP features, protocol versions, and domain-specific capabilities (media_buy, signals, sponsored_intelligence). Args: request: Request parameters including optional protocol filters Returns: TaskResult containing GetAdcpCapabilitiesResponse with: - adcp: Core protocol version information - supported_protocols: List of supported domain protocols - media_buy: Media buy capabilities (if supported) - sponsored_intelligence: SI capabilities (if supported) - signals: Signals capabilities (if supported) """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_adcp_capabilities", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_adcp_capabilities(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_adcp_capabilities", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetAdcpCapabilitiesResponse)Get AdCP capabilities from the agent.
Queries the agent's supported AdCP features, protocol versions, and domain-specific capabilities (media_buy, signals, sponsored_intelligence).
Args
request- Request parameters including optional protocol filters
Returns
TaskResult containing GetAdcpCapabilitiesResponse with: - adcp: Core protocol version information - supported_protocols: List of supported domain protocols - media_buy: Media buy capabilities (if supported) - sponsored_intelligence: SI capabilities (if supported) - signals: Signals capabilities (if supported)
def get_adcp_version(self) ‑> str-
Expand source code
def get_adcp_version(self) -> str: """Return the AdCP protocol release this client is pinned to. Resolved at construction from the ``adcp_version`` kwarg, with fallback to the SDK's compile-time pin (``ADCP_VERSION`` packaged with the wheel) when the caller didn't pin explicitly. Same value across the client's lifetime — the pin is per-instance, not per-call. See ``__init__``'s ``adcp_version`` parameter for the full semantics, including the cross-major fence and the Stage 2 vs Stage 3 distinction (today the pin is plumbing only; Stage 3 threads it through schema/validator selection). """ return self._adcp_versionReturn the AdCP protocol release this client is pinned to.
Resolved at construction from the
adcp_versionkwarg, with fallback to the SDK's compile-time pin (ADCP_VERSIONpackaged with the wheel) when the caller didn't pin explicitly. Same value across the client's lifetime — the pin is per-instance, not per-call.See
__init__'sadcp_versionparameter for the full semantics, including the cross-major fence and the Stage 2 vs Stage 3 distinction (today the pin is plumbing only; Stage 3 threads it through schema/validator selection). async def get_brand_identity(self,
request: GetBrandIdentityRequest) ‑> TaskResult[Union[GetBrandIdentityResponse1, GetBrandIdentityResponse2]]-
Expand source code
async def get_brand_identity( self, request: GetBrandIdentityRequest, ) -> TaskResult[GetBrandIdentityResponse]: """Get brand identity information. Retrieves brand identity data including logos, colors, fonts, voice synthesis config, and rights availability. Args: request: Request with brand_id and optional fields filter. Returns: TaskResult containing GetBrandIdentityResponse. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_brand_identity", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_brand_identity(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_brand_identity", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetBrandIdentityResponse)Get brand identity information.
Retrieves brand identity data including logos, colors, fonts, voice synthesis config, and rights availability.
Args
request- Request with brand_id and optional fields filter.
Returns
TaskResult containing GetBrandIdentityResponse.
async def get_collection_list(self, request: GetCollectionListRequest) ‑> TaskResult[GetCollectionListResponse]-
Expand source code
async def get_collection_list( self, request: GetCollectionListRequest, ) -> TaskResult[GetCollectionListResponse]: """Get a collection list with optional resolution. When resolve=true, returns the resolved members of the collection list. Args: request: Request parameters including list_id and resolve flag Returns: TaskResult containing GetCollectionListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_collection_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_collection_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_collection_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetCollectionListResponse)Get a collection list with optional resolution.
When resolve=true, returns the resolved members of the collection list.
Args
request- Request parameters including list_id and resolve flag
Returns
TaskResult containing GetCollectionListResponse
async def get_content_standards(self, request: GetContentStandardsRequest) ‑> TaskResult[Union[GetContentStandardsResponse1, GetContentStandardsResponse2]]-
Expand source code
async def get_content_standards( self, request: GetContentStandardsRequest, ) -> TaskResult[GetContentStandardsResponse]: """ Get a content standards configuration by ID. Args: request: Request parameters including standards_id Returns: TaskResult containing GetContentStandardsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_content_standards", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_content_standards(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_content_standards", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetContentStandardsResponse)Get a content standards configuration by ID.
Args
request- Request parameters including standards_id
Returns
TaskResult containing GetContentStandardsResponse
async def get_creative_delivery(self,
request: GetCreativeDeliveryRequest) ‑> TaskResult[GetCreativeDeliveryResponse]-
Expand source code
async def get_creative_delivery( self, request: GetCreativeDeliveryRequest, ) -> TaskResult[GetCreativeDeliveryResponse]: """ Get Creative Delivery. Args: request: Request parameters Returns: TaskResult containing GetCreativeDeliveryResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_creative_delivery", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_creative_delivery(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_creative_delivery", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetCreativeDeliveryResponse)Get Creative Delivery.
Args
request- Request parameters
Returns
TaskResult containing GetCreativeDeliveryResponse
async def get_creative_features(self,
request: GetCreativeFeaturesRequest) ‑> TaskResult[Union[GetCreativeFeaturesResponse1, GetCreativeFeaturesResponse2]]-
Expand source code
async def get_creative_features( self, request: GetCreativeFeaturesRequest, ) -> TaskResult[GetCreativeFeaturesResponse]: """Evaluate governance features for a creative manifest.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_creative_features", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_creative_features(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_creative_features", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetCreativeFeaturesResponse)Evaluate governance features for a creative manifest.
async def get_info(self) ‑> dict[str, typing.Any]-
Expand source code
async def get_info(self) -> dict[str, Any]: """ Get agent information including AdCP extension metadata. Returns agent card information including: - Agent name, description, version - Protocol type (mcp or a2a) - AdCP version (from extensions.adcp.adcp_version) - Supported protocols (from extensions.adcp.protocols_supported) - Available tools/skills Returns: Dictionary with agent metadata """ return await self.adapter.get_agent_info()Get agent information including AdCP extension metadata.
Returns agent card information including: - Agent name, description, version - Protocol type (mcp or a2a) - AdCP version (from extensions.adcp.adcp_version) - Supported protocols (from extensions.adcp.protocols_supported) - Available tools/skills
Returns
Dictionary with agent metadata
async def get_media_buy_artifacts(self, request: GetMediaBuyArtifactsRequest) ‑> TaskResult[Union[GetMediaBuyArtifactsResponse1, GetMediaBuyArtifactsResponse2]]-
Expand source code
async def get_media_buy_artifacts( self, request: GetMediaBuyArtifactsRequest, ) -> TaskResult[GetMediaBuyArtifactsResponse]: """ Get artifacts associated with a media buy. Retrieves content artifacts where ads were delivered for a media buy. Args: request: Request parameters including media_buy_id Returns: TaskResult containing GetMediaBuyArtifactsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buy_artifacts", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_media_buy_artifacts(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buy_artifacts", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetMediaBuyArtifactsResponse)Get artifacts associated with a media buy.
Retrieves content artifacts where ads were delivered for a media buy.
Args
request- Request parameters including media_buy_id
Returns
TaskResult containing GetMediaBuyArtifactsResponse
async def get_media_buy_delivery(self,
request: GetMediaBuyDeliveryRequest) ‑> TaskResult[GetMediaBuyDeliveryResponse]-
Expand source code
async def get_media_buy_delivery( self, request: GetMediaBuyDeliveryRequest, ) -> TaskResult[GetMediaBuyDeliveryResponse]: """ Get Media Buy Delivery. Args: request: Request parameters Returns: TaskResult containing GetMediaBuyDeliveryResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buy_delivery", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_media_buy_delivery(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buy_delivery", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetMediaBuyDeliveryResponse)Get Media Buy Delivery.
Args
request- Request parameters
Returns
TaskResult containing GetMediaBuyDeliveryResponse
async def get_media_buys(self,
request: GetMediaBuysRequest) ‑> TaskResult[GetMediaBuysResponse]-
Expand source code
async def get_media_buys( self, request: GetMediaBuysRequest, ) -> TaskResult[GetMediaBuysResponse]: """ Get Media Buys. Args: request: Request parameters Returns: TaskResult containing GetMediaBuysResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) if params.get("include_webhook_activity") is False: params.pop("include_webhook_activity") if params.get("webhook_activity_limit") == 50: params.pop("webhook_activity_limit") self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buys", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_media_buys(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_media_buys", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetMediaBuysResponse)Get Media Buys.
Args
request- Request parameters
Returns
TaskResult containing GetMediaBuysResponse
async def get_plan_audit_logs(self,
request: GetPlanAuditLogsRequest) ‑> TaskResult[GetPlanAuditLogsResponse]-
Expand source code
async def get_plan_audit_logs( self, request: GetPlanAuditLogsRequest, ) -> TaskResult[GetPlanAuditLogsResponse]: """Retrieve governance state and audit logs for one or more plans.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_plan_audit_logs", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_plan_audit_logs(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_plan_audit_logs", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetPlanAuditLogsResponse)Retrieve governance state and audit logs for one or more plans.
async def get_products(self,
request: GetProductsRequest,
fetch_previews: bool = False,
preview_output_format: str = 'url',
creative_agent_client: ADCPClient | None = None) ‑> TaskResult[GetProductsResponse]-
Expand source code
async def get_products( self, request: GetProductsRequest, fetch_previews: bool = False, preview_output_format: str = "url", creative_agent_client: ADCPClient | None = None, ) -> TaskResult[GetProductsResponse]: """ Get advertising products. Args: request: Request parameters fetch_previews: If True, generate preview URLs for each product's formats (uses batch API for 5-10x performance improvement) preview_output_format: "url" for iframe URLs (default), "html" for direct embedding (2-3x faster, no iframe overhead) creative_agent_client: Client for creative agent (required if fetch_previews=True) Returns: TaskResult containing GetProductsResponse with optional preview URLs in metadata Raises: ValueError: If fetch_previews=True but creative_agent_client is not provided """ if fetch_previews and not creative_agent_client: raise ValueError("creative_agent_client is required when fetch_previews=True") operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_products", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_products(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_products", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) result: TaskResult[GetProductsResponse] = self.adapter._parse_response( raw_result, GetProductsResponse ) if ( fetch_previews and result.success and result.data and result.data.products 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 resultGet advertising products.
Args
request- Request parameters
fetch_previews- If True, generate preview URLs for each product's formats (uses batch API for 5-10x performance improvement)
preview_output_format- "url" for iframe URLs (default), "html" for direct embedding (2-3x faster, no iframe overhead)
creative_agent_client- Client for creative agent (required if fetch_previews=True)
Returns
TaskResult containing GetProductsResponse with optional preview URLs in metadata
Raises
ValueError- If fetch_previews=True but creative_agent_client is not provided
async def get_property_list(self, request: GetPropertyListRequest) ‑> TaskResult[GetPropertyListResponse]-
Expand source code
async def get_property_list( self, request: GetPropertyListRequest, ) -> TaskResult[GetPropertyListResponse]: """ Get a property list with optional resolution. When resolve=true, returns the list of resolved property identifiers. Use this to get the actual properties that match the list's filters. Args: request: Request parameters including list_id and resolve flag Returns: TaskResult containing GetPropertyListResponse with identifiers """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_property_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_property_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_property_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetPropertyListResponse)Get a property list with optional resolution.
When resolve=true, returns the list of resolved property identifiers. Use this to get the actual properties that match the list's filters.
Args
request- Request parameters including list_id and resolve flag
Returns
TaskResult containing GetPropertyListResponse with identifiers
async def get_rights(self,
request: GetRightsRequest) ‑> TaskResult[Union[GetRightsResponse1, GetRightsResponse2]]-
Expand source code
async def get_rights( self, request: GetRightsRequest, ) -> TaskResult[GetRightsResponse]: """Get available rights for licensing. Searches for rights offerings using natural language query and filters by type, uses, countries, and buyer compatibility. Args: request: Request with query, uses, and optional filters. Returns: TaskResult containing GetRightsResponse with matched rights. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_rights", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_rights(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_rights", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetRightsResponse)Get available rights for licensing.
Searches for rights offerings using natural language query and filters by type, uses, countries, and buyer compatibility.
Args
request- Request with query, uses, and optional filters.
Returns
TaskResult containing GetRightsResponse with matched rights.
def get_server_version(self) ‑> str | None-
Expand source code
def get_server_version(self) -> str | None: """Return the seller's AdCP wire-shape version, or ``None``. ``None`` means the SDK is assuming a current-major seller (the default). Returns a release-precision string (``"3.0"``, ``"3.1"``, ``"2.5"``) when the adopter pinned via the ``server_version`` constructor arg or — once the agent-card probe lands — when the SDK detected the seller's version from its agent-card. See ``__init__``'s ``server_version`` parameter for what legacy pins mean today (signal only; outbound translation ships in Stage 7-full). """ return self._server_versionReturn the seller's AdCP wire-shape version, or
None.Nonemeans the SDK is assuming a current-major seller (the default). Returns a release-precision string ("3.0","3.1","2.5") when the adopter pinned via theserver_versionconstructor arg or — once the agent-card probe lands — when the SDK detected the seller's version from its agent-card.See
__init__'sserver_versionparameter for what legacy pins mean today (signal only; outbound translation ships in Stage 7-full). async def get_signals(self,
request: GetSignalsRequest) ‑> TaskResult[GetSignalsResponse]-
Expand source code
async def get_signals( self, request: GetSignalsRequest, ) -> TaskResult[GetSignalsResponse]: """ Get Signals. Args: request: Request parameters Returns: TaskResult containing GetSignalsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_signals", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_signals(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_signals", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetSignalsResponse)Get Signals.
Args
request- Request parameters
Returns
TaskResult containing GetSignalsResponse
async def get_task_status(self,
request: GetTaskStatusRequest) ‑> TaskResult[GetTaskStatusResponse]-
Expand source code
async def get_task_status( self, request: GetTaskStatusRequest, ) -> TaskResult[GetTaskStatusResponse]: """ Get Task Status. Args: request: Request parameters Returns: TaskResult containing GetTaskStatusResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_task_status", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.get_task_status(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="get_task_status", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, GetTaskStatusResponse)Get Task Status.
Args
request- Request parameters
Returns
TaskResult containing GetTaskStatusResponse
def get_webhook_url(self, task_type: str, operation_id: str) ‑> str-
Expand source code
def get_webhook_url(self, task_type: str, operation_id: str) -> str: """Generate webhook URL for a task.""" if not self.webhook_url_template: raise ValueError("webhook_url_template not configured") return self.webhook_url_template.format( agent_id=self.agent_config.id, task_type=task_type, operation_id=operation_id, )Generate webhook URL for a task.
async def handle_webhook(self,
payload: dict[str, Any] | Task | TaskStatusUpdateEvent,
task_type: str,
operation_id: str,
signature: str | None = None,
timestamp: str | None = None,
raw_body: bytes | str | None = None) ‑> TaskResult[AdcpAsyncResponseData]-
Expand source code
async def handle_webhook( self, payload: dict[str, Any] | Task | TaskStatusUpdateEvent, task_type: str, operation_id: str, signature: str | None = None, timestamp: str | None = None, raw_body: bytes | str | None = None, ) -> TaskResult[AdcpAsyncResponseData]: """ Handle incoming webhook and return typed result. This method provides a unified interface for handling webhooks from both MCP and A2A protocols: - MCP Webhooks: HTTP POST with dict payload, optional HMAC signature - A2A Webhooks: Task or TaskStatusUpdateEvent objects based on status The method automatically detects the protocol type and routes to the appropriate handler. Both protocols return a consistent TaskResult structure with typed AdCP response data. Args: payload: Webhook payload - one of: - dict[str, Any]: MCP webhook payload from HTTP POST - Task: A2A webhook for terminated statuses (completed, failed) - TaskStatusUpdateEvent: A2A webhook for intermediate statuses (working, input-required, submitted) task_type: Task type from application routing (e.g., "get_products"). Applications should extract this from URL routing pattern: /webhook/{task_type}/{agent_id}/{operation_id} operation_id: Operation identifier from application routing. Used to correlate webhook notifications with original task submission. signature: Optional HMAC-SHA256 signature for MCP webhook verification (X-AdCP-Signature header). Ignored for A2A webhooks. timestamp: Optional Unix timestamp (seconds) for MCP webhook signature verification (X-AdCP-Timestamp header). Required when signature is provided. raw_body: Optional raw HTTP request body bytes for signature verification. When provided, used directly instead of re-serializing the payload, avoiding cross-language JSON serialization mismatches. Strongly recommended for production use. Returns: TaskResult with parsed task-specific response data. The structure is identical regardless of protocol. Raises: ADCPWebhookSignatureError: If MCP signature verification fails ValidationError: If MCP payload doesn't match WebhookPayload schema Note: task_type and operation_id were deprecated from the webhook payload per AdCP specification. Applications must extract these from URL routing and pass them explicitly. Examples: MCP webhook (HTTP endpoint): >>> @app.post("/webhook/{task_type}/{agent_id}/{operation_id}") >>> async def webhook_handler(task_type: str, operation_id: str, request: Request): >>> raw_body = await request.body() >>> payload = json.loads(raw_body) >>> signature = request.headers.get("X-AdCP-Signature") >>> timestamp = request.headers.get("X-AdCP-Timestamp") >>> result = await client.handle_webhook( >>> payload, task_type, operation_id, signature, timestamp, >>> raw_body=raw_body, >>> ) >>> if result.success: >>> print(f"Task completed: {result.data}") A2A webhook with Task (terminated status): >>> async def on_task_completed(task: Task): >>> # Extract task_type and operation_id from your app's task tracking >>> task_type = your_task_registry.get_type(task.id) >>> operation_id = your_task_registry.get_operation_id(task.id) >>> result = await client.handle_webhook( >>> task, task_type, operation_id >>> ) >>> if result.success: >>> print(f"Task completed: {result.data}") A2A webhook with TaskStatusUpdateEvent (intermediate status): >>> async def on_task_update(event: TaskStatusUpdateEvent): >>> # Extract task_type and operation_id from your app's task tracking >>> task_type = your_task_registry.get_type(event.task_id) >>> operation_id = your_task_registry.get_operation_id(event.task_id) >>> result = await client.handle_webhook( >>> event, task_type, operation_id >>> ) >>> if result.status == GeneratedTaskStatus.working: >>> print(f"Task still working: {result.metadata.get('message')}") """ # Detect protocol type and route to appropriate handler if isinstance(payload, (Task, TaskStatusUpdateEvent)): # A2A webhook (Task or TaskStatusUpdateEvent) return await self._handle_a2a_webhook(payload, task_type, operation_id) else: # MCP webhook (dict payload) return await self._handle_mcp_webhook( payload, task_type, operation_id, signature, timestamp, raw_body )Handle incoming webhook and return typed result.
This method provides a unified interface for handling webhooks from both MCP and A2A protocols:
- MCP Webhooks: HTTP POST with dict payload, optional HMAC signature
- A2A Webhooks: Task or TaskStatusUpdateEvent objects based on status
The method automatically detects the protocol type and routes to the appropriate handler. Both protocols return a consistent TaskResult structure with typed AdCP response data.
Args
payload- Webhook payload - one of: - dict[str, Any]: MCP webhook payload from HTTP POST - Task: A2A webhook for terminated statuses (completed, failed) - TaskStatusUpdateEvent: A2A webhook for intermediate statuses (working, input-required, submitted)
task_type- Task type from application routing (e.g., "get_products"). Applications should extract this from URL routing pattern: /webhook/{task_type}/{agent_id}/{operation_id}
operation_id- Operation identifier from application routing. Used to correlate webhook notifications with original task submission.
signature- Optional HMAC-SHA256 signature for MCP webhook verification (X-AdCP-Signature header). Ignored for A2A webhooks.
timestamp- Optional Unix timestamp (seconds) for MCP webhook signature verification (X-AdCP-Timestamp header). Required when signature is provided.
raw_body- Optional raw HTTP request body bytes for signature verification. When provided, used directly instead of re-serializing the payload, avoiding cross-language JSON serialization mismatches. Strongly recommended for production use.
Returns
TaskResult with parsed task-specific response data. The structure is identical regardless of protocol.
Raises
ADCPWebhookSignatureError- If MCP signature verification fails
ValidationError- If MCP payload doesn't match WebhookPayload schema
Note
task_type and operation_id were deprecated from the webhook payload per AdCP specification. Applications must extract these from URL routing and pass them explicitly.
Examples
MCP webhook (HTTP endpoint):
>>> @app.post("/webhook/{task_type}/{agent_id}/{operation_id}") >>> async def webhook_handler(task_type: str, operation_id: str, request: Request): >>> raw_body = await request.body() >>> payload = json.loads(raw_body) >>> signature = request.headers.get("X-AdCP-Signature") >>> timestamp = request.headers.get("X-AdCP-Timestamp") >>> result = await client.handle_webhook( >>> payload, task_type, operation_id, signature, timestamp, >>> raw_body=raw_body, >>> ) >>> if result.success: >>> print(f"Task completed: {result.data}")A2A webhook with Task (terminated status):
>>> async def on_task_completed(task: Task): >>> # Extract task_type and operation_id from your app's task tracking >>> task_type = your_task_registry.get_type(task.id) >>> operation_id = your_task_registry.get_operation_id(task.id) >>> result = await client.handle_webhook( >>> task, task_type, operation_id >>> ) >>> if result.success: >>> print(f"Task completed: {result.data}")A2A webhook with TaskStatusUpdateEvent (intermediate status):
>>> async def on_task_update(event: TaskStatusUpdateEvent): >>> # Extract task_type and operation_id from your app's task tracking >>> task_type = your_task_registry.get_type(event.task_id) >>> operation_id = your_task_registry.get_operation_id(event.task_id) >>> result = await client.handle_webhook( >>> event, task_type, operation_id >>> ) >>> if result.status == GeneratedTaskStatus.working: >>> print(f"Task still working: {result.metadata.get('message')}") async def identity_match(self,
request: IdentityMatchRequest) ‑> TaskResult[IdentityMatchResponse]-
Expand source code
async def identity_match( self, request: IdentityMatchRequest, ) -> TaskResult[IdentityMatchResponse]: """Match user identity for package eligibility. Evaluates a user identity token against all active packages for frequency capping and personalization. Args: request: Identity match request with user_token, uid_type, and package_ids. Returns: TaskResult containing IdentityMatchResponse with eligible_package_ids. """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True, by_alias=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="identity_match", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.identity_match(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="identity_match", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, IdentityMatchResponse)Match user identity for package eligibility.
Evaluates a user identity token against all active packages for frequency capping and personalization.
Args
request- Identity match request with user_token, uid_type, and package_ids.
Returns
TaskResult containing IdentityMatchResponse with eligible_package_ids.
async def list_accounts(self,
request: ListAccountsRequest) ‑> TaskResult[ListAccountsResponse]-
Expand source code
async def list_accounts( self, request: ListAccountsRequest, ) -> TaskResult[ListAccountsResponse]: """ List Accounts. Args: request: Request parameters Returns: TaskResult containing ListAccountsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_accounts", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_accounts(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_accounts", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListAccountsResponse)List Accounts.
Args
request- Request parameters
Returns
TaskResult containing ListAccountsResponse
async def list_collection_lists(self, request: ListCollectionListsRequest) ‑> TaskResult[ListCollectionListsResponse]-
Expand source code
async def list_collection_lists( self, request: ListCollectionListsRequest, ) -> TaskResult[ListCollectionListsResponse]: """List collection lists owned by a principal. Args: request: Request parameters with optional filtering Returns: TaskResult containing ListCollectionListsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_collection_lists", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_collection_lists(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_collection_lists", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListCollectionListsResponse)List collection lists owned by a principal.
Args
request- Request parameters with optional filtering
Returns
TaskResult containing ListCollectionListsResponse
async def list_content_standards(self, request: ListContentStandardsRequest) ‑> TaskResult[ListContentStandardsResponse]-
Expand source code
async def list_content_standards( self, request: ListContentStandardsRequest, ) -> TaskResult[ListContentStandardsResponse]: """ List content standards configurations. Args: request: Request parameters including optional filters Returns: TaskResult containing ListContentStandardsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_content_standards", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_content_standards(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_content_standards", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListContentStandardsResponse)List content standards configurations.
Args
request- Request parameters including optional filters
Returns
TaskResult containing ListContentStandardsResponse
async def list_creative_formats(self,
request: ListCreativeFormatsRequest,
fetch_previews: bool = False,
preview_output_format: str = 'url') ‑> TaskResult[ListCreativeFormatsResponse]-
Expand source code
async def list_creative_formats( self, request: ListCreativeFormatsRequest, fetch_previews: bool = False, preview_output_format: str = "url", ) -> TaskResult[ListCreativeFormatsResponse]: """ List supported creative formats. Args: request: Request parameters fetch_previews: If True, generate preview URLs for each format using sample manifests (uses batch API for 5-10x performance improvement) preview_output_format: "url" for iframe URLs (default), "html" for direct embedding (2-3x faster, no iframe overhead) Returns: TaskResult containing ListCreativeFormatsResponse with optional preview URLs in metadata """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_creative_formats", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_creative_formats(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_creative_formats", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) result: TaskResult[ListCreativeFormatsResponse] = self.adapter._parse_response( raw_result, ListCreativeFormatsResponse ) if fetch_previews and result.success and result.data: from adcp.utils.preview_cache import add_preview_urls_to_formats formats_with_previews = await add_preview_urls_to_formats( result.data.formats, self, use_batch=True, output_format=preview_output_format, ) result.metadata = result.metadata or {} result.metadata["formats_with_previews"] = formats_with_previews return resultList supported creative formats.
Args
request- Request parameters
fetch_previews- If True, generate preview URLs for each format using sample manifests (uses batch API for 5-10x performance improvement)
preview_output_format- "url" for iframe URLs (default), "html" for direct embedding (2-3x faster, no iframe overhead)
Returns
TaskResult containing ListCreativeFormatsResponse with optional preview URLs in metadata
async def list_creatives(self,
request: ListCreativesRequest) ‑> TaskResult[ListCreativesResponse]-
Expand source code
async def list_creatives( self, request: ListCreativesRequest, ) -> TaskResult[ListCreativesResponse]: """ List Creatives. Args: request: Request parameters Returns: TaskResult containing ListCreativesResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_creatives", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_creatives(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_creatives", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListCreativesResponse)List Creatives.
Args
request- Request parameters
Returns
TaskResult containing ListCreativesResponse
async def list_property_lists(self, request: ListPropertyListsRequest) ‑> TaskResult[ListPropertyListsResponse]-
Expand source code
async def list_property_lists( self, request: ListPropertyListsRequest, ) -> TaskResult[ListPropertyListsResponse]: """ List property lists owned by a principal. Retrieves metadata for all property lists, optionally filtered by principal or pagination parameters. Args: request: Request parameters with optional filtering Returns: TaskResult containing ListPropertyListsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_property_lists", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_property_lists(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_property_lists", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListPropertyListsResponse)List property lists owned by a principal.
Retrieves metadata for all property lists, optionally filtered by principal or pagination parameters.
Args
request- Request parameters with optional filtering
Returns
TaskResult containing ListPropertyListsResponse
async def list_tasks(self,
request: ListTasksRequest) ‑> TaskResult[ListTasksResponse]-
Expand source code
async def list_tasks( self, request: ListTasksRequest, ) -> TaskResult[ListTasksResponse]: """ List Tasks. Args: request: Request parameters Returns: TaskResult containing ListTasksResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_tasks", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_tasks(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_tasks", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListTasksResponse)List Tasks.
Args
request- Request parameters
Returns
TaskResult containing ListTasksResponse
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 list_transformers(self,
request: ListTransformersRequestCreativeAgent) ‑> TaskResult[ListTransformersResponseCreativeAgent]-
Expand source code
async def list_transformers( self, request: ListTransformersRequest, ) -> TaskResult[ListTransformersResponse]: """ List Creative Transformers. Args: request: Request parameters Returns: TaskResult containing ListTransformersResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_transformers", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.list_transformers(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="list_transformers", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ListTransformersResponse)List Creative Transformers.
Args
request- Request parameters
Returns
TaskResult containing ListTransformersResponse
async def log_event(self,
request: LogEventRequest) ‑> TaskResult[Union[LogEventResponse1, LogEventResponse2]]-
Expand source code
async def log_event( self, request: LogEventRequest, ) -> TaskResult[LogEventResponse]: """ Log Event. Args: request: Request parameters Returns: TaskResult containing LogEventResponse """ self._validate_task_features("log_event") operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="log_event", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.log_event(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="log_event", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, LogEventResponse)Log Event.
Args
request- Request parameters
Returns
TaskResult containing LogEventResponse
async def preview_creative(self,
request: PreviewCreativeRequest) ‑> TaskResult[Union[PreviewCreativeResponse1, PreviewCreativeResponse2, PreviewCreativeResponse3]]-
Expand source code
async def preview_creative( self, request: PreviewCreativeRequest, ) -> TaskResult[PreviewCreativeResponse]: """ Generate preview of a creative manifest. Args: request: Request parameters Returns: TaskResult containing PreviewCreativeResponse with preview URLs """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="preview_creative", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.preview_creative(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="preview_creative", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, PreviewCreativeResponse)Generate preview of a creative manifest.
Args
request- Request parameters
Returns
TaskResult containing PreviewCreativeResponse with preview URLs
async def provide_performance_feedback(self,
request: ProvidePerformanceFeedbackRequest) ‑> TaskResult[Union[ProvidePerformanceFeedbackResponse1, ProvidePerformanceFeedbackResponse2]]-
Expand source code
async def provide_performance_feedback( self, request: ProvidePerformanceFeedbackRequest, ) -> TaskResult[ProvidePerformanceFeedbackResponse]: """ Provide Performance Feedback. Args: request: Request parameters Returns: TaskResult containing ProvidePerformanceFeedbackResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="provide_performance_feedback", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.provide_performance_feedback(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="provide_performance_feedback", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ProvidePerformanceFeedbackResponse)Provide Performance Feedback.
Args
request- Request parameters
Returns
TaskResult containing ProvidePerformanceFeedbackResponse
async def refresh_capabilities(self) ‑> adcp.types.generated_poc.protocol.get_adcp_capabilities_response.GetAdcpCapabilitiesResponse-
Expand source code
async def refresh_capabilities(self) -> GetAdcpCapabilitiesResponse: """Fetch capabilities from the seller, bypassing cache. On strict-schema validation failure the raw response is inspected with ``looks_like_v3_capabilities``: if the agent is structurally v3-shaped, a wire-shape bug is surfaced loudly with the original validation error rather than silently downgrading to v2 (the v2 fallback would then ask for v2.5 schemas, which aren't shipped — one missing field would cascade into "AdCP schema data for version v2.5 not found"). Genuinely non-v3 responses still fall through to the transport-error path. Returns: The seller's capabilities response. Raises: ADCPError: On transport failure, or when the response is v3-shaped but fails schema validation. The error message explicitly references v3 in the latter case so the underlying wire-shape bug doesn't get blamed on a v2.5-schema cascade. """ result = await self.get_adcp_capabilities(GetAdcpCapabilitiesRequest()) if result.success and result.data is not None: self._capabilities = result.data self._feature_resolver = FeatureResolver(result.data) self._capabilities_fetched_at = time.monotonic() return self._capabilities # The typed call discards the raw payload on parse failure (only the # error string survives). Distinguish parse-failure (worth shape- # checking) from transport-failure (no data ever arrived) by the # error prefix produced by ProtocolAdapter._parse_response. Only on # parse-failure do we re-fetch the raw dict from the adapter to # inspect its shape; transport failures fall straight through to # the original error path. raw_data: Any = None is_parse_failure = result.error is not None and result.error.startswith( "Failed to parse response:" ) if is_parse_failure: raw_result = await self.adapter.get_adcp_capabilities( GetAdcpCapabilitiesRequest().model_dump(mode="json", exclude_none=True) ) raw_data = raw_result.data if isinstance(raw_data, list) and len(raw_data) == 1 and isinstance(raw_data[0], dict): # MCP content array — unwrap a single-item content envelope # so the heuristic sees the same shape the parser would. raw_data = raw_data[0] if looks_like_v3_capabilities(raw_data): logger.warning( "[AdCP] Agent %r returned a get_adcp_capabilities response that " "failed validation, but the response is structurally v3-shaped. " "The agent has a wire-shape bug — that's the thing to fix. " "(has_error=%s, has_data=%s)", self.agent_config.id, bool(result.error), raw_data is not None, ) raise ADCPError( f"v3 capabilities response from agent {self.agent_config.id!r} " f"failed schema validation: {result.error or result.message}. " f"The response is structurally v3-shaped (carries `adcp`, " f"`supported_protocols`, or a v3 protocol block) — fix the " f"agent's wire shape rather than downgrading to v2.", agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, ) raise ADCPError( f"Failed to fetch capabilities: {result.error or result.message}", agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, )Fetch capabilities from the seller, bypassing cache.
On strict-schema validation failure the raw response is inspected with
looks_like_v3_capabilities: if the agent is structurally v3-shaped, a wire-shape bug is surfaced loudly with the original validation error rather than silently downgrading to v2 (the v2 fallback would then ask for v2.5 schemas, which aren't shipped — one missing field would cascade into "AdCP schema data for version v2.5 not found"). Genuinely non-v3 responses still fall through to the transport-error path.Returns
The seller's capabilities response.
Raises
ADCPError- On transport failure, or when the response is v3-shaped but fails schema validation. The error message explicitly references v3 in the latter case so the underlying wire-shape bug doesn't get blamed on a v2.5-schema cascade.
async def report_plan_outcome(self,
request: ReportPlanOutcomeRequest) ‑> TaskResult[ReportPlanOutcomeResponse]-
Expand source code
async def report_plan_outcome( self, request: ReportPlanOutcomeRequest, ) -> TaskResult[ReportPlanOutcomeResponse]: """Report the outcome of a governed action to the governance agent.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="report_plan_outcome", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.report_plan_outcome(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="report_plan_outcome", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ReportPlanOutcomeResponse)Report the outcome of a governed action to the governance agent.
async def report_usage(self,
request: ReportUsageRequest) ‑> TaskResult[ReportUsageResponse]-
Expand source code
async def report_usage( self, request: ReportUsageRequest, ) -> TaskResult[ReportUsageResponse]: """ Report Usage. Args: request: Request parameters Returns: TaskResult containing ReportUsageResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="report_usage", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.report_usage(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="report_usage", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ReportUsageResponse)Report Usage.
Args
request- Request parameters
Returns
TaskResult containing ReportUsageResponse
def require(self, *features: str) ‑> None-
Expand source code
def require(self, *features: str) -> None: """Assert that the seller supports all listed features. Args: *features: Feature identifiers to require. Raises: ADCPFeatureUnsupportedError: If any features are not supported. ADCPError: If capabilities have not been fetched yet. """ self._ensure_resolver().require( *features, agent_id=self.agent_config.id, agent_uri=self.agent_config.agent_uri, )Assert that the seller supports all listed features.
Args
*features- Feature identifiers to require.
Raises
ADCPFeatureUnsupportedError- If any features are not supported.
ADCPError- If capabilities have not been fetched yet.
def reset_context(self, context_id: str | None = None) ‑> None-
Expand source code
def reset_context(self, context_id: str | None = None) -> None: """Start a new A2A conversation on this client. Passing ``None`` (default) clears the current context so the server mints a fresh one on the next call. Passing a string uses it as the new conversation id — useful for resuming a specific prior session or for naming the conversation with your own correlation key. Note: some servers (notably ADK) rewrite client-supplied ids into their own session format; the client auto-adopts the rewritten id on the next response. Also clears any active_task_id — starting a new conversation discards any in-flight task on the old one. Raises ``TypeError`` when called on a non-A2A client. """ if not isinstance(self.adapter, A2AAdapter): raise TypeError( f"reset_context is only supported for A2A protocol; " f"got {self.agent_config.protocol}" ) self.adapter.set_context_id(context_id)Start a new A2A conversation on this client.
Passing
None(default) clears the current context so the server mints a fresh one on the next call. Passing a string uses it as the new conversation id — useful for resuming a specific prior session or for naming the conversation with your own correlation key. Note: some servers (notably ADK) rewrite client-supplied ids into their own session format; the client auto-adopts the rewritten id on the next response.Also clears any active_task_id — starting a new conversation discards any in-flight task on the old one.
Raises
TypeErrorwhen called on a non-A2A client. async def si_get_offering(self, request: SiGetOfferingRequest) ‑> TaskResult[SiGetOfferingResponse]-
Expand source code
async def si_get_offering( self, request: SiGetOfferingRequest, ) -> TaskResult[SiGetOfferingResponse]: """ Get sponsored intelligence offering. Retrieves product/service offerings that can be presented in a sponsored intelligence session. Args: request: Request parameters including brand context Returns: TaskResult containing SiGetOfferingResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_get_offering", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.si_get_offering(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_get_offering", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SiGetOfferingResponse)Get sponsored intelligence offering.
Retrieves product/service offerings that can be presented in a sponsored intelligence session.
Args
request- Request parameters including brand context
Returns
TaskResult containing SiGetOfferingResponse
async def si_initiate_session(self, request: SiInitiateSessionRequest) ‑> TaskResult[SiInitiateSessionResponse]-
Expand source code
async def si_initiate_session( self, request: SiInitiateSessionRequest, ) -> TaskResult[SiInitiateSessionResponse]: """ Initiate a sponsored intelligence session. Starts a conversational brand experience session with a user. Args: request: Request parameters including identity and context Returns: TaskResult containing SiInitiateSessionResponse with session_id """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_initiate_session", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.si_initiate_session(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_initiate_session", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SiInitiateSessionResponse)Initiate a sponsored intelligence session.
Starts a conversational brand experience session with a user.
Args
request- Request parameters including identity and context
Returns
TaskResult containing SiInitiateSessionResponse with session_id
async def si_send_message(self,
request: SiSendMessageRequest) ‑> TaskResult[SiSendMessageResponse]-
Expand source code
async def si_send_message( self, request: SiSendMessageRequest, ) -> TaskResult[SiSendMessageResponse]: """ Send a message in a sponsored intelligence session. Continues the conversation in an active SI session. Args: request: Request parameters including session_id and message Returns: TaskResult containing SiSendMessageResponse with brand response """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_send_message", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.si_send_message(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_send_message", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SiSendMessageResponse)Send a message in a sponsored intelligence session.
Continues the conversation in an active SI session.
Args
request- Request parameters including session_id and message
Returns
TaskResult containing SiSendMessageResponse with brand response
async def si_terminate_session(self, request: SiTerminateSessionRequest) ‑> TaskResult[SiTerminateSessionResponse]-
Expand source code
async def si_terminate_session( self, request: SiTerminateSessionRequest, ) -> TaskResult[SiTerminateSessionResponse]: """ Terminate a sponsored intelligence session. Ends an active SI session, optionally with follow-up actions. Args: request: Request parameters including session_id and termination context Returns: TaskResult containing SiTerminateSessionResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_terminate_session", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.si_terminate_session(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="si_terminate_session", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SiTerminateSessionResponse)Terminate a sponsored intelligence session.
Ends an active SI session, optionally with follow-up actions.
Args
request- Request parameters including session_id and termination context
Returns
TaskResult containing SiTerminateSessionResponse
def supports(self, feature: str) ‑> bool-
Expand source code
def supports(self, feature: str) -> bool: """Check if the seller supports a feature. Supports multiple feature namespaces: - Protocol support: ``supports("media_buy")`` checks ``supported_protocols`` - Extension support: ``supports("ext:scope3")`` checks ``extensions_supported`` - Targeting: ``supports("targeting.geo_countries")`` checks ``media_buy.execution.targeting`` - Media buy features: ``supports("audience_targeting")`` checks ``media_buy.features`` - Signals features: ``supports("catalog_signals")`` checks ``signals.features`` Args: feature: Feature identifier to check. Returns: True if the seller declares the feature as supported. Raises: ADCPError: If capabilities have not been fetched yet. """ return self._ensure_resolver().supports(feature)Check if the seller supports a feature.
Supports multiple feature namespaces: - Protocol support:
supports("media_buy")checkssupported_protocols- Extension support:supports("ext:scope3")checksextensions_supported- Targeting:supports("targeting.geo_countries")checksmedia_buy.execution.targeting- Media buy features:supports("audience_targeting")checksmedia_buy.features- Signals features:supports("catalog_signals")checkssignals.featuresArgs
feature- Feature identifier to check.
Returns
True if the seller declares the feature as supported.
Raises
ADCPError- If capabilities have not been fetched yet.
async def sync_accounts(self,
request: SyncAccountsRequest) ‑> TaskResult[Union[SyncAccountsResponse1, SyncAccountsResponse2]]-
Expand source code
async def sync_accounts( self, request: SyncAccountsRequest, ) -> TaskResult[SyncAccountsResponse]: """ Sync Accounts. Args: request: Request parameters Returns: TaskResult containing SyncAccountsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_accounts", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_accounts(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_accounts", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncAccountsResponse)Sync Accounts.
Args
request- Request parameters
Returns
TaskResult containing SyncAccountsResponse
async def sync_audiences(self,
request: SyncAudiencesRequest) ‑> TaskResult[Union[SyncAudiencesResponse1, SyncAudiencesResponse2, SyncAudiencesResponse3]]-
Expand source code
async def sync_audiences( self, request: SyncAudiencesRequest, ) -> TaskResult[SyncAudiencesResponse]: """ Sync Audiences. Args: request: Request parameters Returns: TaskResult containing SyncAudiencesResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_audiences", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_audiences(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_audiences", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncAudiencesResponse)Sync Audiences.
Args
request- Request parameters
Returns
TaskResult containing SyncAudiencesResponse
async def sync_catalogs(self,
request: SyncCatalogsRequest) ‑> TaskResult[Union[SyncCatalogsResponse1, SyncCatalogsResponse2, SyncCatalogsResponse3]]-
Expand source code
async def sync_catalogs( self, request: SyncCatalogsRequest, ) -> TaskResult[SyncCatalogsResponse]: """ Sync Catalogs. Args: request: Request parameters Returns: TaskResult containing SyncCatalogsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_catalogs", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_catalogs(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_catalogs", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncCatalogsResponse)Sync Catalogs.
Args
request- Request parameters
Returns
TaskResult containing SyncCatalogsResponse
async def sync_creatives(self,
request: SyncCreativesRequest) ‑> TaskResult[Union[SyncCreativesResponse1, SyncCreativesResponse2, SyncCreativesResponse3]]-
Expand source code
async def sync_creatives( self, request: SyncCreativesRequest, ) -> TaskResult[SyncCreativesResponse]: """ Sync Creatives. Args: request: Request parameters Returns: TaskResult containing SyncCreativesResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_creatives", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_creatives(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_creatives", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncCreativesResponse)Sync Creatives.
Args
request- Request parameters
Returns
TaskResult containing SyncCreativesResponse
async def sync_event_sources(self,
request: SyncEventSourcesRequest) ‑> TaskResult[Union[SyncEventSourcesResponse1, SyncEventSourcesResponse2]]-
Expand source code
async def sync_event_sources( self, request: SyncEventSourcesRequest, ) -> TaskResult[SyncEventSourcesResponse]: """ Sync Event Sources. Args: request: Request parameters Returns: TaskResult containing SyncEventSourcesResponse """ self._validate_task_features("sync_event_sources") operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_event_sources", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_event_sources(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_event_sources", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncEventSourcesResponse)Sync Event Sources.
Args
request- Request parameters
Returns
TaskResult containing SyncEventSourcesResponse
async def sync_governance(self, request: SyncGovernanceRequest) ‑> TaskResult[SyncGovernanceResponse]-
Expand source code
async def sync_governance( self, request: SyncGovernanceRequest, ) -> TaskResult[SyncGovernanceResponse]: """Sync governance agents attached to an account. Attach, detach, or replace the set of governance agents that must be consulted for plan approval on an account. Args: request: Request parameters with account and governance agents Returns: TaskResult containing SyncGovernanceResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_governance", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_governance(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_governance", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncGovernanceResponse)Sync governance agents attached to an account.
Attach, detach, or replace the set of governance agents that must be consulted for plan approval on an account.
Args
request- Request parameters with account and governance agents
Returns
TaskResult containing SyncGovernanceResponse
async def sync_plans(self,
request: SyncPlansRequest) ‑> TaskResult[SyncPlansResponse]-
Expand source code
async def sync_plans( self, request: SyncPlansRequest, ) -> TaskResult[SyncPlansResponse]: """Sync campaign governance plans to the governance agent.""" operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_plans", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.sync_plans(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="sync_plans", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, SyncPlansResponse)Sync campaign governance plans to the governance agent.
async def update_collection_list(self, request: UpdateCollectionListRequest) ‑> TaskResult[UpdateCollectionListResponse]-
Expand source code
async def update_collection_list( self, request: UpdateCollectionListRequest, ) -> TaskResult[UpdateCollectionListResponse]: """Update a collection list. Args: request: Request parameters with list_id and updates Returns: TaskResult containing UpdateCollectionListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_collection_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_collection_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_collection_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdateCollectionListResponse)Update a collection list.
Args
request- Request parameters with list_id and updates
Returns
TaskResult containing UpdateCollectionListResponse
async def update_content_standards(self, request: UpdateContentStandardsRequest) ‑> TaskResult[UpdateContentStandardsResponse]-
Expand source code
async def update_content_standards( self, request: UpdateContentStandardsRequest, ) -> TaskResult[UpdateContentStandardsResponse]: """ Update a content standards configuration. Args: request: Request parameters including standards_id and updates Returns: TaskResult containing UpdateContentStandardsResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_content_standards", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_content_standards(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_content_standards", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdateContentStandardsResponse)Update a content standards configuration.
Args
request- Request parameters including standards_id and updates
Returns
TaskResult containing UpdateContentStandardsResponse
async def update_media_buy(self,
request: UpdateMediaBuyRequest) ‑> TaskResult[Union[UpdateMediaBuyResponse1, UpdateMediaBuyResponse2, UpdateMediaBuyResponse3]]-
Expand source code
async def update_media_buy( self, request: UpdateMediaBuyRequest, ) -> TaskResult[UpdateMediaBuyResponse]: """ Update an existing media buy reservation. Modifies a previously created media buy by updating packages or publisher properties. The update operation uses discriminated unions to specify what to change - either package details or targeting properties. Args: request: Media buy update parameters including: - media_buy_id: Identifier from create_media_buy response - updates: Discriminated union specifying update type: * UpdateMediaBuyPackagesRequest: Modify package selections * UpdateMediaBuyPropertiesRequest: Change targeting properties Returns: TaskResult containing UpdateMediaBuyResponse with: - media_buy_id: The updated media buy identifier - status: Updated state of the media buy - packages: Updated package configurations - Additional platform-specific metadata Example: >>> from adcp import ADCPClient, UpdateMediaBuyPackagesRequest >>> client = ADCPClient(agent_config) >>> request = UpdateMediaBuyPackagesRequest( ... media_buy_id="mb_123", ... packages=[updated_package] ... ) >>> result = await client.update_media_buy(request) >>> if result.success: ... updated_packages = result.data.packages """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_media_buy", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_media_buy(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_media_buy", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdateMediaBuyResponse)Update an existing media buy reservation.
Modifies a previously created media buy by updating packages or publisher properties. The update operation uses discriminated unions to specify what to change - either package details or targeting properties.
Args
request- Media buy update parameters including: - media_buy_id: Identifier from create_media_buy response - updates: Discriminated union specifying update type: * UpdateMediaBuyPackagesRequest: Modify package selections * UpdateMediaBuyPropertiesRequest: Change targeting properties
Returns
TaskResult containing UpdateMediaBuyResponse with: - media_buy_id: The updated media buy identifier - status: Updated state of the media buy - packages: Updated package configurations - Additional platform-specific metadata
Example
>>> from adcp import ADCPClient, UpdateMediaBuyPackagesRequest >>> client = ADCPClient(agent_config) >>> request = UpdateMediaBuyPackagesRequest( ... media_buy_id="mb_123", ... packages=[updated_package] ... ) >>> result = await client.update_media_buy(request) >>> if result.success: ... updated_packages = result.data.packages async def update_property_list(self, request: UpdatePropertyListRequest) ‑> TaskResult[UpdatePropertyListResponse]-
Expand source code
async def update_property_list( self, request: UpdatePropertyListRequest, ) -> TaskResult[UpdatePropertyListResponse]: """ Update a property list. Modifies the filters, brand manifest, or other parameters of an existing property list. Args: request: Request parameters with list_id and updates Returns: TaskResult containing UpdatePropertyListResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_property_list", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_property_list(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_property_list", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdatePropertyListResponse)Update a property list.
Modifies the filters, brand manifest, or other parameters of an existing property list.
Args
request- Request parameters with list_id and updates
Returns
TaskResult containing UpdatePropertyListResponse
async def update_rights(self, request: UpdateRightsRequest) ‑> TaskResult[Union[UpdateRightsResponse1, UpdateRightsResponse2]]-
Expand source code
async def update_rights( self, request: UpdateRightsRequest, ) -> TaskResult[UpdateRightsResponse]: """Update terms of an existing rights acquisition. Modifies a previously acquired rights record — typically to extend the ``end_date``, raise the ``impression_cap``, pause/unpause via ``paused``, or swap to a compatible ``pricing_option_id``. Partial update: pass only the fields you want to change. Failure modes (surface as ``TaskResult`` with ``success=False``): * Acquisition is expired or revoked — the seller rejects the update outright; mint a fresh ``acquire_rights`` instead. * ``pricing_option_id`` swap to an incompatible option — rejected; the new option's terms must be a strict superset / compatible with the original acquisition. * No partial-state mutations on rejection: the acquisition remains at its prior state when any field fails validation. Args: request: Request with ``rights_id`` and at least one mutable field (``end_date``, ``impression_cap``, ``paused``, or ``pricing_option_id``). Returns: TaskResult containing UpdateRightsResponse (updated or error). """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_rights", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.update_rights(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="update_rights", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, UpdateRightsResponse)Update terms of an existing rights acquisition.
Modifies a previously acquired rights record — typically to extend the
end_date, raise theimpression_cap, pause/unpause viapaused, or swap to a compatiblepricing_option_id. Partial update: pass only the fields you want to change.Failure modes (surface as
TaskResultwithsuccess=False):- Acquisition is expired or revoked — the seller rejects the update
outright; mint a fresh
acquire_rightsinstead. pricing_option_idswap to an incompatible option — rejected; the new option's terms must be a strict superset / compatible with the original acquisition.- No partial-state mutations on rejection: the acquisition remains at its prior state when any field fails validation.
Args
request- Request with
rights_idand at least one mutable field (end_date,impression_cap,paused, orpricing_option_id).
Returns
TaskResult containing UpdateRightsResponse (updated or error).
- Acquisition is expired or revoked — the seller rejects the update
outright; mint a fresh
def use_idempotency_key(self, key: str) ‑> Iterator[str]-
Expand source code
@contextlib.contextmanager def use_idempotency_key(self, key: str) -> Iterator[str]: """Pin an ``idempotency_key`` for the next mutating call on THIS client. Use when you've persisted a key (e.g., in a buyer-side database) and want the SDK to send that exact key on resume or retry across process restarts. The key is validated against ``^[A-Za-z0-9_.:-]{16,255}$`` on entry; a ``ValueError`` is raised for malformed keys. Scope rules: * **Single-use within scope.** The first mutating call inside the ``with`` block consumes the pinned key; a second mutating call falls through to a fresh UUID. This protects against ``asyncio.gather`` siblings accidentally sharing the key (which would trigger ``IDEMPOTENCY_CONFLICT`` or silently duplicate work). If you need to retry, wrap each attempt in its own ``with`` block. * **Client-scoped.** The pinned key applies only to calls on THIS client. A mutating call on a sibling ``ADCPClient`` inside the same ``with`` block generates a fresh key and emits a ``UserWarning`` — keys must be unique per (seller, request) pair (AdCP #2315). * **No nesting.** Nested ``use_idempotency_key`` on the same client raises ``RuntimeError``. Example:: with client.use_idempotency_key(campaign.stored_key): result = await client.create_media_buy(request) """ from adcp import _idempotency _idempotency.validate_key(key) token = self._idempotency_client_token if token in _idempotency._scoped_keys: raise RuntimeError( "use_idempotency_key is already active on this client; " "nested usage is not supported." ) _idempotency._scoped_keys[token] = key try: yield key finally: _idempotency._scoped_keys.pop(token, None)Pin an
idempotency_keyfor the next mutating call on THIS client.Use when you've persisted a key (e.g., in a buyer-side database) and want the SDK to send that exact key on resume or retry across process restarts. The key is validated against
^[A-Za-z0-9_.:-]{16,255}$on entry; aValueErroris raised for malformed keys.Scope rules:
- Single-use within scope. The first mutating call inside the
withblock consumes the pinned key; a second mutating call falls through to a fresh UUID. This protects againstasyncio.gathersiblings accidentally sharing the key (which would triggerIDEMPOTENCY_CONFLICTor silently duplicate work). If you need to retry, wrap each attempt in its ownwithblock. - Client-scoped. The pinned key applies only to calls on THIS
client. A mutating call on a sibling
ADCPClientinside the samewithblock generates a fresh key and emits aUserWarning— keys must be unique per (seller, request) pair (AdCP #2315). - No nesting. Nested
use_idempotency_keyon the same client raisesRuntimeError.
Example::
with client.use_idempotency_key(campaign.stored_key): result = await client.create_media_buy(request) - Single-use within scope. The first mutating call inside the
async def validate_content_delivery(self, request: ValidateContentDeliveryRequest) ‑> TaskResult[Union[ValidateContentDeliveryResponse1, ValidateContentDeliveryResponse2]]-
Expand source code
async def validate_content_delivery( self, request: ValidateContentDeliveryRequest, ) -> TaskResult[ValidateContentDeliveryResponse]: """ Validate content delivery against standards. Validates that ad delivery records comply with content standards. Args: request: Request parameters including delivery records Returns: TaskResult containing ValidateContentDeliveryResponse """ operation_id = create_operation_id() params = request.model_dump(mode="json", exclude_none=True) self._emit_activity( Activity( type=ActivityType.PROTOCOL_REQUEST, operation_id=operation_id, agent_id=self.agent_config.id, task_type="validate_content_delivery", timestamp=datetime.now(timezone.utc).isoformat(), ) ) raw_result = await self.adapter.validate_content_delivery(params) self._emit_activity( Activity( type=ActivityType.PROTOCOL_RESPONSE, operation_id=operation_id, agent_id=self.agent_config.id, task_type="validate_content_delivery", status=raw_result.status, timestamp=datetime.now(timezone.utc).isoformat(), ) ) return self.adapter._parse_response(raw_result, ValidateContentDeliveryResponse)Validate content delivery against standards.
Validates that ad delivery records comply with content standards.
Args
request- Request parameters including delivery records
Returns
TaskResult containing ValidateContentDeliveryResponse
async def validate_input(self, request: Any) ‑> TaskResult[Any]-
Expand source code
async def validate_input(self, request: Any) -> TaskResult[Any]: """Validate creative input against a format declaration.""" from adcp.types import _generated as gen params = request.model_dump(mode="json", exclude_none=True) raw_result = await self.adapter.validate_input(params) return self.adapter._parse_response(raw_result, gen.ValidateInputResponse)Validate creative input against a format declaration.
async def verify_brand_claim(self, request: Any) ‑> TaskResult[Any]-
Expand source code
async def verify_brand_claim(self, request: Any) -> TaskResult[Any]: """Verify a single brand claim.""" from adcp.types import _generated as gen params = request.model_dump(mode="json", exclude_none=True) raw_result = await self.adapter.verify_brand_claim(params) return self.adapter._parse_response(raw_result, gen.VerifyBrandClaimResponse)Verify a single brand claim.
async def verify_brand_claims(self, request: Any) ‑> TaskResult[Any]-
Expand source code
async def verify_brand_claims(self, request: Any) -> TaskResult[Any]: """Verify multiple brand claims.""" from adcp.types import _generated as gen params = request.model_dump(mode="json", exclude_none=True) raw_result = await self.adapter.verify_brand_claims(params) return self.adapter._parse_response(raw_result, gen.VerifyBrandClaimsResponseBulk)Verify multiple brand claims.
class ADCPConnectionError (message: str, agent_id: str | None = None, agent_uri: str | None = None)-
Expand source code
class ADCPConnectionError(ADCPError): """Connection to agent failed.""" def __init__(self, message: str, agent_id: str | None = None, agent_uri: str | None = None): """Initialize connection error.""" suggestion = ( "Check that the agent URI is correct and the agent is running.\n" " Try testing with: python -m adcp test --config <agent-id>" ) super().__init__(message, agent_id, agent_uri, suggestion) @property def is_retryable(self) -> bool: return TrueConnection to agent failed.
Initialize connection error.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class ADCPError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
suggestion: str | None = None)-
Expand source code
class ADCPError(Exception): """Base exception for all AdCP client errors.""" def __init__( self, message: str, agent_id: str | None = None, agent_uri: str | None = None, suggestion: str | None = None, ): """Initialize exception with context.""" self.message = message self.agent_id = agent_id self.agent_uri = agent_uri self.suggestion = suggestion full_message = message if agent_id: full_message = f"[Agent: {agent_id}] {full_message}" if agent_uri: full_message = f"{full_message}\n URI: {agent_uri}" if suggestion: full_message = f"{full_message}\n Suggestion: {suggestion}" super().__init__(full_message) @property def is_retryable(self) -> bool: """Whether this error is safe to retry.""" return FalseBase exception for all AdCP client errors.
Initialize exception with context.
Ancestors
- builtins.Exception
- builtins.BaseException
Subclasses
- ADCPAuthenticationError
- ADCPConnectionError
- ADCPFeatureUnsupportedError
- ADCPProtocolError
- ADCPSigningRequiredError
- ADCPSimpleAPIError
- ADCPTaskError
- ADCPTimeoutError
- ADCPToolNotFoundError
- ADCPWebhookError
- AdagentsValidationError
- ConfigurationError
- IdempotencyUnsupportedError
- RegistryError
Instance variables
prop is_retryable : bool-
Expand source code
@property def is_retryable(self) -> bool: """Whether this error is safe to retry.""" return FalseWhether this error is safe to retry.
class ADCPFeatureUnsupportedError (unsupported_features: list[str],
declared_features: list[str] | None = None,
agent_id: str | None = None,
agent_uri: str | None = None)-
Expand source code
class ADCPFeatureUnsupportedError(ADCPError): """Seller does not support one or more required features.""" def __init__( self, unsupported_features: list[str], declared_features: list[str] | None = None, agent_id: str | None = None, agent_uri: str | None = None, ): """Initialize feature unsupported error. Args: unsupported_features: Features that are not supported. declared_features: Features the seller does declare. agent_id: Optional agent ID for context. agent_uri: Optional agent URI for context. """ self.unsupported_features = unsupported_features self.declared_features = declared_features or [] missing = ", ".join(unsupported_features) message = f"Seller does not support: {missing}" suggestion = None if self.declared_features: declared = ", ".join(sorted(self.declared_features)) suggestion = f"Declared features: {declared}" super().__init__(message, agent_id, agent_uri, suggestion)Seller does not support one or more required features.
Initialize feature unsupported error.
Args
unsupported_features- Features that are not supported.
declared_features- Features the seller does declare.
agent_id- Optional agent ID for context.
agent_uri- Optional agent URI for context.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class ADCPMultiAgentClient (agents: list[AgentConfig],
webhook_url_template: str | None = None,
webhook_secret: str | None = None,
on_activity: Callable[[Activity], None] | None = None,
handlers: dict[str, Callable[..., Any]] | None = None,
signing: SigningConfig | None = None,
adcp_version: str | dict[str, str] | None = None)-
Expand source code
class ADCPMultiAgentClient: """Client for managing multiple AdCP agents.""" def __init__( self, agents: list[AgentConfig], webhook_url_template: str | None = None, webhook_secret: str | None = None, on_activity: Callable[[Activity], None] | None = None, handlers: dict[str, Callable[..., Any]] | None = None, signing: SigningConfig | None = None, adcp_version: str | dict[str, str] | None = None, ): """ Initialize multi-agent client. Args: agents: List of agent configurations webhook_url_template: Template for webhook URLs webhook_secret: Secret for webhook verification on_activity: Callback for activity events handlers: Task completion handlers signing: Optional RFC 9421 signing config forwarded to every per-agent ADCPClient. The same identity signs traffic to all agents. See ADCPClient.__init__ for details. adcp_version: AdCP protocol release pin. Three forms: - ``None`` (default): every per-agent ADCPClient resolves the SDK's compile-time pin. - ``str`` (e.g. ``"3.1"``): every agent uses this pin. - ``dict[str, str]`` (e.g. ``{"seller_a": "3.0", "seller_b": "3.1"}``): per-agent override map keyed by ``agent.id``. Agents missing from the map fall back to the SDK default — useful for holdco/multi-tenant operators where one seller is ahead of the others on the upgrade cadence. See ADCPClient.__init__ for per-instance semantics. Cross-major pins raise ConfigurationError at construction. """ # Per-agent map → resolve each pin individually for the dict form; # otherwise use the uniform pin for all agents. if isinstance(adcp_version, dict): self._adcp_version: str | None = None # mixed pins self._per_agent_versions: dict[str, str] = { agent_id: resolve_adcp_version(pin) for agent_id, pin in adcp_version.items() } default_pin = resolve_adcp_version(None) self.agents = { agent.id: ADCPClient( agent, webhook_url_template=webhook_url_template, webhook_secret=webhook_secret, on_activity=on_activity, signing=signing, adcp_version=self._per_agent_versions.get(agent.id, default_pin), ) for agent in agents } else: self._adcp_version = resolve_adcp_version(adcp_version) self._per_agent_versions = {} self.agents = { agent.id: ADCPClient( agent, webhook_url_template=webhook_url_template, webhook_secret=webhook_secret, on_activity=on_activity, signing=signing, adcp_version=self._adcp_version, ) for agent in agents } self.handlers = handlers or {} def get_adcp_version(self) -> str: """Return the AdCP protocol release pin for this multi-client. Returns the uniform pin when all agents share one. Raises :class:`ValueError` when agents have heterogeneous pins (the ``dict[str, str]`` constructor form) — in that case, query the per-agent pin via ``multi.agent(agent_id).get_adcp_version()``. """ if self._adcp_version is not None: return self._adcp_version # Heterogeneous: surface uniformly if all agents agree at runtime. versions = {client.get_adcp_version() for client in self.agents.values()} if len(versions) == 1: return next(iter(versions)) raise ValueError( "Multi-agent client has heterogeneous adcp_version pins; " "use multi.agent(agent_id).get_adcp_version() to read per-agent. " f"Pins by agent: { {a: c.get_adcp_version() for a, c in self.agents.items()} }" ) def agent(self, agent_id: str) -> ADCPClient: """Get client for specific agent.""" if agent_id not in self.agents: raise ValueError(f"Agent not found: {agent_id}") return self.agents[agent_id] @property def agent_ids(self) -> list[str]: """Get list of agent IDs.""" return list(self.agents.keys()) async def close(self) -> None: """Close all agent clients and clean up resources.""" import asyncio logger.debug("Closing all agent clients in multi-agent client") close_tasks = [client.close() for client in self.agents.values()] await asyncio.gather(*close_tasks, return_exceptions=True) async def __aenter__(self) -> ADCPMultiAgentClient: """Async context manager entry.""" return self async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: """Async context manager exit.""" await self.close() async def get_products( self, request: GetProductsRequest, ) -> list[TaskResult[GetProductsResponse]]: """ Execute get_products across all agents in parallel. Args: request: Request parameters Returns: List of TaskResults containing GetProductsResponse for each agent """ import asyncio tasks = [agent.get_products(request) for agent in self.agents.values()] return await asyncio.gather(*tasks) @classmethod def from_env(cls) -> ADCPMultiAgentClient: """Create client from environment variables.""" agents_json = os.getenv("ADCP_AGENTS") if not agents_json: raise ValueError("ADCP_AGENTS environment variable not set") agents_data = json.loads(agents_json) agents = [AgentConfig(**agent) for agent in agents_data] return cls( agents=agents, webhook_url_template=os.getenv("WEBHOOK_URL_TEMPLATE"), webhook_secret=os.getenv("WEBHOOK_SECRET"), )Client for managing multiple AdCP agents.
Initialize multi-agent client.
Args
agents- List of agent configurations
webhook_url_template- Template for webhook URLs
webhook_secret- Secret for webhook verification
on_activity- Callback for activity events
handlers- Task completion handlers
signing- Optional RFC 9421 signing config forwarded to every per-agent ADCPClient. The same identity signs traffic to all agents. See ADCPClient.init for details.
adcp_version-
AdCP protocol release pin. Three forms:
None(default): every per-agent ADCPClient resolves the SDK's compile-time pin.str(e.g."3.1"): every agent uses this pin.dict[str, str](e.g.{"seller_a": "3.0", "seller_b": "3.1"}): per-agent override map keyed byagent.id. Agents missing from the map fall back to the SDK default — useful for holdco/multi-tenant operators where one seller is ahead of the others on the upgrade cadence.
See ADCPClient.init for per-instance semantics. Cross-major pins raise ConfigurationError at construction.
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.
def get_adcp_version(self) ‑> str-
Expand source code
def get_adcp_version(self) -> str: """Return the AdCP protocol release pin for this multi-client. Returns the uniform pin when all agents share one. Raises :class:`ValueError` when agents have heterogeneous pins (the ``dict[str, str]`` constructor form) — in that case, query the per-agent pin via ``multi.agent(agent_id).get_adcp_version()``. """ if self._adcp_version is not None: return self._adcp_version # Heterogeneous: surface uniformly if all agents agree at runtime. versions = {client.get_adcp_version() for client in self.agents.values()} if len(versions) == 1: return next(iter(versions)) raise ValueError( "Multi-agent client has heterogeneous adcp_version pins; " "use multi.agent(agent_id).get_adcp_version() to read per-agent. " f"Pins by agent: { {a: c.get_adcp_version() for a, c in self.agents.items()} }" )Return the AdCP protocol release pin for this multi-client.
Returns the uniform pin when all agents share one. Raises :class:
ValueErrorwhen agents have heterogeneous pins (thedict[str, str]constructor form) — in that case, query the per-agent pin viamulti.agent(agent_id).get_adcp_version(). async def get_products(self,
request: GetProductsRequest) ‑> list[TaskResult[GetProductsResponse]]-
Expand source code
async def get_products( self, request: GetProductsRequest, ) -> list[TaskResult[GetProductsResponse]]: """ Execute get_products across all agents in parallel. Args: request: Request parameters Returns: List of TaskResults containing GetProductsResponse for each agent """ import asyncio tasks = [agent.get_products(request) for agent in self.agents.values()] return await asyncio.gather(*tasks)Execute get_products across all agents in parallel.
Args
request- Request parameters
Returns
List of TaskResults containing GetProductsResponse for each agent
class ADCPProtocolError (message: str, agent_id: str | None = None, protocol: str | None = None)-
Expand source code
class ADCPProtocolError(ADCPError): """Protocol-level error (malformed response, unexpected format).""" def __init__(self, message: str, agent_id: str | None = None, protocol: str | None = None): """Initialize protocol error.""" suggestion = ( f"The agent returned an unexpected {protocol} response format." if protocol else "Unexpected response format." ) suggestion += "\n Enable debug mode to see the full request/response." super().__init__(message, agent_id, None, suggestion)Protocol-level error (malformed response, unexpected format).
Initialize protocol error.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class ADCPSigningRequiredError (operation: str, agent_id: str | None = None, agent_uri: str | None = None)-
Expand source code
class ADCPSigningRequiredError(ADCPError): """Raised when an operation in the seller's ``request_signing.required_for`` is called without a ``SigningConfig`` on the client. Signing a ``required_for`` operation is mandatory — sending it unsigned would produce a ``request_signature_required`` rejection from the seller. Raising locally before the wire call saves a round-trip and gives the caller a clear, actionable error. """ def __init__( self, operation: str, agent_id: str | None = None, agent_uri: str | None = None, ): self.operation = operation message = ( f"Operation {operation!r} is in the seller's request_signing.required_for " f"list; signing is mandatory but no SigningConfig was provided" ) suggestion = ( "Pass signing=SigningConfig(private_key=..., key_id=...) when " "constructing ADCPClient. See adcp-keygen for key generation." ) super().__init__(message, agent_id, agent_uri, suggestion)Raised when an operation in the seller's
request_signing.required_foris called without aSigningConfigon the client.Signing a
required_foroperation is mandatory — sending it unsigned would produce arequest_signature_requiredrejection from the seller. Raising locally before the wire call saves a round-trip and gives the caller a clear, actionable error.Initialize exception with context.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class ADCPTimeoutError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
timeout: float | None = None)-
Expand source code
class ADCPTimeoutError(ADCPError): """Request timed out.""" def __init__( self, message: str, agent_id: str | None = None, agent_uri: str | None = None, timeout: float | None = None, ): """Initialize timeout error.""" suggestion = ( f"The request took longer than {timeout}s." if timeout else "The request timed out." ) suggestion += "\n Try increasing the timeout value or check if the agent is overloaded." super().__init__(message, agent_id, agent_uri, suggestion) @property def is_retryable(self) -> bool: return TrueRequest timed out.
Initialize timeout error.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class ADCPToolNotFoundError (tool_name: str,
agent_id: str | None = None,
available_tools: list[str] | None = None)-
Expand source code
class ADCPToolNotFoundError(ADCPError): """Requested tool not found on agent.""" def __init__( self, tool_name: str, agent_id: str | None = None, available_tools: list[str] | None = None ): """Initialize tool not found error.""" message = f"Tool '{tool_name}' not found on agent" suggestion = "List available tools with: python -m adcp list-tools --config <agent-id>" if available_tools: tools_list = ", ".join(available_tools[:5]) if len(available_tools) > 5: tools_list += f", ... ({len(available_tools)} total)" suggestion = f"Available tools: {tools_list}" super().__init__(message, agent_id, None, suggestion)Requested tool not found on agent.
Initialize tool not found error.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class ADCPWebhookError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
suggestion: str | None = None)-
Expand source code
class ADCPWebhookError(ADCPError): """Webhook handling error."""Webhook handling error.
Initialize exception with context.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Subclasses
Inherited members
class ADCPWebhookSignatureError (message: str = 'Invalid webhook signature', agent_id: str | None = None)-
Expand source code
class ADCPWebhookSignatureError(ADCPWebhookError): """Webhook signature verification failed.""" def __init__(self, message: str = "Invalid webhook signature", agent_id: str | None = None): """Initialize webhook signature error.""" suggestion = ( "Verify that the webhook_secret matches the secret configured on the agent.\n" " Webhook signatures use HMAC-SHA256 for security." ) super().__init__(message, agent_id, None, suggestion)Webhook signature verification failed.
Initialize webhook signature error.
Ancestors
- ADCPWebhookError
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class AccountAuthorization (**data: Any)-
Expand source code
class AccountAuthorization(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) allowed_tasks: Annotated[ list[AllowedTask], Field( description="Canonical snake_case task names the caller may invoke against this account (e.g., get_media_buys, update_media_buy, create_media_buy, sync_creatives). Absence of a task from this list MUST be interpreted as 'not permitted' — invoking an absent task MUST return SCOPE_INSUFFICIENT. This list reflects the caller's grant, not the seller's universal capability surface (for that, see get_adcp_capabilities). A seller may grant narrower subsets to different callers on the same account." ), ] field_scopes: Annotated[ dict[str, list[str]] | None, Field( description="Optional per-task allowlist of request fields the caller may set. Keys are task names (which MUST also appear in allowed_tasks). Values are arrays of top-level request-field paths permitted for that task. When a task appears in field_scopes, requests to that task with any field outside the allowlist MUST be rejected with FIELD_NOT_PERMITTED. Implicit framing fields are always permitted and do NOT need to appear in the allowlist — they identify the resource or shape the call rather than mutating business state. The list is non-exhaustive but covers the common cases: typed entity references (`account`, `media_buy_id`, `package_id`, `creative_id`, `signal_id`, `format_id`, `proposal_id`, `plan_id`, `session_id`), concurrency/idempotency (`revision`, `idempotency_key`), buyer-side correlation (`buyer_ref`, `po_number`), mode flags (`dry_run`), pagination (`pagination`, `cursor`, `max_results`), and envelope fields (`context`, `ext`, `adcp_major_version`, `push_notification_config` — transport-level async receipt, not business state). Any other typed entity-id parameter or query-shaping field on a read task SHOULD be treated as framing and not require listing. Tasks absent from field_scopes have no field-level restriction beyond what the task schema already enforces. An entry with an empty array means 'framing fields only, no business fields' — semantically distinct from the task being absent from field_scopes." ), ] = None scope_name: Annotated[ Literal['attestation_verifier'] | ScopeName | None, Field( description='Optional named scope identifier. When present, callers and the vendor agent can reason about the grant by name rather than by enumerating allowed_tasks and field_scopes. Modeled as a discriminated union so code generators produce a literal type for the standard scope(s) and a distinct type for agent-defined values — this prevents a typo of `attestation_verifier` from being silently accepted as a custom scope. Agent-defined scope names MUST use a `custom:` prefix to avoid collision with future standard scopes. The prefix is protocol-neutral: a signals agent, a governance agent, or a creative agent defines custom scopes the same way a media-buy sales agent does.' ), ] = None read_only: Annotated[ bool | None, Field( description='Convenience flag. When true, the caller is permitted only non-mutating tasks. Sellers MUST reject any mutation from a read-only caller with READ_ONLY_SCOPE. Sellers MAY omit this field; omission is equivalent to `false`. Callers MUST NOT infer read-only from `allowed_tasks` alone — the seller MUST set this explicitly when it applies.' ), ] = FalseBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var allowed_tasks : list[adcp.types.generated_poc.core.account_authorization.AllowedTask]var field_scopes : dict[str, list[str]] | Nonevar model_configvar read_only : bool | Nonevar scope_name : Literal['attestation_verifier'] | adcp.types.generated_poc.core.account_authorization.ScopeName | None
Inherited members
class AccountReference (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class AccountReference(RootModel[AccountReference1 | AccountReference2]): root: Annotated[ AccountReference1 | AccountReference2, Field( description='Reference to an account by seller-assigned ID or natural key. Use account_id when the seller or upstream platform owns the canonical account namespace: either a seller-defined account supplied out-of-band, or an upstream-managed namespace discovered with list_accounts before account-scoped calls. If a credential may access more than one account, the seller MUST expose list_accounts; if a credential is bound to exactly one account, the seller SHOULD expose list_accounts returning that singleton and MAY omit it only when the same explicit account_id is provided through another declared path or out-of-band onboarding. Use the natural key (brand + operator) when brand + operator (+ sandbox) is the durable protocol key for buyer-declared accounts provisioned through sync_accounts (require_operator_auth: false). For sandbox: account_id namespaces use pre-existing test accounts discovered via list_accounts or supplied out-of-band; buyer-declared accounts use the natural key with sandbox: true.', examples=[ {'account_id': 'acc_acme_001'}, {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, { 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, 'operator': 'pinnacle-media.com', }, { 'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com', 'sandbox': True, }, ], title='Account Reference', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[AccountReference1, AccountReference2]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.account_ref.AccountReference1 | adcp.types.generated_poc.core.account_ref.AccountReference2
class AccountReferenceById (**data: Any)-
Expand source code
class AccountReference1(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) account_id: Annotated[ str, Field( description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account_id : strvar model_config
Inherited members
class AccountReferenceByNaturalKey (**data: Any)-
Expand source code
class AccountReference2(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) brand: Annotated[ brand_ref.BrandReference, Field(description='Brand reference identifying the advertiser') ] operator: Annotated[ str, Field( description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] sandbox: Annotated[ bool | None, Field( description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' ), ] = FalseBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var brand : adcp.types.generated_poc.core.brand_ref.BrandReferencevar model_configvar operator : strvar sandbox : bool | None
Inherited members
class AccountScope (*args, **kwds)-
Expand source code
class AccountScope(StrEnum): operator = 'operator' brand = 'brand' operator_brand = 'operator_brand' agent = 'agent'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var agentvar brandvar operatorvar operator_brand
class AccountWithAuthorization (**data: Any)-
Expand source code
class AccountWithAuthorization(Account): authorization: Annotated[ account_authorization.AccountAuthorization | None, Field( description="Optional. The caller's scope grant against this account. Vendor agents of any type (media-buy, signals, governance, creative, brand) that support scope introspection SHOULD populate this so callers can preempt SCOPE_INSUFFICIENT / FIELD_NOT_PERMITTED errors rather than discovering scope by trial and error. Media-buy sales agents claiming the `attestation_verifier` standard scope MUST populate it. Absence means the vendor agent does not advertise introspectable scope for this account — callers MUST NOT infer access from absence, and fall back to error-driven discovery via the RBAC error codes." ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.account.Account
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class AcquireRightsRequest (**data: Any)-
Expand source code
class AcquireRightsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) rights_id: Annotated[ str, Field(description='Rights offering identifier from get_rights response') ] pricing_option_id: Annotated[ str, Field(description='Selected pricing option from the rights offering') ] buyer: Annotated[brand_ref.BrandReference, Field(description="The buyer's brand identity")] account: Annotated[ account_ref.AccountReference | None, Field( description='Account context for this acquisition. Used by the brand agent to resolve any governance agent previously bound for this brand+operator pair via sync_governance. When both an inline governance_context token (on the protocol envelope) and a bound governance agent are present, the inline token wins — brand agents MUST consult the agent identified by the inline token. When the request omits both `account` and an inline governance_context token, the brand agent treats the acquisition as ungoverned and the CPM-projection rule on `campaign.estimated_impressions` does not apply (sellers MAY refuse to transact ungoverned requests as a matter of commercial policy). Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.' ), ] = None campaign: Annotated[Campaign, Field(description='Campaign details for rights clearance')] revocation_webhook: Annotated[ push_notification_config_1.PushNotificationConfig, Field( description='Webhook for rights revocation notifications. If the rights holder needs to revoke rights (talent scandal, contract violation, etc.), they POST a revocation-notification to this URL. The buyer is responsible for stopping creative delivery upon receipt.' ), ] push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Webhook for async status updates if the acquisition requires approval. The rights agent sends a webhook notification when the status transitions to acquired or rejected.' ), ] = None idempotency_key: Annotated[ str, Field( description='Client-generated key for safe retries. Resubmitting with the same key returns the original response rather than creating a duplicate acquisition. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar buyer : adcp.types.generated_poc.core.brand_ref.BrandReferencevar campaign : adcp.types.generated_poc.brand.acquire_rights_request.Campaignvar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_configvar pricing_option_id : strvar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar revocation_webhook : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfigvar rights_id : str
Inherited members
class AcquireRightsResponse1 (**data: Any)-
Expand source code
class AcquireRightsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights_id: str rights_status: Literal['acquired'] = 'acquired' brand_id: str terms: rights_terms_1.RightsTerms generation_credentials: list[generation_credential_1.GenerationCredential] restrictions: list[str] | None = None disclosure: Disclosure | None = None approval_webhook: push_notification_config_1.PushNotificationConfig | None = None usage_reporting_url: AnyUrl | None = None rights_constraint: rights_constraint_1.RightsConstraint context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var approval_webhook : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar brand_id : strvar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar disclosure : adcp.types.generated_poc.brand.acquire_rights_response.Disclosure | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar generation_credentials : list[adcp.types.generated_poc.core.generation_credential.GenerationCredential]var model_configvar restrictions : list[str] | Nonevar rights_constraint : adcp.types.generated_poc.core.rights_constraint.RightsConstraintvar rights_id : strvar rights_status : Literal['acquired']var terms : adcp.types.generated_poc.brand.rights_terms.RightsTermsvar usage_reporting_url : pydantic.networks.AnyUrl | None
class AcquireRightsAcquiredResponse (**data: Any)-
Expand source code
class AcquireRightsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights_id: str rights_status: Literal['acquired'] = 'acquired' brand_id: str terms: rights_terms_1.RightsTerms generation_credentials: list[generation_credential_1.GenerationCredential] restrictions: list[str] | None = None disclosure: Disclosure | None = None approval_webhook: push_notification_config_1.PushNotificationConfig | None = None usage_reporting_url: AnyUrl | None = None rights_constraint: rights_constraint_1.RightsConstraint context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var approval_webhook : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar brand_id : strvar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar disclosure : adcp.types.generated_poc.brand.acquire_rights_response.Disclosure | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar generation_credentials : list[adcp.types.generated_poc.core.generation_credential.GenerationCredential]var model_configvar restrictions : list[str] | Nonevar rights_constraint : adcp.types.generated_poc.core.rights_constraint.RightsConstraintvar rights_id : strvar rights_status : Literal['acquired']var terms : adcp.types.generated_poc.brand.rights_terms.RightsTermsvar usage_reporting_url : pydantic.networks.AnyUrl | None
Inherited members
class AcquireRightsPendingResponse (**data: Any)-
Expand source code
class AcquireRightsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights_id: str rights_status: Literal['pending_approval'] = 'pending_approval' brand_id: str detail: str | None = None estimated_response_time: str | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var brand_id : strvar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar detail : str | Nonevar estimated_response_time : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar rights_id : strvar rights_status : Literal['pending_approval']
Inherited members
class AcquireRightsRejectedResponse (**data: Any)-
Expand source code
class AcquireRightsResponse3(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights_id: str rights_status: Literal['rejected'] = 'rejected' brand_id: str reason: str suggestions: list[str] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var brand_id : strvar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar reason : strvar rights_id : strvar rights_status : Literal['rejected']var suggestions : list[str] | None
Inherited members
class AcquireRightsErrorResponse (**data: Any)-
Expand source code
class AcquireRightsResponse4(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class ActivateSignalRequest (**data: Any)-
Expand source code
class ActivateSignalRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) action: Annotated[ Action | None, Field( description="Whether to activate or deactivate the signal. Deactivating removes the segment from downstream platforms, required when campaigns end to comply with data governance policies (GDPR, CCPA). Defaults to 'activate' when omitted." ), ] = Action.activate signal_agent_segment_id: Annotated[ str, Field( description='Opaque activation handle returned in the signal_agent_segment_id field of each get_signals response entry. Pass this string verbatim — do not pass the signal_id object.' ), ] destinations: Annotated[ list[destination.Destination], Field( description='Target destination(s) for activation. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', min_length=1, ), ] pricing_option_id: Annotated[ str | None, Field( description="The pricing option selected from the signal's pricing_options in the get_signals response. Required when the signal has pricing options. Records the buyer's pricing commitment at activation time; pass this same value in report_usage for billing verification." ), ] = None account: Annotated[ account_ref.AccountReference | None, Field( description='Account for this activation. Associates with a commercial relationship established via sync_accounts.' ), ] = None idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. Prevents duplicate activations on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar action : adcp.types.generated_poc.signals.activate_signal_request.Action | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar destinations : list[adcp.types.generated_poc.core.destination.Destination]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_configvar pricing_option_id : str | Nonevar signal_agent_segment_id : str
Inherited members
class ActivateSignalResponse1 (**data: Any)-
Expand source code
class ActivateSignalResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') deployments: list[deployment_1.Deployment] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar deployments : list[adcp.types.generated_poc.core.deployment.Deployment]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
class ActivateSignalSuccessResponse (**data: Any)-
Expand source code
class ActivateSignalResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') deployments: list[deployment_1.Deployment] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar deployments : list[adcp.types.generated_poc.core.deployment.Deployment]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
Inherited members
class ActivateSignalErrorResponse (**data: Any)-
Expand source code
class ActivateSignalResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class SegmentIdActivationKey (**data: Any)-
Expand source code
class ActivationKey1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' segment_id: Annotated[ str, Field(description='The platform-specific segment identifier to use in campaign targeting'), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar segment_id : strvar type : Literal['segment_id']
class PropertyIdActivationKey (**data: Any)-
Expand source code
class ActivationKey1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' segment_id: Annotated[ str, Field(description='The platform-specific segment identifier to use in campaign targeting'), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar segment_id : strvar type : Literal['segment_id']
Inherited members
class KeyValueActivationKey (**data: Any)-
Expand source code
class ActivationKey2(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' key: Annotated[str, Field(description='The targeting parameter key')] value: Annotated[str, Field(description='The targeting parameter value')]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var key : strvar model_configvar type : Literal['key_value']var value : str
class PropertyTagActivationKey (**data: Any)-
Expand source code
class ActivationKey2(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' key: Annotated[str, Field(description='The targeting parameter key')] value: Annotated[str, Field(description='The targeting parameter value')]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var key : strvar model_configvar type : Literal['key_value']var value : str
Inherited members
class AdAgentsValidationResult (domain: str,
url: str,
discovery_method: DiscoveryMethod = 'direct',
manager_domain: str | None = None,
data: dict[str, Any] | None = None,
valid: bool = False,
errors: list[str] = <factory>)-
Expand source code
@dataclass class AdAgentsValidationResult: """Result of discovering and validating a publisher's adagents.json. ``discovery_method`` records which path produced ``data``: ``direct`` for ``/.well-known/adagents.json`` on the publisher, ``authoritative_location`` for a URL-reference redirect, and ``ads_txt_managerdomain`` for the one-hop ads.txt MANAGERDOMAIN fallback (RFC 4175). ``manager_domain`` is set only on the managerdomain path. """ domain: str url: str discovery_method: DiscoveryMethod = "direct" manager_domain: str | None = None data: dict[str, Any] | None = None valid: bool = False errors: list[str] = field(default_factory=list)Result of discovering and validating a publisher's adagents.json.
discovery_methodrecords which path produceddata:directfor/.well-known/adagents.jsonon the publisher,authoritative_locationfor a URL-reference redirect, andads_txt_managerdomainfor the one-hop ads.txt MANAGERDOMAIN fallback (RFC 4175).manager_domainis set only on the managerdomain path.Instance variables
var data : dict[str, typing.Any] | Nonevar discovery_method : Literal['direct', 'authoritative_location', 'ads_txt_managerdomain']var domain : strvar errors : list[str]var manager_domain : str | Nonevar url : strvar valid : bool
class AdagentsAccessBlockedError (publisher_domain: str)-
Expand source code
class AdagentsAccessBlockedError(AdagentsValidationError): """adagents.json fetch blocked by publisher-side bot management (403, cf-mitigated: challenge). Only surfaces in direct-fetch workflows (``fetch_adagents``). SDK callers that use ``fetch_agent_authorizations`` avoid this entirely — the AAO directory crawler handles publisher fetches and serves cached results without exposing the SDK to publisher-side bot management. If you need to catch this specifically without catching all ``AdagentsValidationError``s, use ``except AdagentsAccessBlockedError``. """ def __init__(self, publisher_domain: str): """Initialize bot-management blocked error.""" self.publisher_domain = publisher_domain message = ( f"adagents.json blocked by bot management for {publisher_domain} " f"(HTTP 403, cf-mitigated: challenge)" ) suggestion = ( "The publisher's origin blocked this request with a Cloudflare bot management\n" " challenge. This only affects direct adagents.json fetches (fetch_adagents).\n" "\n" " To unblock local debugging:\n" " - Retry with a browser-like User-Agent via the user_agent= parameter, e.g.\n" ' user_agent="Mozilla/5.0"\n' " - Or call fetch_agent_authorizations() to query the AAO directory instead,\n" " which bypasses publisher-side bot management entirely." ) super().__init__(message, None, None, suggestion)adagents.json fetch blocked by publisher-side bot management (403, cf-mitigated: challenge).
Only surfaces in direct-fetch workflows (
fetch_adagents()). SDK callers that usefetch_agent_authorizations()avoid this entirely — the AAO directory crawler handles publisher fetches and serves cached results without exposing the SDK to publisher-side bot management.If you need to catch this specifically without catching all
AdagentsValidationErrors, useexcept AdagentsAccessBlockedError.Initialize bot-management blocked error.
Ancestors
- AdagentsValidationError
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class AdagentsCacheEntry (body: dict[str, Any], etag: str | None = None, last_modified: str | None = None)-
Expand source code
@dataclass(frozen=True) class AdagentsCacheEntry: """Conditional-refresh cache state for an adagents.json URL. Pass an entry into :func:`fetch_adagents_with_cache` to send ``If-None-Match`` (preferred) and ``If-Modified-Since`` validators on the next fetch. A 304 from the publisher is treated as a successful cache-lifetime refresh — the ``body`` is returned unchanged with refreshed timing, per the adcp#4504 fetch contract. """ body: dict[str, Any] etag: str | None = None last_modified: str | None = NoneConditional-refresh cache state for an adagents.json URL.
Pass an entry into :func:
fetch_adagents_with_cache()to sendIf-None-Match(preferred) andIf-Modified-Sincevalidators on the next fetch. A 304 from the publisher is treated as a successful cache-lifetime refresh — thebodyis returned unchanged with refreshed timing, per the adcp#4504 fetch contract.Instance variables
var body : dict[str, typing.Any]var etag : str | Nonevar last_modified : str | None
class AdagentsEntryError (index: int, kind: EntryErrorKind, message: str, url: str | None = None)-
Expand source code
@dataclass(frozen=True) class AdagentsEntryError: """A single schema violation found in an adagents.json file. ``kind`` is a stable string literal callers can branch on (e.g., distinguish a publisher who shipped bare entries from one who picked an unknown authorization_type). ``message`` is developer-facing and its wording may change between releases — pattern-match on ``kind`` when surfacing publisher-facing diagnostics. For file-level errors (e.g., ``empty_authorized_agents``) ``index`` is ``-1`` and ``url`` is ``None``. """ index: int kind: EntryErrorKind message: str url: str | None = NoneA single schema violation found in an adagents.json file.
kindis a stable string literal callers can branch on (e.g., distinguish a publisher who shipped bare entries from one who picked an unknown authorization_type).messageis developer-facing and its wording may change between releases — pattern-match onkindwhen surfacing publisher-facing diagnostics.For file-level errors (e.g.,
empty_authorized_agents)indexis-1andurlisNone.Instance variables
var index : intvar kind : Literal['missing_url', 'missing_authorized_for', 'missing_authorization_type', 'unknown_authorization_type', 'missing_selector_for_type', 'not_an_object', 'empty_authorized_agents']var message : strvar url : str | None
class AdagentsFetchResult (data: dict[str, Any],
discovery_method: DiscoveryMethod,
etag: str | None = None,
last_modified: str | None = None,
not_modified: bool = False)-
Expand source code
@dataclass(frozen=True) class AdagentsFetchResult: """Result of a fetch, including refreshed cache validators. ``not_modified`` is True when the server returned 304 and ``data`` came from the supplied cache entry. ``etag`` / ``last_modified`` are the validators to persist for the next fetch — on 304 they come from the 304 response headers if present, falling back to the supplied entry's values. """ data: dict[str, Any] discovery_method: DiscoveryMethod etag: str | None = None last_modified: str | None = None not_modified: bool = FalseResult of a fetch, including refreshed cache validators.
not_modifiedis True when the server returned 304 anddatacame from the supplied cache entry.etag/last_modifiedare the validators to persist for the next fetch — on 304 they come from the 304 response headers if present, falling back to the supplied entry's values.Instance variables
var data : dict[str, typing.Any]var discovery_method : Literal['direct', 'authoritative_location', 'ads_txt_managerdomain']var etag : str | Nonevar last_modified : str | Nonevar not_modified : bool
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
- AdagentsValidationError
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class AdagentsTimeoutError (publisher_domain: str, timeout: float)-
Expand source code
class AdagentsTimeoutError(AdagentsValidationError): """Request for adagents.json timed out.""" def __init__(self, publisher_domain: str, timeout: float): """Initialize timeout error.""" message = f"Request to fetch adagents.json timed out after {timeout}s" suggestion = ( "The publisher's server may be slow or unresponsive.\n" " Try increasing the timeout value or check the domain is correct." ) super().__init__(message, None, None, suggestion)Request for adagents.json timed out.
Initialize timeout error.
Ancestors
- AdagentsValidationError
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class AdagentsValidationError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
suggestion: str | None = None)-
Expand source code
class AdagentsValidationError(ADCPError): """Base error for adagents.json validation issues."""Base error for adagents.json validation issues.
Initialize exception with context.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Subclasses
Inherited members
class AdagentsValidationReport (schema_valid: bool,
errors: list[AdagentsEntryError],
authorized_agents_count: int,
properties_count: int,
is_reference: bool = False)-
Expand source code
@dataclass(frozen=True) class AdagentsValidationReport: """Result of structurally validating a parsed adagents.json. Distinguishes the two failure modes that :func:`get_properties_by_agent` collapses into an empty list: a schema-invalid file (``schema_valid`` is False, ``errors`` populated) versus a valid file that simply doesn't list the caller's agent. ``authorized_agents_count`` and ``properties_count`` reflect the array lengths as observed in the input — they are reported regardless of ``schema_valid`` so callers can show "0 agents listed" diagnostics on partially-broken files. ``is_reference`` is True for the URL-reference variant of the schema (an ``authoritative_location`` pointer with no inline ``authorized_agents`` array). Callers that received a report with ``is_reference=True`` should follow the redirect (e.g., via :func:`fetch_adagents`) and validate the resolved file. This flag lets callers distinguish a legitimate URL-reference file from an inline file that happens to have zero entries (which is itself invalid per the schema's ``minItems: 1`` constraint on ``authorized_agents``). """ schema_valid: bool errors: list[AdagentsEntryError] authorized_agents_count: int properties_count: int is_reference: bool = FalseResult of structurally validating a parsed adagents.json.
Distinguishes the two failure modes that :func:
get_properties_by_agent()collapses into an empty list: a schema-invalid file (schema_validis False,errorspopulated) versus a valid file that simply doesn't list the caller's agent.authorized_agents_countandproperties_countreflect the array lengths as observed in the input — they are reported regardless ofschema_validso callers can show "0 agents listed" diagnostics on partially-broken files.is_referenceis True for the URL-reference variant of the schema (anauthoritative_locationpointer with no inlineauthorized_agentsarray). Callers that received a report withis_reference=Trueshould follow the redirect (e.g., via :func:fetch_adagents()) and validate the resolved file. This flag lets callers distinguish a legitimate URL-reference file from an inline file that happens to have zero entries (which is itself invalid per the schema'sminItems: 1constraint onauthorized_agents).Instance variables
var errors : list[AdagentsEntryError]var is_reference : boolvar properties_count : intvar schema_valid : bool
class AdvertiserIndustry (*args, **kwds)-
Expand source code
class AdvertiserIndustry(StrEnum): automotive = 'automotive' automotive_electric_vehicles = 'automotive.electric_vehicles' automotive_parts_accessories = 'automotive.parts_accessories' automotive_luxury = 'automotive.luxury' beauty_cosmetics = 'beauty_cosmetics' beauty_cosmetics_skincare = 'beauty_cosmetics.skincare' beauty_cosmetics_fragrance = 'beauty_cosmetics.fragrance' beauty_cosmetics_haircare = 'beauty_cosmetics.haircare' cannabis = 'cannabis' cpg = 'cpg' cpg_personal_care = 'cpg.personal_care' cpg_household = 'cpg.household' dating = 'dating' education = 'education' education_higher_education = 'education.higher_education' education_online_learning = 'education.online_learning' education_k12 = 'education.k12' energy_utilities = 'energy_utilities' energy_utilities_renewable = 'energy_utilities.renewable' fashion_apparel = 'fashion_apparel' fashion_apparel_luxury = 'fashion_apparel.luxury' fashion_apparel_sportswear = 'fashion_apparel.sportswear' finance = 'finance' finance_banking = 'finance.banking' finance_insurance = 'finance.insurance' finance_investment = 'finance.investment' finance_cryptocurrency = 'finance.cryptocurrency' food_beverage = 'food_beverage' food_beverage_alcohol = 'food_beverage.alcohol' food_beverage_restaurants = 'food_beverage.restaurants' food_beverage_packaged_goods = 'food_beverage.packaged_goods' gambling_betting = 'gambling_betting' gambling_betting_sports_betting = 'gambling_betting.sports_betting' gambling_betting_casino = 'gambling_betting.casino' gaming = 'gaming' gaming_mobile = 'gaming.mobile' gaming_console_pc = 'gaming.console_pc' gaming_esports = 'gaming.esports' government_nonprofit = 'government_nonprofit' government_nonprofit_political = 'government_nonprofit.political' government_nonprofit_charity = 'government_nonprofit.charity' healthcare = 'healthcare' healthcare_pharmaceutical = 'healthcare.pharmaceutical' healthcare_medical_devices = 'healthcare.medical_devices' healthcare_wellness = 'healthcare.wellness' home_garden = 'home_garden' home_garden_furniture = 'home_garden.furniture' home_garden_home_improvement = 'home_garden.home_improvement' media_entertainment = 'media_entertainment' media_entertainment_podcasts = 'media_entertainment.podcasts' media_entertainment_music = 'media_entertainment.music' media_entertainment_film_tv = 'media_entertainment.film_tv' media_entertainment_publishing = 'media_entertainment.publishing' media_entertainment_live_events = 'media_entertainment.live_events' pets = 'pets' professional_services = 'professional_services' professional_services_legal = 'professional_services.legal' professional_services_consulting = 'professional_services.consulting' real_estate = 'real_estate' real_estate_residential = 'real_estate.residential' real_estate_commercial = 'real_estate.commercial' recruitment_hr = 'recruitment_hr' retail = 'retail' retail_ecommerce = 'retail.ecommerce' retail_department_stores = 'retail.department_stores' sports_fitness = 'sports_fitness' sports_fitness_equipment = 'sports_fitness.equipment' sports_fitness_teams_leagues = 'sports_fitness.teams_leagues' technology = 'technology' technology_software = 'technology.software' technology_hardware = 'technology.hardware' technology_ai_ml = 'technology.ai_ml' telecom = 'telecom' telecom_mobile_carriers = 'telecom.mobile_carriers' telecom_internet_providers = 'telecom.internet_providers' transportation_logistics = 'transportation_logistics' travel_hospitality = 'travel_hospitality' travel_hospitality_airlines = 'travel_hospitality.airlines' travel_hospitality_hotels = 'travel_hospitality.hotels' travel_hospitality_cruise = 'travel_hospitality.cruise' travel_hospitality_tourism = 'travel_hospitality.tourism'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var automotivevar automotive_electric_vehiclesvar automotive_luxuryvar automotive_parts_accessoriesvar beauty_cosmeticsvar beauty_cosmetics_fragrancevar beauty_cosmetics_haircarevar beauty_cosmetics_skincarevar cannabisvar cpgvar cpg_householdvar cpg_personal_carevar datingvar educationvar education_higher_educationvar education_k12var education_online_learningvar energy_utilitiesvar energy_utilities_renewablevar fashion_apparelvar fashion_apparel_luxuryvar fashion_apparel_sportswearvar financevar finance_bankingvar finance_cryptocurrencyvar finance_insurancevar finance_investmentvar food_beveragevar food_beverage_alcoholvar food_beverage_packaged_goodsvar food_beverage_restaurantsvar gambling_bettingvar gambling_betting_casinovar gambling_betting_sports_bettingvar gamingvar gaming_console_pcvar gaming_esportsvar gaming_mobilevar government_nonprofitvar government_nonprofit_charityvar government_nonprofit_politicalvar healthcarevar healthcare_medical_devicesvar healthcare_pharmaceuticalvar healthcare_wellnessvar home_gardenvar home_garden_furniturevar home_garden_home_improvementvar media_entertainmentvar media_entertainment_film_tvvar media_entertainment_live_eventsvar media_entertainment_musicvar media_entertainment_podcastsvar media_entertainment_publishingvar petsvar professional_servicesvar professional_services_consultingvar professional_services_legalvar real_estatevar real_estate_commercialvar real_estate_residentialvar recruitment_hrvar retailvar retail_department_storesvar retail_ecommercevar sports_fitnessvar sports_fitness_equipmentvar sports_fitness_teams_leaguesvar technologyvar technology_ai_mlvar technology_hardwarevar technology_softwarevar telecomvar telecom_internet_providersvar telecom_mobile_carriersvar transportation_logisticsvar travel_hospitalityvar travel_hospitality_airlinesvar travel_hospitality_cruisevar travel_hospitality_hotelsvar travel_hospitality_tourism
class AgentAuthorizationsDirectoryResult (**data: Any)-
Expand source code
class AgentAuthorizationsDirectoryResult(AdCPBaseModel): """Response envelope for ``GET /v1/agents/{agent_url}/publishers``. Maps directly to ``schemas/aao/agent-publishers.json`` in the AdCP bundle (adcp#4828). The directory is a discovery accelerator — each ``publisher_domain`` row tells callers where to look; they SHOULD verify the publisher's adagents.json directly before treating an authorization as trusted. """ agent_url: str directory_indexed_at: datetime | None publishers: list[DirectoryPublisherEntry] = Field(default_factory=list) next_cursor: str | None = NoneResponse envelope for
GET /v1/agents/{agent_url}/publishers.Maps directly to
schemas/aao/agent-publishers.jsonin the AdCP bundle (adcp#4828). The directory is a discovery accelerator — eachpublisher_domainrow tells callers where to look; they SHOULD verify the publisher's adagents.json directly before treating an authorization as trusted.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var agent_url : strvar directory_indexed_at : datetime.datetime | Nonevar model_configvar next_cursor : str | Nonevar publishers : list[DirectoryPublisherEntry]
Inherited members
class AgentCapabilities (**data: Any)-
Expand source code
class AgentCapabilities(RegistryBaseModel): tools_count: int tools: list[AgentTool] | None = None standard_operations: AgentStandardOperations | None = None creative_capabilities: AgentCreativeCapabilities | None = None signals_capabilities: SignalsCapabilities | None = None measurement_capabilities: Annotated[ MeasurementCapabilities | None, Field( description="Vendor-published per-metric catalog for measurement agents. Populated when the crawler successfully fetched and validated `get_adcp_capabilities.measurement` (AdCP 3.x). Mirrors the protocol shape — see the AdCP `get_adcp_capabilities` reference for field semantics." ), ] = NoneBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var creative_capabilities : AgentCreativeCapabilities | Nonevar measurement_capabilities : MeasurementCapabilities | Nonevar model_configvar signals_capabilities : SignalsCapabilities | Nonevar standard_operations : AgentStandardOperations | Nonevar tools : list[AgentTool] | Nonevar tools_count : int
class AgentCompliance (**data: Any)-
Expand source code
class AgentCompliance(RegistryBaseModel): status: ComplianceStatus requested_compliance_target: Annotated[ str | None, Field( description="Requested compliance target before alias resolution, e.g. 3.0 or 3.1-beta." ), ] = None adcp_version: Annotated[ str | None, Field( description="Concrete AdCP compliance bundle version used for the latest run, e.g. 3.0.12." ), ] = None lifecycle_stage: AgentLifecycleStage tracks: Annotated[dict[str, str], Field(examples=[{"core": "pass", "products": "fail"}])] track_details: Annotated[ list[TrackDetail] | None, Field( description="Latest-run per-track summary. Skipped tracks with has_coverage_gap_skip=true represent selected coverage gaps, such as missing_test_controller." ), ] = None streak_days: int last_checked_at: str | None headline: str | None monitoring_paused: bool | None = None check_interval_hours: int | None = None verified: bool | None = None verified_roles: Annotated[ list[VerifiedRole] | None, Field( description="AdCP protocols the agent is AAO Verified for (e.g. media-buy, creative). Matches enums/adcp-protocol.json." ), ] = NoneBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var adcp_version : str | Nonevar check_interval_hours : int | Nonevar headline : str | Nonevar last_checked_at : str | Nonevar lifecycle_stage : AgentLifecycleStagevar model_configvar monitoring_paused : bool | Nonevar requested_compliance_target : str | Nonevar status : ComplianceStatusvar streak_days : intvar track_details : list[TrackDetail] | Nonevar tracks : dict[str, str]var verified : bool | Nonevar verified_roles : list[VerifiedRole] | None
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 extra_headers: dict[str, str] = Field(default_factory=dict) """Additional HTTP headers sent on every request to this agent. This is a **transport-layer escape hatch**, not an AdCP protocol extension point — protocol-defined fields belong in the request envelope or ``RequestContext.metadata``. Use this for vendor or deployment-specific routing headers (e.g. tenant routing on a multi-tenant server). Reserved: the configured ``auth_header`` (default ``x-adcp-auth``) and the standard ``Authorization`` header — set credentials via ``auth_token``/``auth_header`` instead. Header names are rejected if they contain CR/LF or other control characters. Persisted plaintext at ``~/.adcp/config.json`` when saved via the CLI — do not store credentials here. """ @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" ) return v @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 @model_validator(mode="after") def _validate_extra_headers(self) -> AgentConfig: if not self.extra_headers: return self reserved = {self.auth_header.lower(), "authorization"} for key, value in self.extra_headers.items(): if not key: raise ValueError("extra_headers contains an empty header name") if any(c in key for c in ("\r", "\n", "\x00")) or any(ord(c) < 0x20 for c in key): raise ValueError(f"extra_headers key contains control character: {key!r}") if any(c in value for c in ("\r", "\n", "\x00")): raise ValueError(f"extra_headers value for {key!r} contains CR/LF/NUL") if key.lower() in reserved: raise ValueError( f"extra_headers may not override reserved auth header " f"{key!r} (collides with auth_header={self.auth_header!r} " f"or 'Authorization'); set credentials via auth_token + " f"auth_header instead" ) return selfAgent 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var agent_uri : strvar auth_header : strvar auth_token : str | Nonevar auth_type : strvar debug : boolvar extra_headers : dict[str, str]-
Additional HTTP headers sent on every request to this agent.
This is a transport-layer escape hatch, not an AdCP protocol extension point — protocol-defined fields belong in the request envelope or
RequestContext.metadata. Use this for vendor or deployment-specific routing headers (e.g. tenant routing on a multi-tenant server).Reserved: the configured
auth_header(defaultx-adcp-auth) and the standardAuthorizationheader — set credentials viaauth_token/auth_headerinstead. Header names are rejected if they contain CR/LF or other control characters.Persisted plaintext at
~/.adcp/config.jsonwhen saved via the CLI — do not store credentials here. var id : strvar mcp_transport : strvar model_configvar protocol : Protocolvar requires_auth : boolvar timeout : float
Static methods
def validate_agent_uri(v: str) ‑> str-
Validate agent URI format.
def validate_auth_type(v: str) ‑> str-
Validate auth type.
def validate_mcp_transport(v: str) ‑> str-
Validate MCP transport type.
def validate_timeout(v: float) ‑> float-
Validate timeout is reasonable.
class AgentHealth (**data: Any)-
Expand source code
class AgentHealth(RegistryBaseModel): online: bool checked_at: str response_time_ms: float | None = None tools_count: int | None = None resources_count: int | None = None error: str | None = NoneBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var checked_at : strvar error : str | Nonevar model_configvar online : boolvar resources_count : int | Nonevar response_time_ms : float | Nonevar tools_count : int | None
class AgentStats (**data: Any)-
Expand source code
class AgentStats(RegistryBaseModel): property_count: int | None = None publisher_count: int | None = None publishers: list[str] | None = None creative_formats: int | None = NoneBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var creative_formats : int | Nonevar model_configvar property_count : int | Nonevar publisher_count : int | Nonevar publishers : list[str] | None
class ArtifactWebhookPayload (**data: Any)-
Expand source code
class ArtifactWebhookPayload(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Sender-generated key stable across retries of the same webhook event. Sales agents MUST generate a cryptographically random value (UUID v4 recommended) per distinct emission of a batch and reuse the same key on every retry. Recipients MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential) — keys from different sales agents are independent. Distinct from `batch_id`, which identifies the logical batch: `idempotency_key` identifies this specific emission event, so a re-emission of the same `batch_id` (e.g., after a correction) is a different event and MUST carry a fresh `idempotency_key`.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] media_buy_id: Annotated[ str, Field(description='Media buy identifier these artifacts belong to') ] batch_id: Annotated[ str, Field( description='Unique identifier for this batch of artifacts. Use for deduplication and acknowledgment.' ), ] timestamp: Annotated[ AwareDatetime, Field(description='When this batch was generated (ISO 8601)') ] artifacts: Annotated[ list[Artifact], Field(description='Content artifacts from delivered impressions') ] pagination: Annotated[ Pagination | None, Field(description='Pagination info when batching large artifact sets') ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var artifacts : list[adcp.types.generated_poc.content_standards.artifact_webhook_payload.Artifact]var batch_id : strvar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar media_buy_id : strvar model_configvar pagination : adcp.types.generated_poc.content_standards.artifact_webhook_payload.Pagination | Nonevar timestamp : pydantic.types.AwareDatetime
Inherited members
class AssetContentType (*args, **kwds)-
Expand source code
class AssetContentType(StrEnum): image = 'image' video = 'video' audio = 'audio' text = 'text' markdown = 'markdown' html = 'html' css = 'css' javascript = 'javascript' vast = 'vast' daast = 'daast' url = 'url' webhook = 'webhook' brief = 'brief' catalog = 'catalog' published_post = 'published_post'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var audiovar briefvar catalogvar cssvar daastvar htmlvar imagevar javascriptvar markdownvar published_postvar textvar urlvar vastvar videovar webhook
class SyncAudiencesAudience (**data: Any)-
Expand source code
class Audience(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) audience_id: Annotated[ str, Field( description="Buyer's identifier for this audience. Used to reference the audience in targeting overlays." ), ] name: Annotated[str | None, Field(description='Human-readable name for this audience')] = None description: Annotated[ str | None, Field( description="Human-readable description of this audience's composition or purpose (e.g., 'High-value customers who purchased in the last 90 days')." ), ] = None audience_type: Annotated[ AudienceType | None, Field( description="Intended use for this audience. 'crm': target these users. 'suppression': exclude these users from delivery. 'lookalike_seed': use as a seed for the seller's lookalike modeling. Sellers may handle audiences differently based on type (e.g., suppression lists bypass minimum size requirements on some platforms)." ), ] = None tags: Annotated[ list[Tag] | None, Field( description="Buyer-defined tags for organizing and filtering audiences (e.g., 'holiday_2026', 'high_ltv'). Tags are stored by the seller and returned in discovery-only calls." ), ] = None add: Annotated[ list[audience_member.AudienceMember] | None, Field( description='Members to add to this audience. Hashed before sending — normalize emails to lowercase+trim, phones to E.164.', min_length=1, ), ] = None remove: Annotated[ list[audience_member.AudienceMember] | None, Field( description='Members to remove from this audience. If the same identifier appears in both add and remove in a single request, remove takes precedence.', min_length=1, ), ] = None delete: Annotated[ bool | None, Field( description='When true, delete this audience from the account entirely. All other fields on this audience object are ignored. Use this to delete a specific audience without affecting others.' ), ] = None consent_basis: Annotated[ consent_basis_1.ConsentBasis | None, Field( description='GDPR lawful basis for processing this audience list. Informational — not validated by the protocol, but required by some sellers operating in regulated markets (e.g. EU). When omitted, the buyer asserts they have a lawful basis appropriate to their jurisdiction.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var add : list[adcp.types.generated_poc.core.audience_member.AudienceMember] | Nonevar audience_id : strvar audience_type : adcp.types.generated_poc.media_buy.sync_audiences_request.AudienceType | Nonevar consent_basis : adcp.types.generated_poc.enums.consent_basis.ConsentBasis | Nonevar delete : bool | Nonevar description : str | Nonevar model_configvar name : str | Nonevar remove : list[adcp.types.generated_poc.core.audience_member.AudienceMember] | None
Inherited members
class AudienceSource (*args, **kwds)-
Expand source code
class AudienceSource(StrEnum): synced = 'synced' platform = 'platform' third_party = 'third_party' lookalike = 'lookalike' retargeting = 'retargeting' unknown = 'unknown'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var lookalikevar platformvar retargetingvar syncedvar third_partyvar unknown
class AuthorizationContext (properties: list[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[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 AuthorizationRequiredDetails (**data: Any)-
Expand source code
class AuthorizationRequiredDetails(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) required_connections: Annotated[ list[downstream_connection_requirement.DownstreamConnectionRequirement] | None, Field( description='Complete set of downstream connections known to be required for the relevant product, format, or request.' ), ] = None missing_connections: Annotated[ list[downstream_connection_requirement.DownstreamConnectionRequirement] | None, Field( description='Subset of downstream connections that blocked the current request. Sellers SHOULD populate this array when the caller needs to route a human through a connections flow. Entries with `status` of `missing`, `pending`, `expired`, or `revoked` MUST include either `provider` or `authorization_url` so the buyer can route the remediation unambiguously.' ), ] = None authorization_url: Annotated[ AnyUrl | None, Field( description='General recovery URL when there is a single obvious authorization step or when the seller has its own connection-management page.' ), ] = None authorization_instructions: Annotated[ str | None, Field( description='Human-readable recovery instructions. Use `missing_connections[].authorization_instructions` when instructions differ per downstream connection.' ), ] = None reference_authorization: Annotated[ dict[str, Any] | None, Field( description='Legacy or provider-specific authorization hint for the referenced object. Prefer `missing_connections[]` for new implementations.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var missing_connections : list[adcp.types.generated_poc.core.downstream_connection_requirement.DownstreamConnectionRequirement] | Nonevar model_configvar required_connections : list[adcp.types.generated_poc.core.downstream_connection_requirement.DownstreamConnectionRequirement] | None
Inherited members
class AuthorizedAgentsByPropertyId (**data: Any)-
Expand source code
class AuthorizedAgents1(AuthorizedAgentBaseFields): model_config = ConfigDict( extra='allow', ) authorization_type: Annotated[ Literal['property_ids'], Field(description='Discriminator indicating authorization by specific property IDs'), ] = 'property_ids' property_ids: Annotated[ list[property_id.PropertyId], Field( description='Property IDs this agent is authorized for. Resolved against the top-level properties array in this file', min_length=1, ), ] collections: Annotated[ list[collection_selector.CollectionSelector] | None, Field( description='Optional collection constraints. When present, authorization only applies to inventory associated with these collections.', min_length=1, ), ] = None placement_ids: Annotated[ list[str] | None, Field( description='Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.', min_length=1, ), ] = None placement_tags: Annotated[ list[str] | None, Field( description='Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.', min_length=1, ), ] = None delegation_type: Annotated[ DelegationType | None, Field( description="Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint." ), ] = None exclusive: Annotated[ bool | None, Field( description="Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory." ), ] = None countries: Annotated[ list[Country] | None, Field( description='Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.', min_length=1, ), ] = None effective_from: Annotated[ AwareDatetime | None, Field(description='Optional start time for this authorization window.'), ] = None effective_until: Annotated[ AwareDatetime | None, Field(description='Optional end time for this authorization window.') ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.authorized_agent_base.AuthorizedAgentBaseFields
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var collections : list[adcp.types.generated_poc.core.collection_selector.CollectionSelector] | Nonevar countries : list[adcp.types.generated_poc.adagents.Country] | Nonevar delegation_type : adcp.types.generated_poc.adagents.DelegationType | Nonevar effective_from : pydantic.types.AwareDatetime | Nonevar effective_until : pydantic.types.AwareDatetime | Nonevar exclusive : bool | Nonevar model_configvar placement_ids : list[str] | Nonevar property_ids : list[adcp.types.generated_poc.core.property_id.PropertyId]
Inherited members
class AuthorizedAgentsByPropertyTag (**data: Any)-
Expand source code
class AuthorizedAgents2(AuthorizedAgentBaseFields): model_config = ConfigDict( extra='allow', ) authorization_type: Annotated[ Literal['property_tags'], Field(description='Discriminator indicating authorization by property tags'), ] = 'property_tags' property_tags: Annotated[ list[property_tag.PropertyTag], Field( description='Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching', min_length=1, ), ] collections: Annotated[ list[collection_selector.CollectionSelector] | None, Field( description='Optional collection constraints. When present, authorization only applies to inventory associated with these collections.', min_length=1, ), ] = None placement_ids: Annotated[ list[str] | None, Field( description='Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.', min_length=1, ), ] = None placement_tags: Annotated[ list[str] | None, Field( description='Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.', min_length=1, ), ] = None delegation_type: Annotated[ DelegationType | None, Field( description="Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint." ), ] = None exclusive: Annotated[ bool | None, Field( description="Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory." ), ] = None countries: Annotated[ list[Country] | None, Field( description='Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.', min_length=1, ), ] = None effective_from: Annotated[ AwareDatetime | None, Field(description='Optional start time for this authorization window.'), ] = None effective_until: Annotated[ AwareDatetime | None, Field(description='Optional end time for this authorization window.') ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.authorized_agent_base.AuthorizedAgentBaseFields
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var collections : list[adcp.types.generated_poc.core.collection_selector.CollectionSelector] | Nonevar countries : list[adcp.types.generated_poc.adagents.Country] | Nonevar delegation_type : adcp.types.generated_poc.adagents.DelegationType | Nonevar effective_from : pydantic.types.AwareDatetime | Nonevar effective_until : pydantic.types.AwareDatetime | Nonevar exclusive : bool | Nonevar model_configvar placement_ids : list[str] | None
Inherited members
class AuthorizedAgentsByInlineProperties (**data: Any)-
Expand source code
class AuthorizedAgents3(AuthorizedAgentBaseFields): model_config = ConfigDict( extra='allow', ) authorization_type: Annotated[ Literal['inline_properties'], Field( description='Discriminator indicating authorization by inline property definitions. Companion field is `properties` (not `inline_properties`) — the only authorization_type whose companion field name does not mirror the discriminator value.' ), ] = 'inline_properties' properties: Annotated[ list[property.Property], Field( description='Specific properties this agent is authorized for, defined inline on the agent entry (alternative to property_ids/property_tags). Note: this is the companion field for `authorization_type: "inline_properties"` — the field is named `properties`, not `inline_properties`.', min_length=1, ), ] collections: Annotated[ list[collection_selector.CollectionSelector] | None, Field( description='Optional collection constraints. When present, authorization only applies to inventory associated with these collections.', min_length=1, ), ] = None placement_ids: Annotated[ list[str] | None, Field( description='Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.', min_length=1, ), ] = None placement_tags: Annotated[ list[str] | None, Field( description='Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.', min_length=1, ), ] = None delegation_type: Annotated[ DelegationType | None, Field( description="Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint." ), ] = None exclusive: Annotated[ bool | None, Field( description="Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory." ), ] = None countries: Annotated[ list[Country] | None, Field( description='Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.', min_length=1, ), ] = None effective_from: Annotated[ AwareDatetime | None, Field(description='Optional start time for this authorization window.'), ] = None effective_until: Annotated[ AwareDatetime | None, Field(description='Optional end time for this authorization window.') ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.authorized_agent_base.AuthorizedAgentBaseFields
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var collections : list[adcp.types.generated_poc.core.collection_selector.CollectionSelector] | Nonevar countries : list[adcp.types.generated_poc.adagents.Country] | Nonevar delegation_type : adcp.types.generated_poc.adagents.DelegationType | Nonevar effective_from : pydantic.types.AwareDatetime | Nonevar effective_until : pydantic.types.AwareDatetime | Nonevar exclusive : bool | Nonevar model_configvar placement_ids : list[str] | Nonevar properties : list[adcp.types.generated_poc.core.property.Property]
Inherited members
class AuthorizedAgentsByPublisherProperties (**data: Any)-
Expand source code
class AuthorizedAgents4(AuthorizedAgentBaseFields): model_config = ConfigDict( extra='allow', ) authorization_type: Annotated[ Literal['publisher_properties'], Field( description='Discriminator indicating authorization for properties from other publisher domains' ), ] = 'publisher_properties' publisher_properties: Annotated[ list[publisher_property_selector.PublisherPropertySelector], Field( description='Properties from other publisher domains this agent is authorized for. Each entry specifies a publisher domain and which of their properties this agent can sell', min_length=1, ), ] collections: Annotated[ list[collection_selector.CollectionSelector] | None, Field( description='Optional collection constraints. When present, authorization only applies to inventory associated with these collections.', min_length=1, ), ] = None placement_ids: Annotated[ list[str] | None, Field( description='Optional placement constraints. When present, authorization only applies to these placement IDs from the top-level placements array in this file.', min_length=1, ), ] = None placement_tags: Annotated[ list[str] | None, Field( description='Optional placement tag constraints. When present, authorization only applies to placements whose tags include any of these publisher-defined values.', min_length=1, ), ] = None delegation_type: Annotated[ DelegationType | None, Field( description="Commercial relationship for this inventory path. 'direct' means the publisher treats this as a direct way to buy from them, even if a third party operates the software. 'delegated' means the agent is authorized to sell on the publisher's behalf. 'ad_network' means the inventory is sold as part of a network/package context rather than as the publisher's direct endpoint." ), ] = None exclusive: Annotated[ bool | None, Field( description="Whether this agent is the publisher's sole authorized path for the scoped inventory slice. When false or absent, other authorized agents may also sell the same inventory." ), ] = None countries: Annotated[ list[Country] | None, Field( description='Optional ISO 3166-1 alpha-2 country codes limiting where this authorization applies. Omit for worldwide authorization.', min_length=1, ), ] = None effective_from: Annotated[ AwareDatetime | None, Field(description='Optional start time for this authorization window.'), ] = None effective_until: Annotated[ AwareDatetime | None, Field(description='Optional end time for this authorization window.') ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.authorized_agent_base.AuthorizedAgentBaseFields
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var collections : list[adcp.types.generated_poc.core.collection_selector.CollectionSelector] | Nonevar countries : list[adcp.types.generated_poc.adagents.Country] | Nonevar delegation_type : adcp.types.generated_poc.adagents.DelegationType | Nonevar effective_from : pydantic.types.AwareDatetime | Nonevar effective_until : pydantic.types.AwareDatetime | Nonevar exclusive : bool | Nonevar model_configvar placement_ids : list[str] | Nonevar publisher_properties : list[adcp.types.generated_poc.core.publisher_property_selector.PublisherPropertySelector]
Inherited members
class AuthorizedAgentsBySignalId (**data: Any)-
Expand source code
class AuthorizedAgents5(AuthorizedAgentBaseFields): model_config = ConfigDict( extra='allow', ) authorization_type: Annotated[ Literal['signal_ids'], Field(description='Discriminator indicating authorization by specific signal IDs'), ] = 'signal_ids' signal_ids: Annotated[ list[SignalId], Field( description='Signal IDs this agent is authorized to resell. Resolved against the top-level signals array in this file', min_length=1, ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.authorized_agent_base.AuthorizedAgentBaseFields
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar signal_ids : list[adcp.types.generated_poc.adagents.SignalId]
Inherited members
class AuthorizedAgentsBySignalTag (**data: Any)-
Expand source code
class AuthorizedAgents6(AuthorizedAgentBaseFields): model_config = ConfigDict( extra='allow', ) authorization_type: Annotated[ Literal['signal_tags'], Field(description='Discriminator indicating authorization by signal tags'), ] = 'signal_tags' signal_tags: Annotated[ list[SignalTag], Field( description='Signal tags this agent is authorized for. Agent can resell all signals with these tags', min_length=1, ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.authorized_agent_base.AuthorizedAgentBaseFields
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class BrandActivity (**data: Any)-
Expand source code
class BrandActivity(RegistryBaseModel): domain: Annotated[str, Field(examples=["acmecorp.com"])] total: Annotated[int, Field(examples=[3])] revisions: list[ActivityRevision]Base model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var domain : strvar model_configvar revisions : list[ActivityRevision]var total : int
class BrandReference (**data: Any)-
Expand source code
class BrandReference(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) domain: Annotated[ str, Field( description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] brand_id: Annotated[ brand_id_1.BrandId | None, Field( description='Brand identifier within the house portfolio. Optional for single-brand domains.' ), ] = None industries: Annotated[ list[str] | None, Field( description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." ), ] = None data_subject_contestation: Annotated[ DataSubjectContestation | None, Field( description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." ), ] = None brand_kit_override: Annotated[ BrandKitOverride | None, Field( description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var brand_id : adcp.types.generated_poc.core.brand_id.BrandId | Nonevar brand_kit_override : adcp.types.generated_poc.core.brand_ref.BrandKitOverride | Nonevar data_subject_contestation : adcp.types.generated_poc.core.brand_ref.DataSubjectContestation | Nonevar domain : strvar industries : list[str] | Nonevar model_config
Inherited members
class BrandRegistryItem (**data: Any)-
Expand source code
class BrandRegistryItem(RegistryBaseModel): domain: Annotated[str, Field(examples=["acmecorp.com"])] brand_name: Annotated[str | None, Field(examples=["Acme Corp"])] = None source: BrandRegistrySource has_manifest: bool verified: bool house_domain: str | None = None keller_type: KellerType | None = NoneBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var brand_name : str | Nonevar domain : strvar has_manifest : boolvar house_domain : str | Nonevar keller_type : KellerType | Nonevar model_configvar source : BrandRegistrySourcevar verified : bool
class BrandSource (*args, **kwds)-
Expand source code
class BrandSource(Enum): brand_json = "brand_json" community = "community" enriched = "enriched"Create a collection of name/value pairs.
Example enumeration:
>>> class Color(Enum): ... RED = 1 ... BLUE = 2 ... GREEN = 3Access them by:
- attribute access::
>>> Color.RED <Color.RED: 1>- value lookup:
>>> Color(1) <Color.RED: 1>- name lookup:
>>> Color['RED'] <Color.RED: 1>Enumerations can be iterated over, and know how many members they have:
>>> len(Color) 3>>> list(Color) [<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.
Ancestors
- enum.Enum
Class variables
var brand_jsonvar communityvar enriched
class BuildCreativeRequest (**data: Any)-
Expand source code
class BuildCreativeRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) message: Annotated[ str | None, Field( description='Natural language instructions for the transformation or generation. For pure generation, this is the creative brief. For transformation, this provides guidance on how to adapt the creative. For refinement, this describes the desired changes.' ), ] = None creative_manifest: Annotated[ creative_manifest_1.CreativeManifest | None, Field( description='Creative manifest to transform or generate from. For pure generation, this should include the target format_id and any required input assets. For transformation (e.g., resizing, reformatting), this is the complete creative to adapt. When creative_id is provided, the agent resolves the creative from its library and this field is ignored.' ), ] = None creative_id: Annotated[ str | None, Field( description="Reference to a creative in the agent's library. The creative agent resolves this to a manifest from its library. Use this instead of creative_manifest when retrieving an existing creative for tag generation or format adaptation." ), ] = None concept_id: Annotated[ str | None, Field( description='Creative concept containing the creative. Creative agents SHOULD assign globally unique creative_id values; when they cannot guarantee uniqueness, concept_id is REQUIRED to disambiguate.' ), ] = None media_buy_id: Annotated[ str | None, Field( description='Media buy identifier for tag generation context. When the creative agent is also the ad server, this provides the trafficking context needed to generate placement-specific tags (e.g., CM360 placement ID). Not needed when tags are generated at the creative level (most creative platforms).' ), ] = None package_id: Annotated[ str | None, Field( description='Package identifier within the media buy. Used with media_buy_id when the creative agent needs line-item-level context for tag generation. Omit to get a tag not scoped to a specific package.' ), ] = None target_format_id: Annotated[ format_id.FormatReferenceStructuredObject | None, Field( description='Single format ID to generate. Mutually exclusive with target_format_ids. The format definition specifies required input assets and output structure.' ), ] = None target_format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description='Array of format IDs to generate in a single call. Mutually exclusive with target_format_id. The creative agent produces one manifest per format. Each format definition specifies its own required input assets and output structure.', min_length=1, ), ] = None transformer_id: Annotated[ str | None, Field( description="Selects an account-scoped transformer (discovered via list_transformers) to perform the build. One transformer per call. When present, the build uses this transformer and target_format_id/target_format_ids select which of its outputs to produce — they MUST be a subset of the transformer's output_format_ids. Render configuration goes in `config`." ), ] = None config: Annotated[ dict[str, Any] | None, Field( description='Typed render configuration for the selected transformer, keyed by each param\'s `field` (from the transformer\'s params[] in list_transformers). Example: { "voice": "isaac", "speaking_rate": 1.1, "mastering_preset": "podcast" }. The agent MUST validate `config` against the transformer\'s live params for this account and reject unrecognized keys and out-of-range / non-enumerated values with a field-attributed error (e.g. `config.voice`) rather than silently ignoring them — config drives a paid render. Genuinely vendor-specific or experimental knobs not declared as params belong in `ext`, not here. (The schema leaves this object open because legal keys are dynamic per transformer; strict validation is a normative agent obligation.) When `refine_from_build_variant_id` is set, `config` is applied as a DELTA over the parent leaf\'s config.' ), ] = None refine_from_build_variant_id: Annotated[ str | None, Field( description="Refine a previously produced variant: re-build from the referenced `build_variant_id`, applying the natural-language instruction in `message` and any `config` delta, and return NEW lineage-linked variant(s) — each with `parent_build_variant_id` set to this id. A refinement is never a mutation; the parent leaf is unchanged. The `transformer_id` and target format(s) are inherited from the parent and need not be repeated; passing a `transformer_id` or `target_format_id`/`target_format_ids` that differs from the parent's is rejected with `INVALID_REQUEST`. Composes with `max_variants` / `variant_axis` (produces N refined alternatives), but is mutually exclusive with `max_creatives` / catalog fan-out (you refine one prior creative, not a catalog). Requires the agent to advertise `creative.supports_refinement: true` in get_adcp_capabilities; agents that do not retain prior builds reject with `UNSUPPORTED_FEATURE`. A ref that is unknown or no longer retained (agents retain produced leaves for an agent-defined window) is rejected with `REFERENCE_NOT_FOUND`, with `error.field` set to `refine_from_build_variant_id`. To refine a buyer-held manifest when the agent retains nothing, use the transform path instead (`creative_manifest` + `message`)." ), ] = None mode: Annotated[ Mode | None, Field( description="`execute` (default) produces and bills the creative(s). `estimate` is a DRY RUN: the agent produces nothing and bills nothing, and returns a BuildCreativeEstimate with a projected cost band (cost_low/cost_high) computed against THIS request's actual inputs (script length, brief, catalog size, max_creatives × max_variants) — the band the buyer cannot derive itself, since per_unit gives the rate but not the unit count. Requires the agent to advertise `creative.supports_spend_controls`; otherwise rejected with `UNSUPPORTED_FEATURE`." ), ] = Mode.execute max_spend: Annotated[ MaxSpend | None, Field( description='Hard per-call spend ceiling. The agent produces leaves until the NEXT leaf would push the run\'s aggregate vendor_cost over `amount`, then STOPS and returns the partial BuildCreativeVariantSuccess produced so far with `budget_status: "capped"` (every returned leaf is real, trafficable, and billed — nothing produced is discarded; the leaf shortfall is `leaves_returned` < `leaves_total`). If even the first leaf would exceed the cap, the call fails with BUDGET_CAP_REACHED. `currency` MUST match the rate card\'s currency (the agent does not FX-convert) or the request is rejected with INVALID_REQUEST (error.field `max_spend.currency`). Requires `creative.supports_spend_controls`. Caps a SINGLE call — to bound a refinement loop, track aggregate vendor_cost across calls and stop issuing them (buyer responsibility in this revision). max_spend bounds only build-time vendor_cost: CPM-priced builds (estimate basis `cpm_deferred`) have build-time vendor_cost 0 and accrue at serve time, so max_spend never engages for them — bound a CPM fan-out with max_creatives instead.' ), ] = None max_creatives: Annotated[ int | None, Field( description='Caps how many DISTINCT creatives to produce along the catalog/item fan-out axis — one creative per catalog item. Use it to sample a large catalog (e.g. send 150 job openings, set max_creatives: 5 to preview five). Distinct from item_limit, which caps how many catalog items a SINGLE creative consumes (DCO-style). Omitted with a catalog input means one creative per item up to the catalog/format bound; omitted without a catalog collapses to a single creative. Large fan-outs may return asynchronously. Mutually exclusive with `refine_from_build_variant_id` (refinement targets one prior creative, not a catalog fan-out). Supported only when the agent advertises `creative.multiplicity.supports_catalog_fanout`; values above `max_creatives_limit` are clamped. Pair with `max_spend` to bound the bill of a large fan-out.', ge=1, ), ] = None signal_conditions: Annotated[ list[SignalCondition] | None, Field( description="Advisory keep-all PRODUCTION axis: produce one distinct creative group per signal condition, each kept and trafficked with its own signal targeting (e.g. a rain creative AND a sun creative). Sibling to max_creatives (catalog axis), NOT a variant_axis value (which is choose-among). Each item reuses SignalTargeting (value_type-discriminated binary/categorical/numeric over signal_ref) so the produced group's signal_condition resolves condition identity through the SAME schema the sales-side package targeting uses, plus an optional signal_agent_segment_id carrying the RESOLVED-segment identity (vs signal_ref's definition identity) — echo a provider-exposed handle verbatim; it is the primary trafficking-compatibility key, with categorical signal_ref+value as the weaker fallback. Per #5280 this is an ADVISORY context pointer — it informs production and MUST NOT hard-block at the build_creative layer; trafficking-compatibility (a sun creative MUST NOT serve into rain-targeted packages) is enforced reject-at-trafficking on the sales side (SIGNAL_TARGETING_INCOMPATIBLE), not here. Triggers the BuildCreativeVariantSuccess shape. Supported only when the agent advertises creative.multiplicity.supports_signal_fanout; condition counts above max_signal_conditions_limit are CLAMPED (not rejected), consistent with max_creatives. Composes with max_creatives (catalog × conditions cross-product) and max_variants (variants per group).", min_length=1, ), ] = None max_variants: Annotated[ int | None, Field( description='Caps how many ALTERNATIVES to produce per creative (different voices, themes, best-of-N, etc.). Default 1 preserves single-output behavior. Each variant is a real, independently-billed build (you pay for all produced); the buyer keeps one or many. When variant_axis.values[] is provided, its length is authoritative over max_variants. Resolutions/quality tiers are NOT variants — request them as additional target formats.', ge=1, ), ] = 1 variant_axis: Annotated[ VariantAxis | None, Field( description='Declares the dimension along which variants differ. When `values` is provided, the agent produces exactly one variant per value (e.g. an A/B of two voices). When only `dimension` is provided, the agent chooses up to max_variants variants along that dimension (e.g. best-of-N, themes).' ), ] = None keep_mode: Annotated[ KeepMode | None, Field( description='Advisory hint for how the buyer intends to use the variants. `keep_one` (best-of-N) and `keep_some` signal the agent to set `recommended`/`rank` on returned variants. Advisory only — it does not change what is returned or billed; every produced variant is returned and charged. Keeping is a client act of trafficking the chosen build_variant_id(s).' ), ] = KeepMode.keep_all selection_strategy: Annotated[ creative_selection_strategy.CreativeSelectionStrategy | None, Field( description='Governs HOW the agent samples when max_creatives < items_total (folds #5262). audience_relevance draws its ranking input from the SAME signal_ref pointers in signal_conditions / package targeting — NOT a parallel signals[] array. proximity takes a location input (geo shape TBD — WG open). inventory_priority is seller-side catalog metadata (margin/overstock/promo; no buyer input). random is the status-quo default. Per-creative selection ordering surfaces on the existing rank / recommended fields of creatives[].variants[], not a new selection_rank. Advisory; absent => agent default (random).' ), ] = None account: Annotated[ account_ref.AccountReference | None, Field( description='Account reference for pricing and billing. When present, the creative agent applies account-specific pricing from the rate card, records the build against the account for billing, and can enforce account-level quotas or entitlements. Required by creative agents that charge for their services.' ), ] = None brand: Annotated[ brand_ref.BrandReference | None, Field( description='Brand reference for creative generation. Resolved to full brand identity (colors, logos, tone) at execution time.' ), ] = None quality: Annotated[ creative_quality.CreativeQuality | None, Field( description="Quality tier for generation. 'draft' produces fast, lower-fidelity output for iteration and review. 'production' produces full-quality output for final delivery. If omitted, the creative agent uses its own default. For non-generative transforms (e.g., format resizing), creative agents MAY ignore this field." ), ] = None evaluator: Annotated[ evaluator_spec.EvaluatorSpec | None, Field( description="Optional advisory evaluator (buyer-attached pointer, #5280) declaring how produced variants should be evaluated and ranked — the rank-side of the get_creative_features feature oracle. Experimental (x-status: experimental): the whole evaluator surface is new and unfrozen, and requires creative.supports_evaluator, which sellers MUST pair with `creative.evaluator` in experimental_features. Drives the producing agent's gate-then-rank pipeline over its best_of_n exploration: per leaf, evaluate (the chosen form) → optionally GATE (`evaluator.feature_requirement[]`, drop fails — internal pruning of which leaves the agent recommends, never an AdCP-layer block of an already-produced billable leaf) → RANK survivors (`evaluator.rank_by`, an explicit {feature_id, direction} ordering). Feature discovery uses get_adcp_capabilities governance.creative_features for rank_by, feature_requirement, and eval.features[]; evaluator_id is a pre-provisioned/account-arranged preset, not an ID discovered from that catalog. Populates a per-leaf `eval` block of creative-feature values (creative-feature-result[]) when supports_evaluator. When the evaluator names an external agent (`evaluator.feature_agent.agent_url` or the agent-form `agent_url`), that agent MUST appear in the seller's `creative_policy.accepted_verifiers[]` (the same allowlist #5280 established for provenance verify_agent); an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED`. The outbound evaluator call authenticates on the transport (request signing/JWKS, mTLS, or a pre-provisioned static credential); credentials and caller-supplied trust material MUST NOT appear in evaluator, context, ext, or creative payload fields, and credential- or trust-material keys should be rejected with `CREDENTIAL_IN_ARGS`. With no `feature_requirement`, evaluation is advisory only and does not change what is produced or billed; an unreachable/unknown on-list agent degrades to seller-default ranking (advisory errors[] note), not a failure. Requires creative.supports_evaluator; otherwise ignored." ), ] = None item_limit: Annotated[ int | None, Field( description="Maximum number of catalog items a SINGLE creative consumes when generating (DCO-style — e.g. how many items fill one carousel/feed creative). When a catalog asset contains more items than this limit, the creative agent selects the top items based on relevance or catalog ordering. When item_limit exceeds the format's max_items, the creative agent SHOULD use the lesser of the two. Ignored when the manifest contains no catalog assets. Distinct from `max_creatives`, which fans OUT across catalog items to produce one distinct creative per item.", ge=1, ), ] = None include_preview: Annotated[ bool | None, Field( description="When true, requests the creative agent to include preview renders in the response alongside the manifest. Agents that support this return a 'preview' object in the response using the same structure as preview_creative. Agents that do not support inline preview simply omit the field. This avoids a separate preview_creative round trip for platforms that generate previews as a byproduct of building." ), ] = None preview_inputs: Annotated[ list[PreviewInput] | None, Field( description='Input sets for preview generation when include_preview is true. Each input set defines macros and context values for one preview variant. If include_preview is true but this is omitted, the agent generates a single default preview. Only supported with target_format_id (single-format requests). Ignored when using target_format_ids — multi-format requests generate one default preview per format. Ignored when include_preview is false or omitted.', min_length=1, ), ] = None preview_quality: Annotated[ creative_quality.CreativeQuality | None, Field( description="Render quality for inline preview when include_preview is true. 'draft' produces fast, lower-fidelity renderings. 'production' produces full-quality renderings. Independent of the build quality parameter — you can build at draft quality and preview at production quality, or vice versa. If omitted, the creative agent uses its own default. Ignored when include_preview is false or omitted." ), ] = None preview_output_format: Annotated[ preview_output_format_1.PreviewOutputFormat | None, Field( description="Output format for preview renders when include_preview is true. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). Ignored when include_preview is false or omitted." ), ] = preview_output_format_1.PreviewOutputFormat.url macro_values: Annotated[ dict[str, str] | None, Field( description="Macro values to pre-substitute into the output manifest's assets. Keys are universal macro names (e.g., CLICK_URL, CACHEBUSTER); values are the substitution strings. The creative agent translates universal macros to its platform's native syntax. Substitution is literal — all occurrences of each macro in output assets are replaced with the provided value. The caller is responsible for URL-encoding values if the output context requires it. Macros not provided here remain as {MACRO} placeholders for the sales agent to resolve at serve time. Creative agents MUST ignore keys they do not recognize — unknown macro names are not an error." ), ] = None idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. Prevents duplicate creative generation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async terminal completion/failure notifications on build_creative. Meaningful only when the request enters the async lifecycle and returns a Submitted envelope. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change response timing semantics: agents MUST NOT route a request through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; requests that can be completed inline still return the synchronous success shape.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar brand : adcp.types.generated_poc.core.brand_ref.BrandReference | Nonevar concept_id : str | Nonevar config : dict[str, typing.Any] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_id : str | Nonevar creative_manifest : adcp.types.generated_poc.core.creative_manifest.CreativeManifest | Nonevar evaluator : adcp.types.generated_poc.core.evaluator_spec.EvaluatorSpec | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar include_preview : bool | Nonevar item_limit : int | Nonevar keep_mode : adcp.types.generated_poc.media_buy.build_creative_request.KeepMode | Nonevar macro_values : dict[str, str] | Nonevar max_creatives : int | Nonevar max_spend : adcp.types.generated_poc.media_buy.build_creative_request.MaxSpend | Nonevar max_variants : int | Nonevar media_buy_id : str | Nonevar message : str | Nonevar mode : adcp.types.generated_poc.media_buy.build_creative_request.Mode | Nonevar model_configvar package_id : str | Nonevar preview_inputs : list[adcp.types.generated_poc.media_buy.build_creative_request.PreviewInput] | Nonevar preview_output_format : adcp.types.generated_poc.enums.preview_output_format.PreviewOutputFormat | Nonevar preview_quality : adcp.types.generated_poc.enums.creative_quality.CreativeQuality | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar quality : adcp.types.generated_poc.enums.creative_quality.CreativeQuality | Nonevar refine_from_build_variant_id : str | Nonevar selection_strategy : adcp.types.generated_poc.enums.creative_selection_strategy.CreativeSelectionStrategy | Nonevar signal_conditions : list[adcp.types.generated_poc.media_buy.build_creative_request.SignalCondition] | Nonevar target_format_id : adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject | Nonevar target_format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar transformer_id : str | Nonevar variant_axis : adcp.types.generated_poc.media_buy.build_creative_request.VariantAxis | None
Inherited members
class BuildCreativeSuccessResponse (**data: Any)-
Expand source code
class BuildCreativeResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') creative_manifest: creative_manifest_1.CreativeManifest build_variant_id: str | None = None recipe_hash: str | None = None sandbox: bool | None = None expires_at: AwareDatetime | None = None preview: Preview | None = None preview_error: error_1.Error | None = None pricing_option_id: str | None = None vendor_cost: Annotated[float, Field(ge=0)] | None = None currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None consumption: creative_consumption_1.CreativeConsumption | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var build_variant_id : str | Nonevar consumption : adcp.types.generated_poc.core.creative_consumption.CreativeConsumption | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_manifest : adcp.types.generated_poc.core.creative_manifest.CreativeManifestvar currency : str | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar preview : adcp.types.generated_poc.media_buy.build_creative_response.Preview | Nonevar preview_error : adcp.types.generated_poc.core.error.Error | Nonevar pricing_option_id : str | Nonevar recipe_hash : str | Nonevar sandbox : bool | Nonevar vendor_cost : float | None
class BuildCreativeResponse1 (**data: Any)-
Expand source code
class BuildCreativeResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') creative_manifest: creative_manifest_1.CreativeManifest build_variant_id: str | None = None recipe_hash: str | None = None sandbox: bool | None = None expires_at: AwareDatetime | None = None preview: Preview | None = None preview_error: error_1.Error | None = None pricing_option_id: str | None = None vendor_cost: Annotated[float, Field(ge=0)] | None = None currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None consumption: creative_consumption_1.CreativeConsumption | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var build_variant_id : str | Nonevar consumption : adcp.types.generated_poc.core.creative_consumption.CreativeConsumption | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_manifest : adcp.types.generated_poc.core.creative_manifest.CreativeManifestvar currency : str | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar preview : adcp.types.generated_poc.media_buy.build_creative_response.Preview | Nonevar preview_error : adcp.types.generated_poc.core.error.Error | Nonevar pricing_option_id : str | Nonevar recipe_hash : str | Nonevar sandbox : bool | Nonevar vendor_cost : float | None
Inherited members
class BuildCreativeErrorResponse (**data: Any)-
Expand source code
class BuildCreativeResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class BuildCreativeSubmittedResponse (**data: Any)-
Expand source code
class BuildCreativeResponse6(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal[<TaskStatus.submitted: 'submitted'>]var task_id : str
Inherited members
class BuyingMode (*args, **kwds)-
Expand source code
class BuyingMode(StrEnum): brief = 'brief' wholesale = 'wholesale' refine = 'refine'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var briefvar refinevar wholesale
class CalibrateContentSuccessResponse (**data: Any)-
Expand source code
class CalibrateContentResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') verdict: binary_verdict_1.BinaryVerdict confidence: Annotated[float, Field(ge=0, le=1)] | None = None explanation: str | None = None features: list[Feature] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var confidence : float | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar explanation : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar features : list[adcp.types.generated_poc.content_standards.calibrate_content_response.Feature] | Nonevar model_configvar verdict : adcp.types.generated_poc.enums.binary_verdict.BinaryVerdict
class CalibrateContentResponse1 (**data: Any)-
Expand source code
class CalibrateContentResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') verdict: binary_verdict_1.BinaryVerdict confidence: Annotated[float, Field(ge=0, le=1)] | None = None explanation: str | None = None features: list[Feature] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var confidence : float | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar explanation : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar features : list[adcp.types.generated_poc.content_standards.calibrate_content_response.Feature] | Nonevar model_configvar verdict : adcp.types.generated_poc.enums.binary_verdict.BinaryVerdict
Inherited members
class CalibrateContentErrorResponse (**data: Any)-
Expand source code
class CalibrateContentResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class Catalog (**data: Any)-
Expand source code
class Catalog(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) catalog_id: Annotated[ str | None, Field( description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." ), ] = None name: Annotated[ str | None, Field( description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." ), ] = None type: Annotated[ catalog_type.CatalogType, Field( description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema." ), ] url: Annotated[ AnyUrl | None, Field( description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." ), ] = None feed_format: Annotated[ feed_format_1.FeedFormat | None, Field( description='Format of the external feed at url. Required when url points to a non-AdCP feed (e.g., Google Merchant Center XML, Meta Product Catalog). Omit for offering-type catalogs where the feed is native AdCP JSON.' ), ] = None update_frequency: Annotated[ update_frequency_1.UpdateFrequency | None, Field( description='How often the platform should re-fetch the feed from url. Only applicable when url is provided. Platforms may use this as a hint for polling schedules.' ), ] = None items: Annotated[ list[dict[str, Any]] | None, Field( description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", min_length=1, ), ] = None ids: Annotated[ list[str] | None, Field( description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', min_length=1, ), ] = None gtins: Annotated[ list[Gtin] | None, Field( description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", min_length=1, ), ] = None tags: Annotated[ list[str] | None, Field( description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', min_length=1, ), ] = None category: Annotated[ str | None, Field( description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." ), ] = None query: Annotated[ str | None, Field( description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." ), ] = None conversion_events: Annotated[ list[event_type.EventType] | None, Field( description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", min_length=1, ), ] = None content_id_type: Annotated[ content_id_type_1.ContentIdType | None, Field( description="Identifier type that the event's content_ids field should be matched against for items in this catalog. For example, 'gtin' means content_ids values are Global Trade Item Numbers, 'sku' means retailer SKUs. Omit when using a custom identifier scheme not listed in the enum." ), ] = None feed_field_mappings: Annotated[ list[catalog_field_mapping.CatalogFieldMapping] | None, Field( description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', min_length=1, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Subclasses
- adcp.types.generated_poc.core.assets.catalog_asset.CatalogAsset
Class variables
var catalog_id : str | Nonevar category : str | Nonevar content_id_type : adcp.types.generated_poc.enums.content_id_type.ContentIdType | Nonevar conversion_events : list[adcp.types.generated_poc.enums.event_type.EventType] | Nonevar feed_field_mappings : list[adcp.types.generated_poc.core.catalog_field_mapping.CatalogFieldMapping] | Nonevar feed_format : adcp.types.generated_poc.enums.feed_format.FeedFormat | Nonevar gtins : list[adcp.types.generated_poc.core.catalog.Gtin] | Nonevar ids : list[str] | Nonevar items : list[dict[str, typing.Any]] | Nonevar model_configvar name : str | Nonevar query : str | Nonevar type : adcp.types.generated_poc.enums.catalog_type.CatalogTypevar update_frequency : adcp.types.generated_poc.enums.update_frequency.UpdateFrequency | Nonevar url : pydantic.networks.AnyUrl | None
class SyncCatalogResult (**data: Any)-
Expand source code
class Catalog(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') catalog_id: str action: catalog_action_1.CatalogAction platform_id: str | None = None item_count: Annotated[int, Field(ge=0)] | None = None items_approved: Annotated[int, Field(ge=0)] | None = None items_pending: Annotated[int, Field(ge=0)] | None = None items_rejected: Annotated[int, Field(ge=0)] | None = None item_issues: list[ItemIssue] | None = None last_synced_at: AwareDatetime | None = None next_fetch_at: AwareDatetime | None = None changes: list[str] | None = None errors: list[error_1.Error] | None = None warnings: list[str] | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var action : adcp.types.generated_poc.enums.catalog_action.CatalogActionvar catalog_id : strvar changes : list[str] | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar item_count : int | Nonevar item_issues : list[adcp.types.generated_poc.media_buy.sync_catalogs_response.ItemIssue] | Nonevar items_approved : int | Nonevar items_pending : int | Nonevar items_rejected : int | Nonevar last_synced_at : pydantic.types.AwareDatetime | Nonevar model_configvar next_fetch_at : pydantic.types.AwareDatetime | Nonevar platform_id : str | Nonevar warnings : list[str] | None
Inherited members
class CatalogAction (*args, **kwds)-
Expand source code
class CatalogAction(StrEnum): created = 'created' updated = 'updated' unchanged = 'unchanged' failed = 'failed' deleted = 'deleted'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var createdvar deletedvar failedvar unchangedvar updated
class CatalogFieldBinding (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class CatalogFieldBinding(RootModel[ScalarBinding | AssetPoolBinding | CatalogFieldBinding1]): root: Annotated[ ScalarBinding | AssetPoolBinding | CatalogFieldBinding1, Field( description="Maps a format template slot to a catalog item field or typed asset pool. The 'kind' field identifies the binding variant. All bindings are optional — agents can still infer mappings without them.", discriminator='kind', examples=[ { 'description': 'Scalar binding — hotel name to headline slot', 'data': {'kind': 'scalar', 'asset_id': 'headline', 'catalog_field': 'name'}, }, { 'description': 'Scalar binding — nested field (nightly rate)', 'data': { 'kind': 'scalar', 'asset_id': 'price_badge', 'catalog_field': 'price.amount', }, }, { 'description': 'Asset pool binding — hero image from landscape pool', 'data': { 'kind': 'asset_pool', 'asset_id': 'hero_image', 'asset_group_id': 'images_landscape', }, }, { 'description': 'Asset pool binding — Snap vertical background from vertical pool', 'data': { 'kind': 'asset_pool', 'asset_id': 'snap_background', 'asset_group_id': 'images_vertical', }, }, { 'description': 'Catalog group binding — carousel where each slide is one hotel', 'data': { 'kind': 'catalog_group', 'format_group_id': 'slide', 'catalog_item': True, 'per_item_bindings': [ {'kind': 'scalar', 'asset_id': 'title', 'catalog_field': 'name'}, { 'kind': 'scalar', 'asset_id': 'price', 'catalog_field': 'price.amount', }, { 'kind': 'asset_pool', 'asset_id': 'image', 'asset_group_id': 'images_landscape', }, ], }, }, ], title='Catalog Field Binding', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[ScalarBinding, AssetPoolBinding, CatalogFieldBinding1]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.requirements.catalog_field_binding.ScalarBinding | adcp.types.generated_poc.core.requirements.catalog_field_binding.AssetPoolBinding | adcp.types.generated_poc.core.requirements.catalog_field_binding.CatalogFieldBinding1
class CatalogGroupBinding (**data: Any)-
Expand source code
class CatalogFieldBinding1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) kind: Literal['catalog_group'] = 'catalog_group' format_group_id: Annotated[ str, Field(description="The asset_group_id of a repeatable_group in the format's assets array."), ] catalog_item: Annotated[ Literal[True], Field( description="Each repetition of the format's repeatable_group maps to one item from the catalog." ), ] per_item_bindings: Annotated[ list[PerItemBindings] | None, Field( description='Scalar and asset pool bindings that apply within each repetition of the group. Nested catalog_group bindings are not permitted.', min_length=1, ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var catalog_item : Literal[True]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_group_id : strvar kind : Literal['catalog_group']var model_configvar per_item_bindings : list[adcp.types.generated_poc.core.requirements.catalog_field_binding.PerItemBindings] | None
Inherited members
class CatalogFieldMapping (**data: Any)-
Expand source code
class CatalogFieldMapping(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) feed_field: Annotated[ str | None, Field( description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' ), ] = None catalog_field: Annotated[ str | None, Field( description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." ), ] = None asset_group_id: Annotated[ str | None, Field( description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." ), ] = None value: Annotated[ Any | None, Field( description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' ), ] = None transform: Annotated[ Transform | None, Field( description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' ), ] = None format: Annotated[ str | None, Field( description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." ), ] = None timezone: Annotated[ str | None, Field( description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." ), ] = None by: Annotated[ float | None, Field( description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", gt=0.0, ), ] = None separator: Annotated[ str | None, Field( description="For transform 'split': the separator character or string to split on. Defaults to ','." ), ] = ',' default: Annotated[ Any | None, Field( description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var asset_group_id : str | Nonevar by : float | Nonevar catalog_field : str | Nonevar default : typing.Any | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar feed_field : str | Nonevar format : str | Nonevar model_configvar separator : str | Nonevar timezone : str | Nonevar transform : adcp.types.generated_poc.core.catalog_field_mapping.Transform | Nonevar value : typing.Any | None
Inherited members
class CatalogItemStatus (*args, **kwds)-
Expand source code
class CatalogItemStatus(StrEnum): approved = 'approved' pending = 'pending' rejected = 'rejected' warning = 'warning' withdrawn = 'withdrawn'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var approvedvar pendingvar rejectedvar warningvar withdrawn
class CatalogRequirements (**data: Any)-
Expand source code
class CatalogRequirements(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) catalog_type: Annotated[ catalog_type_1.CatalogType, Field(description='The catalog type this requirement applies to'), ] required: Annotated[ bool | None, Field( description='Whether this catalog type must be present. When true, creatives using this format must reference a synced catalog of this type.' ), ] = True min_items: Annotated[ int | None, Field( description='Minimum number of items the catalog must contain for this format to render properly (e.g., a carousel might require at least 3 products)', ge=1, ), ] = None max_items: Annotated[ int | None, Field( description='Maximum number of items the format can render. Items beyond this limit are ignored. Useful for fixed-slot layouts (e.g., a 3-product card) or feed-size constraints.', ge=1, ), ] = None required_fields: Annotated[ list[str] | None, Field( description="Fields that must be present and non-empty on every item in the catalog. Field names are catalog-type-specific (e.g., 'title', 'price', 'image_url' for product catalogs; 'store_id', 'quantity' for inventory feeds).", min_length=1, ), ] = None feed_formats: Annotated[ list[feed_format.FeedFormat] | None, Field( description='Accepted feed formats for this catalog type. When specified, the synced catalog must use one of these formats. When omitted, any format is accepted.', min_length=1, ), ] = None offering_asset_constraints: Annotated[ list[offering_asset_constraint.OfferingAssetConstraint] | None, Field( description="Per-item creative asset requirements. Declares what asset groups (headlines, images, videos) each catalog item must provide in its assets array, along with count bounds and per-asset technical constraints. Applicable to 'offering' and all vertical catalog types (hotel, flight, job, etc.) whose items carry typed assets.", min_length=1, ), ] = None field_bindings: Annotated[ list[catalog_field_binding.CatalogFieldBinding] | None, Field( description='Explicit mappings from format template slots to catalog item fields or typed asset pools. Optional — creative agents can infer mappings without them, but bindings make the relationship self-describing and enable validation. Covers scalar fields (asset_id → catalog_field), asset pools (asset_id → asset_group_id on the catalog item), and repeatable groups that iterate over catalog items.', min_length=1, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var catalog_type : adcp.types.generated_poc.enums.catalog_type.CatalogTypevar feed_formats : list[adcp.types.generated_poc.enums.feed_format.FeedFormat] | Nonevar field_bindings : list[adcp.types.generated_poc.core.requirements.catalog_field_binding.CatalogFieldBinding] | Nonevar max_items : int | Nonevar min_items : int | Nonevar model_configvar offering_asset_constraints : list[adcp.types.generated_poc.core.requirements.offering_asset_constraint.OfferingAssetConstraint] | Nonevar required : bool | Nonevar required_fields : list[str] | None
Inherited members
class CatalogType (*args, **kwds)-
Expand source code
class CatalogType(StrEnum): offering = 'offering' product = 'product' inventory = 'inventory' store = 'store' promotion = 'promotion' hotel = 'hotel' flight = 'flight' job = 'job' vehicle = 'vehicle' real_estate = 'real_estate' education = 'education' destination = 'destination' app = 'app'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var appvar destinationvar educationvar flightvar hotelvar inventoryvar jobvar offeringvar productvar promotionvar real_estatevar storevar vehicle
class CheckGovernanceRequest (**data: Any)-
Expand source code
class CheckGovernanceRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) plan_id: Annotated[ str, Field( description='Campaign governance plan identifier. The plan uniquely scopes the account and operator; do not include a separate `account` field — the governance agent resolves account from the plan. Governance agents MUST treat any sibling `account` field as a contract violation and reject the request.' ), ] caller: Annotated[AnyUrl, Field(description='URL of the agent making the request.')] purchase_type: Annotated[ purchase_type_1.PurchaseType | None, Field( description="The type of financial commitment being checked. Determines which budget allocation (if any) to validate against. Defaults to 'media_buy' when omitted." ), ] = purchase_type_1.PurchaseType.media_buy tool: Annotated[ str | None, Field( description="The AdCP tool being checked (e.g., 'create_media_buy', 'acquire_rights', 'activate_signal'). Present on intent checks (orchestrator). The governance agent uses the presence of tool+payload to identify an intent check." ), ] = None payload: Annotated[ dict[str, Any] | None, Field( description='The full tool arguments as they would be sent to the seller. Present on intent checks. The governance agent can inspect any field to validate against the plan.' ), ] = None governance_context: Annotated[ str | None, Field( description='Governance context token from a prior check_governance response. Pass this on subsequent checks for the same governed action so the governance agent can maintain continuity across the lifecycle. In 3.0 governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context); callers persist and forward the value verbatim.', max_length=4096, min_length=1, pattern='^[\\x20-\\x7E]+$', ), ] = None phase: Annotated[ governance_phase.GovernancePhase | None, Field( description="The phase of the governed action's lifecycle. 'purchase': initial commitment (create_media_buy, acquire_rights, activate_signal). 'modification': update to existing commitment. 'delivery': periodic delivery or usage reporting. Defaults to 'purchase' if omitted." ), ] = governance_phase.GovernancePhase.purchase planned_delivery: Annotated[ planned_delivery_1.PlannedDelivery | None, Field(description='What the seller will actually deliver. Present on execution checks.'), ] = None delivery_metrics: Annotated[ DeliveryMetrics | None, Field( description="Actual delivery performance data. MUST be present for 'delivery' phase. The governance agent compares these metrics against the planned delivery to detect drift." ), ] = None modification_summary: Annotated[ str | None, Field( description="Human-readable summary of what changed. SHOULD be present for 'modification' phase.", max_length=1000, ), ] = None invoice_recipient: Annotated[ business_entity.BusinessEntity | None, Field( description='Invoice recipient from the purchase request. MUST be present when the tool payload includes invoice_recipient, so the governance agent can validate billing changes.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var caller : pydantic.networks.AnyUrlvar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar delivery_metrics : adcp.types.generated_poc.governance.check_governance_request.DeliveryMetrics | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar governance_context : str | Nonevar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar model_configvar modification_summary : str | Nonevar payload : dict[str, typing.Any] | Nonevar phase : adcp.types.generated_poc.enums.governance_phase.GovernancePhase | Nonevar plan_id : strvar planned_delivery : adcp.types.generated_poc.core.planned_delivery.PlannedDelivery | Nonevar purchase_type : adcp.types.generated_poc.enums.purchase_type.PurchaseType | Nonevar tool : str | None
Inherited members
class CheckGovernanceResponse (**data: Any)-
Expand source code
class CheckGovernanceResponse(AdcpVersionEnvelope): @model_validator(mode='before') @classmethod def _status_to_verdict(cls, data: Any) -> Any: if isinstance(data, dict) and 'verdict' not in data and 'status' in data: data = dict(data) data['verdict'] = data['status'] return data model_config = ConfigDict( extra='allow', ) check_id: Annotated[ str, Field( description='Unique identifier for this governance check record. Use in report_plan_outcome to link outcomes to the check that authorized them.' ), ] verdict: Annotated[ governance_decision.GovernanceDecision, Field( description='Governance verdict: approved | denied | conditions. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization. The enum values are unchanged; only the property name moved.' ), ] plan_id: Annotated[str, Field(description='Echoed from request.')] explanation: Annotated[ str, Field(description='Human-readable explanation of the governance decision.') ] findings: Annotated[ list[Finding] | None, Field( description="Specific issues found during the governance check. Present when verdict is 'denied' or 'conditions'. MAY also be present on 'approved' for informational findings (e.g., budget approaching limit)." ), ] = None conditions: Annotated[ list[Condition] | None, Field( description="Present when verdict is 'conditions'. Specific adjustments the caller must make. After applying conditions, the caller MUST re-call check_governance with the adjusted parameters before proceeding." ), ] = None expires_at: Annotated[ AwareDatetime | None, Field( description="When this approval expires. Present when verdict is 'approved' or 'conditions'. The caller must act before this time or re-call check_governance. A lapsed approval is no approval." ), ] = None next_check: Annotated[ AwareDatetime | None, Field( description='When the seller should next call check_governance with delivery metrics. Present when the governance agent expects ongoing delivery reporting.' ), ] = None categories_evaluated: Annotated[ list[str] | None, Field( description="Governance categories evaluated during this check. Each value is an **agent-internal** label (e.g., `budget_authority`, `regulatory_compliance`, or any internal-reviewer key the agent's policy model defines) — not a protocol-level enum. Since one governance agent per account composes all specialist review behind its single endpoint, `categories_evaluated` is how that internal decomposition surfaces to auditors. Consumers MUST treat values as opaque labels for display and audit, not as a machine-level contract." ), ] = None policies_evaluated: Annotated[ list[str] | None, Field( description="Policy IDs evaluated during this check. Includes registry policy IDs (resolved via the policy registry) and any inline `policy_id`s declared in the plan's `custom_policies`." ), ] = None mode: Annotated[ governance_mode.GovernanceMode | None, Field( description='Governance enforcement mode active when this check was evaluated. Allows counterparties, regulators, and auditors to distinguish whether a finding blocked execution (enforce) or was logged silently (audit).' ), ] = None governance_context: Annotated[ str | None, Field( description="Governance context token for this governed action. The buyer MUST attach this to the protocol envelope when sending the purchase request (media buy, rights acquisition, signal activation) to the seller. The seller MUST persist it and include it on all subsequent check_governance calls for this action's lifecycle.\n\nValue format: in 3.0 governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged so auditors can verify downstream. In 3.1 all sellers MUST verify per the checklist. Non-JWS values from pre-3.0 governance agents are deprecated and will be rejected in 3.1.\n\nSellers that implement verification MUST verify signature, `aud`, `exp`, `jti` replay, and revocation per the profile before treating the request as governance-approved. This is the primary correlation key for audit and reporting across the governance lifecycle — the governance agent decodes its own signed token to look up internal plan state (buyer correlation IDs, policy decision log, etc.).", max_length=4096, min_length=1, pattern='^[\\x20-\\x7E]+$', ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var categories_evaluated : list[str] | Nonevar check_id : strvar conditions : list[adcp.types.generated_poc.governance.check_governance_response.Condition] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar explanation : strvar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar findings : list[adcp.types.generated_poc.governance.check_governance_response.Finding] | Nonevar governance_context : str | Nonevar mode : adcp.types.generated_poc.enums.governance_mode.GovernanceMode | Nonevar model_configvar next_check : pydantic.types.AwareDatetime | Nonevar plan_id : strvar policies_evaluated : list[str] | Nonevar verdict : adcp.types.generated_poc.enums.governance_decision.GovernanceDecision
Inherited members
class Checkpoint (*args, **kwargs)-
Expand source code
class Checkpoint(TypedDict): """Persistable session-resume state for an A2A ``ADCPClient``. The minimal set of fields needed to reconnect to an in-flight A2A conversation after a process restart. Produced by ``ADCPClient.checkpoint()``; consumed by ``ADCPClient.from_checkpoint()``. - ``agent_id`` — binds the checkpoint to the agent that minted it, so a restore against the wrong ``AgentConfig`` fails loudly instead of sending Agent A's ids to Agent B. - ``context_id`` — the A2A conversation id. - ``active_task_id`` — the in-flight task the next message must echo; ``None`` if no task is pending. """ agent_id: str context_id: str | None active_task_id: str | NonePersistable session-resume state for an A2A
ADCPClient.The minimal set of fields needed to reconnect to an in-flight A2A conversation after a process restart. Produced by
ADCPClient.checkpoint(); consumed byADCPClient.from_checkpoint().agent_id— binds the checkpoint to the agent that minted it, so a restore against the wrongAgentConfigfails loudly instead of sending Agent A's ids to Agent B.context_id— the A2A conversation id.active_task_id— the in-flight task the next message must echo;Noneif no task is pending.
Ancestors
- builtins.dict
Class variables
var active_task_id : str | Nonevar agent_id : strvar context_id : str | None
class ComplyTestControllerRequest (**data: Any)-
Expand source code
class ComplyTestControllerRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) scenario: Annotated[ str, Field( description="Test scenario to execute. 'list_scenarios' discovers supported scenarios. 'force_*' and 'simulate_*' trigger state transitions. 'force_creative_purge' destroys or tombstones a sandbox creative so account-level `creative.purged` webhooks can be observed where the seller supports the lifecycle surface. 'force_create_media_buy_arm', 'force_get_products_arm', and 'force_get_signals_arm' register one-shot response-arm directives for the next matching operation from the caller's authenticated sandbox account + principal pair. 'seed_*' scenarios pre-populate fixtures (account, product, pricing option, creative, plan, media buy, creative format, measurement catalog) so storyboards can reference fixture IDs and external-catalog facts without implementers guessing which fixtures the conformance suite expects. 'query_upstream_traffic' returns outbound HTTP calls the agent has made since session start (or since a caller-supplied timestamp), so storyboard runners can assert upstream side-effects via `check: upstream_traffic`. 'query_provenance_audit_observations' returns sandbox-only audit observations recorded for a submitted creative so storyboards can assert non-blocking governance observations without exposing an internal audit log on public seller responses. 'force_upstream_unavailable' marks a named upstream dependency as unreachable for the duration of the compliance session (or until the seller resets it), so storyboards can exercise stale-cache fallback paths - see the `stale_response_advisory` universal storyboard. The contract raises the bar against unintentional facades - adapters that satisfy AdCP schema requirements with synthetic placeholders. It is NOT an adversarial integrity check: adopters self-report their own traffic. Adopters MUST scope the response to traffic caused by the requesting principal's session/auth context - cross-caller traffic MUST NOT be returned, regardless of the supplied since_timestamp. Multi-tenant sandboxes MUST key the recording buffer on the comply_test_controller invocation's auth principal. Runners and sellers MUST accept unknown scenario strings - new scenarios may be added in additive releases." ), ] params: Annotated[ Params | None, Field( description='Scenario-specific parameters. Required for all scenarios except list_scenarios.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = None account: Annotated[ Account | None, Field( description="Sandbox account assertion. The runner MUST set sandbox: true on every comply_test_controller request. The seller MUST refuse the request (returning a structured error) if the targeted account is not a sandbox account in the seller's persisted records. This field is a caller-side declaration of intent — it does not grant sandbox status; sellers verify against their own account state. The (Sandbox) verification tier is defined by this gate: real production endpoints accept sandbox-flagged traffic and process it without real-world side effects, no separate test-mode endpoint required. See spec issue #3755 and the (Sandbox) framing in #4379." ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.compliance.comply_test_controller_request.Account | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar params : adcp.types.generated_poc.compliance.comply_test_controller_request.Params | Nonevar scenario : str
Inherited members
class ComplyTestControllerResponse (**data: Any)-
Expand source code
class ComplyTestControllerResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
class ComplyTestControllerResponse1 (**data: Any)-
Expand source code
class ComplyTestControllerResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class ConfigurationError (message: str,
agent_id: str | None = None,
agent_uri: str | None = None,
suggestion: str | None = None)-
Expand source code
class ConfigurationError(ADCPError): """Invalid SDK configuration detected at construction time. Raised when a value passed to a client/server constructor cannot be reconciled with the SDK's compile-time pin — most commonly a cross-major ``adcp_version`` (e.g. ``adcp_version="4.0"`` against an SDK built for AdCP 3.x), or an unparseable version string. Recovery: install the SDK major that targets the wire version you want to speak. Cross-major pinning is not supported within a single SDK major. """Invalid SDK configuration detected at construction time.
Raised when a value passed to a client/server constructor cannot be reconciled with the SDK's compile-time pin — most commonly a cross-major
adcp_version(e.g.adcp_version="4.0"against an SDK built for AdCP 3.x), or an unparseable version string.Recovery: install the SDK major that targets the wire version you want to speak. Cross-major pinning is not supported within a single SDK major.
Initialize exception with context.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class ConsentBasis (*args, **kwds)-
Expand source code
class ConsentBasis(StrEnum): consent = 'consent' legitimate_interest = 'legitimate_interest' contract = 'contract' legal_obligation = 'legal_obligation'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var consentvar contractvar legal_obligationvar legitimate_interest
class ContentIdType (*args, **kwds)-
Expand source code
class ContentIdType(StrEnum): sku = 'sku' gtin = 'gtin' offering_id = 'offering_id' job_id = 'job_id' hotel_id = 'hotel_id' flight_id = 'flight_id' vehicle_id = 'vehicle_id' listing_id = 'listing_id' store_id = 'store_id' program_id = 'program_id' destination_id = 'destination_id' app_id = 'app_id'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var app_idvar destination_idvar flight_idvar gtinvar hotel_idvar job_idvar listing_idvar offering_idvar program_idvar skuvar store_idvar vehicle_id
class ContextMatchRequest (**data: Any)-
Expand source code
class ContextMatchRequest(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) field_schema: Annotated[ AnyUrl | None, Field( alias='$schema', description='Optional schema URI for validation. Ignored at runtime.' ), ] = None adcp_version: Annotated[ str | None, Field( description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin. Inlined here (rather than via core/version-envelope.json allOf) so this schema can keep `additionalProperties: false` — the privacy boundary on this endpoint is contract-bearing.', pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', ), ] = None adcp_major_version: Annotated[ int | None, Field( description='DEPRECATED in favor of adcp_version. Removed in 4.0. Inlined alongside adcp_version to preserve strict-mode on this endpoint.', ge=1, le=99, ), ] = None type: Annotated[ Literal['context_match_request'], Field(description='Message type discriminator for deserialization.'), ] = 'context_match_request' protocol_version: Annotated[ str | None, Field( description='TMP protocol version. Allows receivers to handle semantic differences across versions.' ), ] = '1.0' request_id: Annotated[ str, Field( description='Unique request identifier. MUST NOT correlate with any identity match request_id.' ), ] property_rid: Annotated[ UUID, Field( description='Property catalog UUID (UUID v7). Globally unique, stable identifier assigned by the property catalog. The primary key for TMP matching and property list targeting.' ), ] property_id: Annotated[ property_id_1.PropertyId | None, Field( description="Publisher's human-readable property slug (e.g., 'cnn_homepage'). Optional when property_rid is present. Useful for logging and debugging." ), ] = None property_type: Annotated[ property_type_1.PropertyType, Field(description='Type of the publisher property') ] placement_id: Annotated[ str, Field( description="Placement identifier from the publisher's placement registry in adagents.json. Identifies where on the property this ad opportunity exists. One placement per request." ), ] seller_agent_url: Annotated[ AnyUrl, Field( description="API endpoint URL of the seller agent issuing this request. The provider uses this to resolve the active package set it has synced for this seller; when `package_ids` is omitted, evaluation occurs against that full set. If `seller_agent_url` does not match any seller the provider has synced packages for, the provider MUST return an empty offer set — it MUST NOT fall back to another seller's active set. The value identifies the asking seller, is identical for every user on a given placement, and carries no user identity, so it neither varies the request per user nor weakens the context/identity decorrelation boundary. Compared using the AdCP URL canonicalization rules, not byte-equality — see docs/reference/url-canonicalization. Consistent with `seller_agent_url` on the identity match request, `seller_agent.agent_url` on `AvailablePackage`, and `agent_url` in `adagents.json`." ), ] artifact: Annotated[ artifact_1.Artifact | None, Field( description='Full content artifact adjacent to this ad opportunity. Same schema used for content standards evaluation. The publisher sends the artifact when they want the buyer to evaluate the full content. Contractual protections govern buyer use. TEE deployment upgrades contractual trust to cryptographic verification. Publishers MUST NOT include asset access credentials (bearer tokens, service accounts) — the router fans out to multiple buyer agents. For secured assets, use signed URLs with short expiry. Routers MUST strip access fields from artifacts before forwarding.' ), ] = None artifact_refs: Annotated[ list[ArtifactRef] | None, Field( description='Public content references adjacent to this ad opportunity. Each artifact identifies content via a public identifier the buyer can resolve independently — no private registry sync required.', max_length=20, min_length=1, ), ] = None geo: Annotated[ Geo | None, Field( description='Coarse geographic location of the viewer. Publisher controls granularity — country is sufficient for regulatory compliance and volume filtering, region or metro helps with campaign targeting and valuation. Coarsened to prevent user identification: no postcode, no coordinates. All fields optional.' ), ] = None context_signals: Annotated[ ContextSignals | None, Field( description='Pre-computed classifier outputs for the content environment. Use when the publisher wants to provide classified context without sharing content or public references. Can supplement artifact_refs (e.g., URL + pre-classified topics) or replace them entirely (e.g., ephemeral conversation turns). Raw content MUST NOT be included — only classified outputs. The publisher is the classifier boundary.' ), ] = None package_ids: Annotated[ list[str] | None, Field( description='Restrict evaluation to specific packages. When omitted, the provider evaluates all eligible packages for this placement (the common case). MUST NOT vary by user — the same package_ids must be sent for every user on a given placement. User-dependent filtering leaks identity into the context path.', max_length=500, min_length=1, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var adcp_major_version : int | Nonevar adcp_version : str | Nonevar artifact : adcp.types.generated_poc.content_standards.artifact.Artifact | Nonevar artifact_refs : list[adcp.types.generated_poc.tmp.context_match_request.ArtifactRef] | Nonevar context_signals : adcp.types.generated_poc.tmp.context_match_request.ContextSignals | Nonevar field_schema : pydantic.networks.AnyUrl | Nonevar geo : adcp.types.generated_poc.tmp.context_match_request.Geo | Nonevar model_configvar package_ids : list[str] | Nonevar placement_id : strvar property_id : adcp.types.generated_poc.core.property_id.PropertyId | Nonevar property_rid : uuid.UUIDvar property_type : adcp.types.generated_poc.enums.property_type.PropertyTypevar protocol_version : str | Nonevar request_id : strvar seller_agent_url : pydantic.networks.AnyUrlvar type : Literal['context_match_request']
Inherited members
class ContextMatchResponse (**data: Any)-
Expand source code
class ContextMatchResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) type: Annotated[ Literal['context_match_response'], Field(description='Message type discriminator for deserialization.'), ] = 'context_match_response' request_id: Annotated[ str, Field(description='Echoed request identifier from the context match request') ] offers: Annotated[ list[offer.Offer], Field( description='Offers from the buyer, one per activated package. An empty array means no packages matched. For simple activation, each offer has just package_id. For richer responses, offers include brand, price, summary, and creative manifest.' ), ] cache_ttl: Annotated[ int | None, Field( description='Optional override for the default 5-minute cache TTL, in seconds. When present, the router MUST use this value instead of its default. Set to 0 to disable caching (e.g., when targeting configuration has just changed).', ge=0, le=86400, ), ] = None signals: Annotated[ Signals | None, Field( description='Response-level targeting signals for ad server pass-through. In the GAM case, these carry the key-value pairs that trigger line items. Not per-offer — applies to the response as a whole.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var cache_ttl : int | Nonevar model_configvar offers : list[adcp.types.generated_poc.tmp.offer.Offer]var request_id : strvar signals : adcp.types.generated_poc.tmp.context_match_response.Signals | Nonevar type : Literal['context_match_response']
Inherited members
class ContextObject (**data: Any)-
Expand source code
class ContextObject(AdCPBaseModel): model_config = ConfigDict( extra='allow', )Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class CpaPricingOption (**data: Any)-
Expand source code
class CpaPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[ Literal['cpa'], Field(description='Cost per acquisition (conversion event)') ] = 'cpa' event_type: Annotated[ event_type_1.EventType, Field( description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' ), ] custom_event_name: Annotated[ str | None, Field( description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." ), ] = None event_source_id: Annotated[ str | None, Field( description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' ), ] = None currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) ] min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar custom_event_name : str | Nonevar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar event_source_id : str | Nonevar event_type : adcp.types.generated_poc.enums.event_type.EventTypevar fixed_price : floatvar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar pricing_model : Literal['cpa']var pricing_option_id : str
Inherited members
class CpcPricingOption (**data: Any)-
Expand source code
class CpcPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['cpc']var pricing_option_id : str
Inherited members
class CpcvPricingOption (**data: Any)-
Expand source code
class CpcvPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[ Literal['cpcv'], Field(description='Cost per completed view (100% completion)') ] = 'cpcv' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['cpcv']var pricing_option_id : str
Inherited members
class CpmPricingOption (**data: Any)-
Expand source code
class CpmPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['cpm']var pricing_option_id : str
class CpmAuctionPricingOption (**data: Any)-
Expand source code
class CpmPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['cpm']var pricing_option_id : str
class CpmFixedRatePricingOption (**data: Any)-
Expand source code
class CpmPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['cpm']var pricing_option_id : str
Inherited members
class CppPricingOption (**data: Any)-
Expand source code
class CppPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None parameters: Annotated[ Parameters, Field(description='CPP-specific parameters for demographic targeting') ] min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar min_spend_per_package : float | Nonevar model_configvar parameters : adcp.types.generated_poc.pricing_options.cpp_option.Parametersvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['cpp']var pricing_option_id : str
Inherited members
class CpvPricingOption (**data: Any)-
Expand source code
class CpvPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None parameters: Annotated[ Parameters, Field(description='CPV-specific parameters defining the view threshold') ] min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar parameters : adcp.types.generated_poc.pricing_options.cpv_option.Parametersvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['cpv']var pricing_option_id : str
Inherited members
class CreateContentStandardsSuccessResponse (**data: Any)-
Expand source code
class CreateContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
class CreateContentStandardsResponse1 (**data: Any)-
Expand source code
class CreateContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
class CreateContentStandardsErrorResponse (**data: Any)-
Expand source code
class CreateContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class CreateMediaBuyRequest (**data: Any)-
Expand source code
class CreateMediaBuyRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. If a request with the same idempotency_key and account has already been processed, the seller returns the existing media buy rather than creating a duplicate. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] plan_id: Annotated[ str | None, Field( description='Campaign governance plan identifier. Required when the account has governance_agents. The seller includes this in the committed check_governance request so the governance agent can validate against the correct plan.' ), ] = None account: Annotated[ account_ref.AccountReference, Field( description='Account to bill for this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.' ), ] proposal_id: Annotated[ str | None, Field( description="ID of a committed proposal from get_products to execute. When provided with total_budget, the publisher converts the proposal's allocation percentages into packages automatically. Alternative to providing packages array. If the referenced proposal has proposal_status: 'draft', the seller MUST reject with PROPOSAL_NOT_COMMITTED; the buyer finalizes first via get_products refine action 'finalize'." ), ] = None total_budget: Annotated[ TotalBudget | None, Field( description="Total budget for the media buy when executing a proposal. The publisher applies the proposal's allocation percentages to this amount to derive package budgets." ), ] = None packages: Annotated[ Sequence[package_request.PackageRequest] | None, Field( description="Array of package configurations. Required when not using proposal_id. When executing a proposal, this can be omitted and packages will be derived from the proposal's allocations.", min_length=1, ), ] = None brand: Annotated[ brand_ref.BrandReference, Field( description='Brand reference for this media buy. Resolved to full brand identity at execution time from brand.json or the registry.' ), ] advertiser_industry: Annotated[ advertiser_industry_1.AdvertiserIndustry | None, Field( description="Industry classification for this specific campaign. A brand may operate across multiple industries (brand.json industries field), but each media buy targets one. For example, a consumer health company running a wellness campaign sends 'healthcare.wellness', not 'cpg'. Sellers map this to platform-native codes (e.g., Spotify ADV categories, LinkedIn industry IDs). When omitted, sellers may infer from the brand manifest's industries field." ), ] = None invoice_recipient: Annotated[ business_entity.BusinessEntity | None, Field( description="Override the account's default billing entity for this specific buy. When provided, the seller invoices this entity instead. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." ), ] = None io_acceptance: Annotated[ IoAcceptance | None, Field( description="Acceptance of an insertion order from a committed proposal. Required when the proposal's insertion_order has requires_signature: true. References the io_id from the proposal's insertion_order." ), ] = None po_number: Annotated[str | None, Field(description='Purchase order number for tracking')] = None agency_estimate_number: Annotated[ str | None, Field( description="Agency estimate or authorization number. Primary financial reference for broadcast buys — links the order to the agency's media plan and billing system. Travels with the order and creative traffic identifiers through the transaction lifecycle.", max_length=100, ), ] = None start_time: start_timing.StartTiming end_time: Annotated[ AwareDatetime, Field(description='Campaign end date/time in ISO 8601 format') ] paused: Annotated[ bool | None, Field( description="Create the media buy in a paused delivery state. When true, and the buy would otherwise be active because creatives are assigned and the flight has started, the seller returns media_buy_status 'paused'. Setup blockers still take precedence: a buy with no creatives remains 'pending_creatives', and a future-dated buy remains 'pending_start' until its flight can start. Defaults to false." ), ] = False push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async task status notifications. Publisher will send webhooks when status changes (working, input-required, completed, failed, canceled). Buyers SHOULD supply `push_notification_config_1.operation_id` as the canonical correlation value; publishers echo that field back verbatim in webhook payloads and MUST NOT parse the URL to derive it.' ), ] = None reporting_webhook: Annotated[ reporting_webhook_1.ReportingWebhook | None, Field(description='Optional webhook configuration for automated reporting delivery'), ] = None artifact_webhook: Annotated[ ArtifactWebhook | None, Field( description='Optional webhook configuration for content artifact delivery. Used by governance agents to validate content adjacency. Seller pushes artifacts to this endpoint; orchestrator forwards to governance agent for validation.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar advertiser_industry : adcp.types.generated_poc.enums.advertiser_industry.AdvertiserIndustry | Nonevar agency_estimate_number : str | Nonevar artifact_webhook : adcp.types.generated_poc.media_buy.create_media_buy_request.ArtifactWebhook | Nonevar brand : adcp.types.generated_poc.core.brand_ref.BrandReferencevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar end_time : pydantic.types.AwareDatetimevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar io_acceptance : adcp.types.generated_poc.media_buy.create_media_buy_request.IoAcceptance | Nonevar model_configvar packages : collections.abc.Sequence[adcp.types.generated_poc.media_buy.package_request.PackageRequest] | Nonevar paused : bool | Nonevar plan_id : str | Nonevar po_number : str | Nonevar proposal_id : str | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar reporting_webhook : adcp.types.generated_poc.core.reporting_webhook.ReportingWebhook | Nonevar start_time : adcp.types.generated_poc.core.start_timing.StartTimingvar total_budget : adcp.types.generated_poc.media_buy.create_media_buy_request.TotalBudget | None
Inherited members
class CreateMediaBuySuccessResponse (**data: Any)-
Expand source code
class CreateMediaBuyResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str account: account_1.Account | None = None invoice_recipient: business_entity_1.BusinessEntity | None = None media_buy_status: media_buy_status_1.MediaBuyStatus | None = None status: Literal['completed'] confirmed_at: AwareDatetime creative_deadline: AwareDatetime | None = None revision: Annotated[int, Field(ge=1)] currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None total_budget: Annotated[float, Field(ge=0)] | None = None valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None packages: list[package_1.Package] planned_delivery: planned_delivery_1.PlannedDelivery | None = None sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = None @model_validator(mode='before') @classmethod def _normalize_legacy_status(cls, data: Any) -> Any: if not isinstance(data, dict): return data raw_status = unwrap_enum_value(data.get('status')) media_buy_status = unwrap_enum_value(data.get('media_buy_status')) if raw_status is None: data = dict(data) data['status'] = 'completed' elif raw_status == 'completed': data = dict(data) data['status'] = 'completed' elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: data = dict(data) data['media_buy_status'] = raw_status data['status'] = 'completed' elif media_buy_status is not None and raw_status == media_buy_status: data = dict(data) data['status'] = 'completed' return dataBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account.Account | Nonevar available_actions : list[adcp.types.generated_poc.core.media_buy_available_action.MediaBuyAvailableAction] | Nonevar confirmed_at : pydantic.types.AwareDatetimevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_deadline : pydantic.types.AwareDatetime | Nonevar currency : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar media_buy_id : strvar media_buy_status : adcp.types.generated_poc.enums.media_buy_status.MediaBuyStatus | Nonevar model_configvar packages : list[adcp.types.generated_poc.core.package.Package]var planned_delivery : adcp.types.generated_poc.core.planned_delivery.PlannedDelivery | Nonevar revision : intvar sandbox : bool | Nonevar status : Literal['completed']var total_budget : float | Nonevar valid_actions : list[adcp.types.generated_poc.enums.media_buy_valid_action.MediaBuyValidAction] | None
class CreateMediaBuyResponse1 (**data: Any)-
Expand source code
class CreateMediaBuyResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str account: account_1.Account | None = None invoice_recipient: business_entity_1.BusinessEntity | None = None media_buy_status: media_buy_status_1.MediaBuyStatus | None = None status: Literal['completed'] confirmed_at: AwareDatetime creative_deadline: AwareDatetime | None = None revision: Annotated[int, Field(ge=1)] currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None total_budget: Annotated[float, Field(ge=0)] | None = None valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None packages: list[package_1.Package] planned_delivery: planned_delivery_1.PlannedDelivery | None = None sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = None @model_validator(mode='before') @classmethod def _normalize_legacy_status(cls, data: Any) -> Any: if not isinstance(data, dict): return data raw_status = unwrap_enum_value(data.get('status')) media_buy_status = unwrap_enum_value(data.get('media_buy_status')) if raw_status is None: data = dict(data) data['status'] = 'completed' elif raw_status == 'completed': data = dict(data) data['status'] = 'completed' elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: data = dict(data) data['media_buy_status'] = raw_status data['status'] = 'completed' elif media_buy_status is not None and raw_status == media_buy_status: data = dict(data) data['status'] = 'completed' return dataBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account.Account | Nonevar available_actions : list[adcp.types.generated_poc.core.media_buy_available_action.MediaBuyAvailableAction] | Nonevar confirmed_at : pydantic.types.AwareDatetimevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_deadline : pydantic.types.AwareDatetime | Nonevar currency : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar media_buy_id : strvar media_buy_status : adcp.types.generated_poc.enums.media_buy_status.MediaBuyStatus | Nonevar model_configvar packages : list[adcp.types.generated_poc.core.package.Package]var planned_delivery : adcp.types.generated_poc.core.planned_delivery.PlannedDelivery | Nonevar revision : intvar sandbox : bool | Nonevar status : Literal['completed']var total_budget : float | Nonevar valid_actions : list[adcp.types.generated_poc.enums.media_buy_valid_action.MediaBuyValidAction] | None
Inherited members
class CreateMediaBuyErrorResponse (**data: Any)-
Expand source code
class CreateMediaBuyResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class CreateMediaBuySubmittedResponse (**data: Any)-
Expand source code
class CreateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal[<TaskStatus.submitted: 'submitted'>]var task_id : str
Inherited members
class Creative (**data: Any)-
Expand source code
class Creative(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) creative_id: Annotated[str, Field(description='Creative identifier')] media_buy_id: Annotated[ str | None, Field( description="Publisher's media buy identifier for this creative. Present when the request spanned multiple media buys, so the buyer can correlate each creative to its media buy." ), ] = None format_id: Annotated[ format_id_1.FormatReferenceStructuredObject | None, Field(description='Format of this creative'), ] = None totals: Annotated[ delivery_metrics.DeliveryMetrics | None, Field(description='Aggregate delivery metrics across all variants of this creative'), ] = None variant_count: Annotated[ int | None, Field( description='Total number of variants for this creative. When max_variants was specified in the request, this may exceed the number of items in the variants array.', ge=0, ), ] = None variants: Annotated[ list[creative_variant.CreativeVariant], Field( description='Variant-level delivery breakdown. Each variant includes the rendered manifest and delivery metrics. For standard creatives, contains a single variant. For asset group optimization, one per combination. For generative creative, one per generated execution. Empty when a creative has no variants yet.' ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var creative_id : strvar format_id : adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject | Nonevar media_buy_id : str | Nonevar model_configvar totals : adcp.types.generated_poc.core.delivery_metrics.DeliveryMetrics | Nonevar variant_count : int | Nonevar variants : list[adcp.types.generated_poc.core.creative_variant.CreativeVariant]
class SyncCreativeResult (**data: Any)-
Expand source code
class Creative(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') creative_id: str account: account_1.Account | None = None action: creative_action_1.CreativeAction status: creative_status_1.CreativeStatus | None = None platform_id: str | None = None changes: list[str] | None = None errors: list[error_1.Error] | None = None warnings: list[str] | None = None preview_url: AnyUrl | None = None expires_at: AwareDatetime | None = None assigned_to: list[str] | None = None assignment_errors: dict[Annotated[str, StringConstraints(pattern='^[a-zA-Z0-9_-]+$')], str] | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account.Account | Nonevar action : adcp.types.generated_poc.enums.creative_action.CreativeActionvar assigned_to : list[str] | Nonevar assignment_errors : dict[str, str] | Nonevar changes : list[str] | Nonevar creative_id : strvar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar model_configvar platform_id : str | Nonevar preview_url : pydantic.networks.AnyUrl | Nonevar status : adcp.types.generated_poc.enums.creative_status.CreativeStatus | Nonevar warnings : list[str] | None
Inherited members
class CreativeApproval (**data: Any)-
Expand source code
class CreativeApproval(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) creative_id: Annotated[str, Field(description='Creative identifier')] approval_status: creative_approval_status.CreativeApprovalStatus rejection_reason: Annotated[ str | None, Field( description="Human-readable explanation of why the creative was rejected. Present only when approval_status is 'rejected'." ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var approval_status : adcp.types.generated_poc.enums.creative_approval_status.CreativeApprovalStatusvar creative_id : strvar model_configvar rejection_reason : str | None
Inherited members
class CreativeApprovalStatus (*args, **kwds)-
Expand source code
class CreativeApprovalStatus(StrEnum): pending_review = 'pending_review' approved = 'approved' rejected = 'rejected'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var approvedvar pending_reviewvar rejected
class CreativeFilters (**data: Any)-
Expand source code
class CreativeFilters(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) accounts: Annotated[ list[account_ref.AccountReference] | None, Field( description='Filter creatives by owning accounts. Useful for agencies managing multiple client accounts.', min_length=1, ), ] = None statuses: Annotated[ list[creative_status.CreativeStatus] | None, Field(description='Filter by creative approval statuses', min_length=1), ] = None tags: Annotated[ list[str] | None, Field(description='Filter by creative tags (all tags must match)', min_length=1), ] = None tags_any: Annotated[ list[str] | None, Field(description='Filter by creative tags (any tag must match)', min_length=1), ] = None name_contains: Annotated[ str | None, Field(description='Filter by creative names containing this text (case-insensitive)'), ] = None creative_ids: Annotated[ list[str] | None, Field(description='Filter by specific creative IDs', max_length=100, min_length=1), ] = None created_after: Annotated[ AwareDatetime | None, Field(description='Filter creatives created after this date (ISO 8601)'), ] = None created_before: Annotated[ AwareDatetime | None, Field(description='Filter creatives created before this date (ISO 8601)'), ] = None updated_after: Annotated[ AwareDatetime | None, Field(description='Filter creatives last updated after this date (ISO 8601)'), ] = None updated_before: Annotated[ AwareDatetime | None, Field(description='Filter creatives last updated before this date (ISO 8601)'), ] = None assigned_to_packages: Annotated[ list[str] | None, Field( description='Filter creatives assigned to any of these packages. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.', min_length=1, ), ] = None media_buy_ids: Annotated[ list[str] | None, Field( description='Filter creatives assigned to any of these media buys. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.', min_length=1, ), ] = None unassigned: Annotated[ bool | None, Field( description='Filter for unassigned creatives when true, assigned creatives when false. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.' ), ] = None has_served: Annotated[ bool | None, Field( description='When true, return only creatives that have served at least one impression. When false, return only creatives that have never served.' ), ] = None concept_ids: Annotated[ list[str] | None, Field( description='Filter by creative concept IDs. Concepts group related creatives across sizes and formats (e.g., Flashtalking concepts, Celtra campaign folders, CM360 creative groups).', min_length=1, ), ] = None format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description='Filter by structured format IDs. Returns creatives that match any of these formats.', min_length=1, ), ] = None has_variables: Annotated[ bool | None, Field( description='When true, return only creatives with dynamic variables (DCO). When false, return only static creatives.' ), ] = None ext: Annotated[ ext_1.ExtensionObject | None, Field( description='Vendor-namespaced extension parameters for seller- or platform-specific creative filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; avoid unbounded logging or labels, and do not interpolate values into caller-visible error strings, LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var accounts : list[adcp.types.generated_poc.core.account_ref.AccountReference] | Nonevar assigned_to_packages : list[str] | Nonevar concept_ids : list[str] | Nonevar created_after : pydantic.types.AwareDatetime | Nonevar created_before : pydantic.types.AwareDatetime | Nonevar creative_ids : list[str] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar has_served : bool | Nonevar has_variables : bool | Nonevar media_buy_ids : list[str] | Nonevar model_configvar name_contains : str | Nonevar statuses : list[adcp.types.generated_poc.enums.creative_status.CreativeStatus] | Nonevar unassigned : bool | Nonevar updated_after : pydantic.types.AwareDatetime | Nonevar updated_before : pydantic.types.AwareDatetime | None
Inherited members
class CreativeManifest (**data: Any)-
Expand source code
class CreativeManifest1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) format_id: Annotated[ format_id_1.FormatReferenceStructuredObject, Field( description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. 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 in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`." ), ] format_kind: Annotated[ canonical_format_kind.CanonicalFormatKind | None, Field( description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`." ), ] = None format_option_ref: Annotated[ format_option_ref_1.FormatOptionReference | None, Field( description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.' ), ] = None assets: Annotated[ dict[Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], asset_union.AssetVariant | Assets], Field( description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." ), ] brand: Annotated[ brand_ref.BrandReference | None, Field( description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context." ), ] = None rights: Annotated[ list[rights_constraint.RightsConstraint] | None, Field( description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' ), ] = None industry_identifiers: Annotated[ list[industry_identifier.IndustryIdentifier] | None, Field( description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' ), ] = None provenance: Annotated[ provenance_1.Provenance | None, Field( description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var assets : dict[str, adcp.types.generated_poc.core.assets.asset_union.AssetVariant | adcp.types.generated_poc.core.creative_manifest.Assets]var brand : adcp.types.generated_poc.core.brand_ref.BrandReference | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_id : adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObjectvar format_kind : adcp.types.generated_poc.core.canonical_format_kind.CanonicalFormatKind | Nonevar format_option_ref : adcp.types.generated_poc.core.format_option_ref.FormatOptionReference | Nonevar industry_identifiers : list[adcp.types.generated_poc.core.industry_identifier.IndustryIdentifier] | Nonevar model_configvar provenance : adcp.types.generated_poc.core.provenance.Provenance | Nonevar rights : list[adcp.types.generated_poc.core.rights_constraint.RightsConstraint] | None
Inherited members
class CreativeStatus (*args, **kwds)-
Expand source code
class CreativeStatus(StrEnum): processing = 'processing' pending_review = 'pending_review' approved = 'approved' suspended = 'suspended' rejected = 'rejected' archived = 'archived'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var approvedvar archivedvar pending_reviewvar processingvar rejectedvar suspended
class CreativeVariant (**data: Any)-
Expand source code
class CreativeVariant(DeliveryMetrics): variant_id: Annotated[str, Field(description='Platform-assigned identifier for this variant')] manifest: Annotated[ creative_manifest.CreativeManifest | None, Field( description='The rendered creative manifest for this variant — the actual output that was served, not the input assets. Contains format_id and the resolved assets (specific headline, image, video, etc. the platform selected or generated). For Tier 2, shows which asset combination was picked. For Tier 3, contains the generated assets which may differ entirely from the input brand identity. Pass to preview_creative to re-render.' ), ] = None generation_context: Annotated[ GenerationContext | None, Field( description='Input signals that triggered generation of this variant (Tier 3). Describes why the platform created this specific variant. Platforms should provide summarized or anonymized signals rather than raw user input. For web contexts, may include page topic or URL. For conversational contexts, an anonymized content signal. For search, query category or intent. When the content context is managed through AdCP content standards, reference the artifact directly via the artifact field.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.delivery_metrics.DeliveryMetrics
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var generation_context : adcp.types.generated_poc.core.creative_variant.GenerationContext | Nonevar manifest : adcp.types.generated_poc.core.creative_manifest.CreativeManifest | Nonevar model_configvar variant_id : str
Inherited members
class CursorStore (*args, **kwargs)-
Expand source code
@runtime_checkable class CursorStore(Protocol): """Protocol for persisting the feed cursor.""" async def load(self) -> str | None: """Load the saved cursor, or None if no cursor exists.""" ... async def save(self, cursor: str) -> None: """Save the current cursor.""" ...Protocol for persisting the feed cursor.
Ancestors
- typing.Protocol
- typing.Generic
Methods
async def load(self) ‑> str | None-
Expand source code
async def load(self) -> str | None: """Load the saved cursor, or None if no cursor exists.""" ...Load the saved cursor, or None if no cursor exists.
async def save(self, cursor: str) ‑> None-
Expand source code
async def save(self, cursor: str) -> None: """Save the current cursor.""" ...Save the current cursor.
class UrlDaastAsset (**data: Any)-
Expand source code
class DaastAsset1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) asset_type: Annotated[ Literal['daast'], Field( description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' ), ] = 'daast' daast_version: Annotated[ daast_version_1.DaastVersion | None, Field(description='DAAST specification version') ] = None duration_ms: Annotated[ int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) ] = None tracking_events: Annotated[ list[daast_tracking_event.DaastTrackingEvent] | None, Field(description='Tracking events supported by this DAAST tag'), ] = None companion_ads: Annotated[ bool | None, Field(description='Whether companion display ads are included') ] = None transcript_url: Annotated[ AnyUrl | None, Field(description='URL to text transcript of the audio content') ] = None provenance: Annotated[ provenance_1.Provenance | None, Field( description='Provenance metadata for this asset, overrides manifest-level provenance' ), ] = None delivery_type: Annotated[ Literal['url'], Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), ] = 'url' url: Annotated[ str, Field( description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var asset_type : Literal['daast']var companion_ads : bool | Nonevar daast_version : adcp.types.generated_poc.enums.daast_version.DaastVersion | Nonevar delivery_type : Literal['url']var duration_ms : int | Nonevar model_configvar provenance : adcp.types.generated_poc.core.provenance.Provenance | Nonevar tracking_events : list[adcp.types.generated_poc.enums.daast_tracking_event.DaastTrackingEvent] | Nonevar transcript_url : pydantic.networks.AnyUrl | Nonevar url : str
Inherited members
class InlineDaastAsset (**data: Any)-
Expand source code
class DaastAsset2(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) asset_type: Annotated[ Literal['daast'], Field( description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' ), ] = 'daast' daast_version: Annotated[ daast_version_1.DaastVersion | None, Field(description='DAAST specification version') ] = None duration_ms: Annotated[ int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) ] = None tracking_events: Annotated[ list[daast_tracking_event.DaastTrackingEvent] | None, Field(description='Tracking events supported by this DAAST tag'), ] = None companion_ads: Annotated[ bool | None, Field(description='Whether companion display ads are included') ] = None transcript_url: Annotated[ AnyUrl | None, Field(description='URL to text transcript of the audio content') ] = None provenance: Annotated[ provenance_1.Provenance | None, Field( description='Provenance metadata for this asset, overrides manifest-level provenance' ), ] = None delivery_type: Annotated[ Literal['inline'], Field(description='Discriminator indicating DAAST is delivered as inline XML content'), ] = 'inline' content: Annotated[str, Field(description='Inline DAAST XML content')]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var asset_type : Literal['daast']var companion_ads : bool | Nonevar content : strvar daast_version : adcp.types.generated_poc.enums.daast_version.DaastVersion | Nonevar delivery_type : Literal['inline']var duration_ms : int | Nonevar model_configvar provenance : adcp.types.generated_poc.core.provenance.Provenance | Nonevar tracking_events : list[adcp.types.generated_poc.enums.daast_tracking_event.DaastTrackingEvent] | Nonevar transcript_url : pydantic.networks.AnyUrl | None
Inherited members
class DateRange (**data: Any)-
Expand source code
class DateRange(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) start: Annotated[date_aliased, Field(description='Start date (inclusive), ISO 8601')] end: Annotated[date_aliased, Field(description='End date (inclusive), ISO 8601')]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var end : datetime.datevar model_configvar start : datetime.date
Inherited members
class DatetimeRange (**data: Any)-
Expand source code
class DatetimeRange(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) start: Annotated[AwareDatetime, Field(description='Start timestamp (inclusive), ISO 8601')] end: Annotated[AwareDatetime, Field(description='End timestamp (inclusive), ISO 8601')]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var end : pydantic.types.AwareDatetimevar model_configvar start : pydantic.types.AwareDatetime
Inherited members
class DeliveryStatus (*args, **kwds)-
Expand source code
class DeliveryStatus(StrEnum): delivering = 'delivering' not_delivering = 'not_delivering' completed = 'completed' budget_exhausted = 'budget_exhausted' flight_ended = 'flight_ended' goal_met = 'goal_met'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var budget_exhaustedvar completedvar deliveringvar flight_endedvar goal_metvar not_delivering
class PlatformDeployment (**data: Any)-
Expand source code
class Deployment1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) type: Annotated[ Literal['platform'], Field(description='Discriminator indicating this is a platform-based deployment'), ] = 'platform' platform: Annotated[str, Field(description='Platform identifier for DSPs')] account: Annotated[str | None, Field(description='Account identifier if applicable')] = None is_live: Annotated[ bool, Field(description='Whether signal is currently active on this deployment') ] activation_key: Annotated[ activation_key_1.ActivationKey | None, Field( description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.' ), ] = None estimated_activation_duration_minutes: Annotated[ float | None, Field( description='Estimated time to activate if not live, or to complete activation if in progress', ge=0.0, ), ] = None deployed_at: Annotated[ AwareDatetime | None, Field(description='Timestamp when activation completed (if is_live=true)'), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : str | Nonevar activation_key : adcp.types.generated_poc.core.activation_key.ActivationKey | Nonevar deployed_at : pydantic.types.AwareDatetime | Nonevar estimated_activation_duration_minutes : float | Nonevar is_live : boolvar model_configvar platform : strvar type : Literal['platform']
Inherited members
class AgentDeployment (**data: Any)-
Expand source code
class Deployment2(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) type: Annotated[ Literal['agent'], Field(description='Discriminator indicating this is an agent URL-based deployment'), ] = 'agent' agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')] account: Annotated[str | None, Field(description='Account identifier if applicable')] = None is_live: Annotated[ bool, Field(description='Whether signal is currently active on this deployment') ] activation_key: Annotated[ activation_key_1.ActivationKey | None, Field( description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.' ), ] = None estimated_activation_duration_minutes: Annotated[ float | None, Field( description='Estimated time to activate if not live, or to complete activation if in progress', ge=0.0, ), ] = None deployed_at: Annotated[ AwareDatetime | None, Field(description='Timestamp when activation completed (if is_live=true)'), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : str | Nonevar activation_key : adcp.types.generated_poc.core.activation_key.ActivationKey | Nonevar agent_url : pydantic.networks.AnyUrlvar deployed_at : pydantic.types.AwareDatetime | Nonevar estimated_activation_duration_minutes : float | Nonevar is_live : boolvar model_configvar type : Literal['agent']
Inherited members
class PlatformDestination (**data: Any)-
Expand source code
class Destination1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) type: Annotated[ Literal['platform'], Field(description='Discriminator indicating this is a platform-based deployment'), ] = 'platform' platform: Annotated[ str, Field(description="Platform identifier for DSPs (e.g., 'the-trade-desk', 'amazon-dsp')"), ] account: Annotated[ str | None, Field(description='Optional account identifier on the platform') ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : str | Nonevar model_configvar platform : strvar type : Literal['platform']
Inherited members
class AgentDestination (**data: Any)-
Expand source code
class Destination2(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) type: Annotated[ Literal['agent'], Field(description='Discriminator indicating this is an agent URL-based deployment'), ] = 'agent' agent_url: Annotated[ AnyUrl, Field(description='URL identifying the deployment agent (for sales agents, etc.)') ] account: Annotated[ str | None, Field(description='Optional account identifier on the agent') ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : str | Nonevar agent_url : pydantic.networks.AnyUrlvar model_configvar type : Literal['agent']
Inherited members
class DevicePlatform (*args, **kwds)-
Expand source code
class DevicePlatform(StrEnum): ios = 'ios' android = 'android' windows = 'windows' macos = 'macos' linux = 'linux' chromeos = 'chromeos' tvos = 'tvos' tizen = 'tizen' webos = 'webos' fire_os = 'fire_os' roku_os = 'roku_os' unknown = 'unknown'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var androidvar chromeosvar fire_osvar iosvar linuxvar macosvar roku_osvar tizenvar tvosvar unknownvar webosvar windows
class DeviceType (*args, **kwds)-
Expand source code
class DeviceType(StrEnum): desktop = 'desktop' mobile = 'mobile' tablet = 'tablet' ctv = 'ctv' dooh = 'dooh' unknown = 'unknown'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var ctvvar desktopvar doohvar mobilevar tabletvar unknown
class DirectoryPublisherEntry (**data: Any)-
Expand source code
class DirectoryPublisherEntry(AdCPBaseModel): """One publisher row in an AAO directory inverse-lookup response.""" publisher_domain: str discovery_method: DirectoryDiscoveryMethod manager_domain: str | None = None properties_authorized: int = Field(ge=0) properties_total: int = Field(ge=0) signing_keys_pinned: bool | None = None status: DirectoryEdgeStatus last_verified_at: datetime property_ids: list[str] | None = Field( default=None, description=( "Canonical property IDs the agent's selectors resolve to under " "this publisher. Present iff the request was made with " "include=['properties'] AND the directory server supports it " "(per adcp#4894). None signals count-only mode for downstream " "consumers." ), )One publisher row in an AAO directory inverse-lookup response.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var discovery_method : Literal['direct', 'authoritative_location', 'adagents_authoritative', 'ads_txt_managerdomain']var last_verified_at : datetime.datetimevar manager_domain : str | Nonevar model_configvar properties_total : intvar property_ids : list[str] | Nonevar publisher_domain : strvar signing_keys_pinned : bool | Nonevar status : Literal['authorized', 'revoked']
Inherited members
class DomainLookupResult (**data: Any)-
Expand source code
class DomainLookupResult(RegistryBaseModel): domain: Annotated[str, Field(examples=["examplepub.com"])] authorized_agents: list[DomainAuthorizedAgent] sales_agents_claiming: list[SalesAgentClaim]Base model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var domain : strvar model_configvar sales_agents_claiming : list[SalesAgentClaim]
class DownstreamConnectionRequirement (**data: Any)-
Expand source code
class DownstreamConnectionRequirement(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) provider: Annotated[ str | None, Field( description='Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.' ), ] = None connection_type: Annotated[ ConnectionType, Field( description='Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity.' ), ] required_for: Annotated[ list[RequiredForItem] | None, Field( description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' ), ] = None scope: Annotated[Scope | None, Field(description='Granularity of the downstream grant.')] = None status: Annotated[ Status | None, Field( description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' ), ] = None connection_id: Annotated[ str | None, Field( description='Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.' ), ] = None resource_ref: Annotated[ ResourceRef | None, Field( description='Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.' ), ] = None authorization_url: Annotated[ AnyUrl | None, Field( description='Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.' ), ] = None authorization_instructions: Annotated[ str | None, Field( description='Human-readable instructions for completing or restoring this downstream connection.' ), ] = None expires_at: Annotated[ AwareDatetime | None, Field(description='Expiration time for the downstream grant, when known.'), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var connection_id : str | Nonevar connection_type : adcp.types.generated_poc.core.downstream_connection_requirement.ConnectionTypevar expires_at : pydantic.types.AwareDatetime | Nonevar model_configvar provider : str | Nonevar required_for : list[adcp.types.generated_poc.core.downstream_connection_requirement.RequiredForItem] | Nonevar resource_ref : adcp.types.generated_poc.core.downstream_connection_requirement.ResourceRef | Nonevar scope : adcp.types.generated_poc.core.downstream_connection_requirement.Scope | Nonevar status : adcp.types.generated_poc.core.downstream_connection_requirement.Status | None
Inherited members
class Duration (**data: Any)-
Expand source code
class Duration(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) interval: Annotated[ int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) ] unit: Annotated[ Unit, Field( description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var interval : intvar model_configvar unit : adcp.types.generated_poc.core.duration.Unit
Inherited members
class Error (**data: Any)-
Expand source code
class Error(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) code: Annotated[ str, Field( description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', max_length=64, min_length=1, ), ] message: Annotated[str, Field(description='Human-readable error message')] field: Annotated[ str | None, Field( description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." ), ] = None suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None retry_after: Annotated[ float | None, Field( description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', ge=1.0, le=3600.0, ), ] = None issues: Annotated[ list[Issue] | None, Field( description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' ), ] = None details: Annotated[ dict[str, Any] | None, Field( description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: <array>` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' ), ] = None recovery: Annotated[ Recovery | None, Field( description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' ), ] = None source: Annotated[ Source | None, Field( description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' ), ] = None sdk_id: Annotated[ str | None, Field( description='Optional identifier for the SDK that augmented this error entry. Format: `<sdk_package_name>@<version>` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var code : strvar details : dict[str, typing.Any] | Nonevar field : str | Nonevar issues : list[adcp.types.generated_poc.core.error.Issue] | Nonevar message : strvar model_configvar recovery : adcp.types.generated_poc.core.error.Recovery | Nonevar retry_after : float | Nonevar sdk_id : str | Nonevar source : adcp.types.generated_poc.core.error.Source | Nonevar suggestion : str | None
Inherited members
class ErrorCode (*args, **kwds)-
Expand source code
class ErrorCode(StrEnum): INVALID_REQUEST = 'INVALID_REQUEST' AUTH_REQUIRED = 'AUTH_REQUIRED' AUTH_MISSING = 'AUTH_MISSING' AUTH_INVALID = 'AUTH_INVALID' AUTHORIZATION_REQUIRED = 'AUTHORIZATION_REQUIRED' RATE_LIMITED = 'RATE_LIMITED' SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE' CONFIGURATION_ERROR = 'CONFIGURATION_ERROR' POLICY_VIOLATION = 'POLICY_VIOLATION' PRODUCT_NOT_FOUND = 'PRODUCT_NOT_FOUND' PRODUCT_UNAVAILABLE = 'PRODUCT_UNAVAILABLE' PROPOSAL_EXPIRED = 'PROPOSAL_EXPIRED' BUDGET_TOO_LOW = 'BUDGET_TOO_LOW' CREATIVE_REJECTED = 'CREATIVE_REJECTED' CREATIVE_VALUE_NOT_ALLOWED = 'CREATIVE_VALUE_NOT_ALLOWED' UNSUPPORTED_FEATURE = 'UNSUPPORTED_FEATURE' UNPRICEABLE_OUTPUT = 'UNPRICEABLE_OUTPUT' UNSUPPORTED_GRANULARITY = 'UNSUPPORTED_GRANULARITY' UNSUPPORTED_PROVISIONING = 'UNSUPPORTED_PROVISIONING' AUDIENCE_TOO_SMALL = 'AUDIENCE_TOO_SMALL' ACCOUNT_NOT_FOUND = 'ACCOUNT_NOT_FOUND' ACCOUNT_SETUP_REQUIRED = 'ACCOUNT_SETUP_REQUIRED' ACCOUNT_AMBIGUOUS = 'ACCOUNT_AMBIGUOUS' ACCOUNT_PAYMENT_REQUIRED = 'ACCOUNT_PAYMENT_REQUIRED' ACCOUNT_SUSPENDED = 'ACCOUNT_SUSPENDED' COMPLIANCE_UNSATISFIED = 'COMPLIANCE_UNSATISFIED' GOVERNANCE_DENIED = 'GOVERNANCE_DENIED' BUDGET_EXHAUSTED = 'BUDGET_EXHAUSTED' BUDGET_EXCEEDED = 'BUDGET_EXCEEDED' BUDGET_CAP_REACHED = 'BUDGET_CAP_REACHED' CONFLICT = 'CONFLICT' IDEMPOTENCY_CONFLICT = 'IDEMPOTENCY_CONFLICT' IDEMPOTENCY_EXPIRED = 'IDEMPOTENCY_EXPIRED' IDEMPOTENCY_IN_FLIGHT = 'IDEMPOTENCY_IN_FLIGHT' CREATIVE_DEADLINE_EXCEEDED = 'CREATIVE_DEADLINE_EXCEEDED' CREATIVE_INACCESSIBLE = 'CREATIVE_INACCESSIBLE' INVALID_STATE = 'INVALID_STATE' MEDIA_BUY_NOT_FOUND = 'MEDIA_BUY_NOT_FOUND' NOT_CANCELLABLE = 'NOT_CANCELLABLE' PACKAGE_NOT_FOUND = 'PACKAGE_NOT_FOUND' CREATIVE_NOT_FOUND = 'CREATIVE_NOT_FOUND' SIGNAL_NOT_FOUND = 'SIGNAL_NOT_FOUND' SIGNAL_TARGETING_INCOMPATIBLE = 'SIGNAL_TARGETING_INCOMPATIBLE' SESSION_NOT_FOUND = 'SESSION_NOT_FOUND' PLAN_NOT_FOUND = 'PLAN_NOT_FOUND' REFERENCE_NOT_FOUND = 'REFERENCE_NOT_FOUND' SESSION_TERMINATED = 'SESSION_TERMINATED' VALIDATION_ERROR = 'VALIDATION_ERROR' PRODUCT_EXPIRED = 'PRODUCT_EXPIRED' PROPOSAL_NOT_COMMITTED = 'PROPOSAL_NOT_COMMITTED' PROPOSAL_NOT_FOUND = 'PROPOSAL_NOT_FOUND' MULTI_FINALIZE_UNSUPPORTED = 'MULTI_FINALIZE_UNSUPPORTED' IO_REQUIRED = 'IO_REQUIRED' TERMS_REJECTED = 'TERMS_REJECTED' REQUOTE_REQUIRED = 'REQUOTE_REQUIRED' VERSION_UNSUPPORTED = 'VERSION_UNSUPPORTED' CAMPAIGN_SUSPENDED = 'CAMPAIGN_SUSPENDED' GOVERNANCE_UNAVAILABLE = 'GOVERNANCE_UNAVAILABLE' PERMISSION_DENIED = 'PERMISSION_DENIED' SCOPE_INSUFFICIENT = 'SCOPE_INSUFFICIENT' READ_ONLY_SCOPE = 'READ_ONLY_SCOPE' FIELD_NOT_PERMITTED = 'FIELD_NOT_PERMITTED' PROVENANCE_REQUIRED = 'PROVENANCE_REQUIRED' PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING = 'PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING' PROVENANCE_DISCLOSURE_MISSING = 'PROVENANCE_DISCLOSURE_MISSING' PROVENANCE_EMBEDDED_MISSING = 'PROVENANCE_EMBEDDED_MISSING' PROVENANCE_VERIFIER_NOT_ACCEPTED = 'PROVENANCE_VERIFIER_NOT_ACCEPTED' PROVENANCE_CLAIM_CONTRADICTED = 'PROVENANCE_CLAIM_CONTRADICTED' EVALUATOR_AGENT_NOT_ACCEPTED = 'EVALUATOR_AGENT_NOT_ACCEPTED' BILLING_NOT_SUPPORTED = 'BILLING_NOT_SUPPORTED' BILLING_NOT_PERMITTED_FOR_AGENT = 'BILLING_NOT_PERMITTED_FOR_AGENT' BILLING_OUT_OF_BAND = 'BILLING_OUT_OF_BAND' PAYMENT_TERMS_NOT_SUPPORTED = 'PAYMENT_TERMS_NOT_SUPPORTED' BRAND_REQUIRED = 'BRAND_REQUIRED' AGENT_SUSPENDED = 'AGENT_SUSPENDED' AGENT_BLOCKED = 'AGENT_BLOCKED' CREDENTIAL_IN_ARGS = 'CREDENTIAL_IN_ARGS' ACTION_NOT_ALLOWED = 'ACTION_NOT_ALLOWED' PRIVATE_FIELD_IN_PUBLIC_PLACEMENT = 'PRIVATE_FIELD_IN_PUBLIC_PLACEMENT' FORMAT_PROJECTION_FAILED = 'FORMAT_PROJECTION_FAILED' FORMAT_DECLARATION_DIVERGENT = 'FORMAT_DECLARATION_DIVERGENT' FORMAT_DECLARATION_V1_AMBIGUOUS = 'FORMAT_DECLARATION_V1_AMBIGUOUS' FORMAT_OPTION_UNRESOLVED = 'FORMAT_OPTION_UNRESOLVED' FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE = 'FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE' FORMAT_NOT_SUPPORTED = 'FORMAT_NOT_SUPPORTED' PIXEL_TRACKER_LOSSY_DOWNGRADE = 'PIXEL_TRACKER_LOSSY_DOWNGRADE' PIXEL_TRACKER_UPGRADE_INFERRED = 'PIXEL_TRACKER_UPGRADE_INFERRED' STALE_RESPONSE = 'STALE_RESPONSE' FEED_FETCH_FAILED = 'FEED_FETCH_FAILED' INVALID_FEED_FORMAT = 'INVALID_FEED_FORMAT' ITEM_VALIDATION_FAILED = 'ITEM_VALIDATION_FAILED' CATALOG_LIMIT_EXCEEDED = 'CATALOG_LIMIT_EXCEEDED'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var ACCOUNT_AMBIGUOUSvar ACCOUNT_NOT_FOUNDvar ACCOUNT_PAYMENT_REQUIREDvar ACCOUNT_SETUP_REQUIREDvar ACCOUNT_SUSPENDEDvar ACTION_NOT_ALLOWEDvar AGENT_BLOCKEDvar AGENT_SUSPENDEDvar AUDIENCE_TOO_SMALLvar AUTHORIZATION_REQUIREDvar AUTH_INVALIDvar AUTH_MISSINGvar AUTH_REQUIREDvar BILLING_NOT_PERMITTED_FOR_AGENTvar BILLING_NOT_SUPPORTEDvar BILLING_OUT_OF_BANDvar BRAND_REQUIREDvar BUDGET_CAP_REACHEDvar BUDGET_EXCEEDEDvar BUDGET_EXHAUSTEDvar BUDGET_TOO_LOWvar CAMPAIGN_SUSPENDEDvar CATALOG_LIMIT_EXCEEDEDvar COMPLIANCE_UNSATISFIEDvar CONFIGURATION_ERRORvar CONFLICTvar CREATIVE_DEADLINE_EXCEEDEDvar CREATIVE_INACCESSIBLEvar CREATIVE_NOT_FOUNDvar CREATIVE_REJECTEDvar CREATIVE_VALUE_NOT_ALLOWEDvar CREDENTIAL_IN_ARGSvar EVALUATOR_AGENT_NOT_ACCEPTEDvar FEED_FETCH_FAILEDvar FIELD_NOT_PERMITTEDvar FORMAT_DECLARATION_DIVERGENTvar FORMAT_DECLARATION_V1_AMBIGUOUSvar FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZEvar FORMAT_NOT_SUPPORTEDvar FORMAT_OPTION_UNRESOLVEDvar FORMAT_PROJECTION_FAILEDvar GOVERNANCE_DENIEDvar GOVERNANCE_UNAVAILABLEvar IDEMPOTENCY_CONFLICTvar IDEMPOTENCY_EXPIREDvar IDEMPOTENCY_IN_FLIGHTvar INVALID_FEED_FORMATvar INVALID_REQUESTvar INVALID_STATEvar IO_REQUIREDvar ITEM_VALIDATION_FAILEDvar MEDIA_BUY_NOT_FOUNDvar MULTI_FINALIZE_UNSUPPORTEDvar NOT_CANCELLABLEvar PACKAGE_NOT_FOUNDvar PAYMENT_TERMS_NOT_SUPPORTEDvar PERMISSION_DENIEDvar PIXEL_TRACKER_LOSSY_DOWNGRADEvar PIXEL_TRACKER_UPGRADE_INFERREDvar PLAN_NOT_FOUNDvar POLICY_VIOLATIONvar PRIVATE_FIELD_IN_PUBLIC_PLACEMENTvar PRODUCT_EXPIREDvar PRODUCT_NOT_FOUNDvar PRODUCT_UNAVAILABLEvar PROPOSAL_EXPIREDvar PROPOSAL_NOT_COMMITTEDvar PROPOSAL_NOT_FOUNDvar PROVENANCE_CLAIM_CONTRADICTEDvar PROVENANCE_DIGITAL_SOURCE_TYPE_MISSINGvar PROVENANCE_DISCLOSURE_MISSINGvar PROVENANCE_EMBEDDED_MISSINGvar PROVENANCE_REQUIREDvar PROVENANCE_VERIFIER_NOT_ACCEPTEDvar RATE_LIMITEDvar READ_ONLY_SCOPEvar REFERENCE_NOT_FOUNDvar REQUOTE_REQUIREDvar SCOPE_INSUFFICIENTvar SERVICE_UNAVAILABLEvar SESSION_NOT_FOUNDvar SESSION_TERMINATEDvar SIGNAL_NOT_FOUNDvar SIGNAL_TARGETING_INCOMPATIBLEvar STALE_RESPONSEvar TERMS_REJECTEDvar UNPRICEABLE_OUTPUTvar UNSUPPORTED_FEATUREvar UNSUPPORTED_GRANULARITYvar UNSUPPORTED_PROVISIONINGvar VALIDATION_ERRORvar VERSION_UNSUPPORTED
class EventType (*args, **kwds)-
Expand source code
class EventType(StrEnum): page_view = 'page_view' view_content = 'view_content' select_content = 'select_content' select_item = 'select_item' search = 'search' share = 'share' add_to_cart = 'add_to_cart' remove_from_cart = 'remove_from_cart' viewed_cart = 'viewed_cart' add_to_wishlist = 'add_to_wishlist' initiate_checkout = 'initiate_checkout' add_payment_info = 'add_payment_info' purchase = 'purchase' refund = 'refund' lead = 'lead' qualify_lead = 'qualify_lead' close_convert_lead = 'close_convert_lead' disqualify_lead = 'disqualify_lead' complete_registration = 'complete_registration' subscribe = 'subscribe' follow = 'follow' content_view = 'content_view' watch_milestone = 'watch_milestone' start_trial = 'start_trial' app_install = 'app_install' app_launch = 'app_launch' contact = 'contact' schedule = 'schedule' donate = 'donate' submit_application = 'submit_application' custom = 'custom'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var add_payment_infovar add_to_cartvar add_to_wishlistvar app_installvar app_launchvar close_convert_leadvar complete_registrationvar contactvar content_viewvar customvar disqualify_leadvar donatevar followvar initiate_checkoutvar leadvar page_viewvar purchasevar qualify_leadvar refundvar remove_from_cartvar schedulevar searchvar select_contentvar select_itemvar start_trialvar submit_applicationvar subscribevar view_contentvar viewed_cartvar watch_milestone
class ExtensionObject (**data: Any)-
Expand source code
class ExtensionObject(AdCPBaseModel): model_config = ConfigDict( extra='allow', )Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class FeatureResolver (capabilities: GetAdcpCapabilitiesResponse)-
Expand source code
class FeatureResolver: """Resolves feature support from a GetAdcpCapabilitiesResponse. Supports multiple feature namespaces: - Protocol support: ``"media_buy"`` checks ``supported_protocols`` - Extension support: ``"ext:scope3"`` checks ``extensions_supported`` - Targeting: ``"targeting.geo_countries"`` checks ``media_buy.execution.targeting`` - Media buy features: ``"inline_creative_management"`` checks ``media_buy.features`` - Signals features: ``"catalog_signals"`` checks ``signals.features`` """ def __init__(self, capabilities: GetAdcpCapabilitiesResponse) -> None: self._caps = capabilities # Pre-compute the set of valid protocol names so supports() doesn't # need a runtime import on every call. from adcp.types.generated_poc.protocol.get_adcp_capabilities_response import ( SupportedProtocol, ) self._valid_protocols = {p.value for p in SupportedProtocol} self._declared_protocols = {p.value for p in capabilities.supported_protocols} @property def capabilities(self) -> GetAdcpCapabilitiesResponse: return self._caps def supports_v3(self) -> bool: """Check if the seller supports ADCP v3. Returns: True if major_versions includes 3. """ for v in self._caps.adcp.major_versions: if (v.root if hasattr(v, "root") else v) == 3: return True return False def supports(self, feature: str) -> bool: """Check if a feature is supported.""" caps = self._caps # Extension check: "ext:scope3" if feature.startswith("ext:"): ext_name = feature[4:] if caps.extensions_supported is None: return False return any(item.root == ext_name for item in caps.extensions_supported) # Targeting check: "targeting.geo_countries" if feature.startswith("targeting."): attr_name = feature[len("targeting.") :] if caps.media_buy is None or caps.media_buy.execution is None: return False targeting = caps.media_buy.execution.targeting if targeting is None: return False if attr_name not in type(targeting).model_fields: return False val = getattr(targeting, attr_name, None) # For bool fields, check truthiness. For object fields (like geo_metros), # presence means supported. return val is not None and val is not False # Protocol check: if the string is a known protocol name, resolve it # against supported_protocols and stop — don't fall through to features. if feature in self._declared_protocols: return True if feature in self._valid_protocols: return False # Media buy features check if caps.media_buy is not None and caps.media_buy.features is not None: if feature in type(caps.media_buy.features).model_fields: val = getattr(caps.media_buy.features, feature, None) if val is True: return True # Signals features check if caps.signals is not None and caps.signals.features is not None: if feature in type(caps.signals.features).model_fields: val = getattr(caps.signals.features, feature, None) if val is True: return True return False def require( self, *features: str, agent_id: str | None = None, agent_uri: str | None = None, ) -> None: """Assert that all listed features are supported. Args: *features: Feature identifiers to require. agent_id: Optional agent ID for error context. agent_uri: Optional agent URI for error context. Raises: ADCPFeatureUnsupportedError: If any features are not supported. """ unsupported = [f for f in features if not self.supports(f)] if not unsupported: return declared = self.get_declared_features() raise ADCPFeatureUnsupportedError( unsupported_features=unsupported, declared_features=declared, agent_id=agent_id, agent_uri=agent_uri, ) def get_declared_features(self) -> list[str]: """Collect all features the response declares as supported.""" caps = self._caps declared: list[str] = [] # Supported protocols for p in caps.supported_protocols: declared.append(p.value) # Media buy features if caps.media_buy is not None and caps.media_buy.features is not None: for field_name in type(caps.media_buy.features).model_fields: if getattr(caps.media_buy.features, field_name, None) is True: declared.append(field_name) # Signals features if caps.signals is not None and caps.signals.features is not None: for field_name in type(caps.signals.features).model_fields: if getattr(caps.signals.features, field_name, None) is True: declared.append(field_name) # Targeting features if caps.media_buy is not None and caps.media_buy.execution is not None: targeting = caps.media_buy.execution.targeting if targeting is not None: for field_name in type(targeting).model_fields: val = getattr(targeting, field_name, None) if val is not None and val is not False: declared.append(f"targeting.{field_name}") # Extensions if caps.extensions_supported is not None: for item in caps.extensions_supported: declared.append(f"ext:{item.root}") return declaredResolves feature support from a GetAdcpCapabilitiesResponse.
Supports multiple feature namespaces:
- Protocol support:
"media_buy"checkssupported_protocols - Extension support:
"ext:scope3"checksextensions_supported - Targeting:
"targeting.geo_countries"checksmedia_buy.execution.targeting - Media buy features:
"inline_creative_management"checksmedia_buy.features - Signals features:
"catalog_signals"checkssignals.features
Instance variables
prop capabilities : GetAdcpCapabilitiesResponse-
Expand source code
@property def capabilities(self) -> GetAdcpCapabilitiesResponse: return self._caps
Methods
def get_declared_features(self) ‑> list[str]-
Expand source code
def get_declared_features(self) -> list[str]: """Collect all features the response declares as supported.""" caps = self._caps declared: list[str] = [] # Supported protocols for p in caps.supported_protocols: declared.append(p.value) # Media buy features if caps.media_buy is not None and caps.media_buy.features is not None: for field_name in type(caps.media_buy.features).model_fields: if getattr(caps.media_buy.features, field_name, None) is True: declared.append(field_name) # Signals features if caps.signals is not None and caps.signals.features is not None: for field_name in type(caps.signals.features).model_fields: if getattr(caps.signals.features, field_name, None) is True: declared.append(field_name) # Targeting features if caps.media_buy is not None and caps.media_buy.execution is not None: targeting = caps.media_buy.execution.targeting if targeting is not None: for field_name in type(targeting).model_fields: val = getattr(targeting, field_name, None) if val is not None and val is not False: declared.append(f"targeting.{field_name}") # Extensions if caps.extensions_supported is not None: for item in caps.extensions_supported: declared.append(f"ext:{item.root}") return declaredCollect all features the response declares as supported.
def require(self, *features: str, agent_id: str | None = None, agent_uri: str | None = None) ‑> None-
Expand source code
def require( self, *features: str, agent_id: str | None = None, agent_uri: str | None = None, ) -> None: """Assert that all listed features are supported. Args: *features: Feature identifiers to require. agent_id: Optional agent ID for error context. agent_uri: Optional agent URI for error context. Raises: ADCPFeatureUnsupportedError: If any features are not supported. """ unsupported = [f for f in features if not self.supports(f)] if not unsupported: return declared = self.get_declared_features() raise ADCPFeatureUnsupportedError( unsupported_features=unsupported, declared_features=declared, agent_id=agent_id, agent_uri=agent_uri, )Assert that all listed features are supported.
Args
*features- Feature identifiers to require.
agent_id- Optional agent ID for error context.
agent_uri- Optional agent URI for error context.
Raises
ADCPFeatureUnsupportedError- If any features are not supported.
def supports(self, feature: str) ‑> bool-
Expand source code
def supports(self, feature: str) -> bool: """Check if a feature is supported.""" caps = self._caps # Extension check: "ext:scope3" if feature.startswith("ext:"): ext_name = feature[4:] if caps.extensions_supported is None: return False return any(item.root == ext_name for item in caps.extensions_supported) # Targeting check: "targeting.geo_countries" if feature.startswith("targeting."): attr_name = feature[len("targeting.") :] if caps.media_buy is None or caps.media_buy.execution is None: return False targeting = caps.media_buy.execution.targeting if targeting is None: return False if attr_name not in type(targeting).model_fields: return False val = getattr(targeting, attr_name, None) # For bool fields, check truthiness. For object fields (like geo_metros), # presence means supported. return val is not None and val is not False # Protocol check: if the string is a known protocol name, resolve it # against supported_protocols and stop — don't fall through to features. if feature in self._declared_protocols: return True if feature in self._valid_protocols: return False # Media buy features check if caps.media_buy is not None and caps.media_buy.features is not None: if feature in type(caps.media_buy.features).model_fields: val = getattr(caps.media_buy.features, feature, None) if val is True: return True # Signals features check if caps.signals is not None and caps.signals.features is not None: if feature in type(caps.signals.features).model_fields: val = getattr(caps.signals.features, feature, None) if val is True: return True return FalseCheck if a feature is supported.
def supports_v3(self) ‑> bool-
Expand source code
def supports_v3(self) -> bool: """Check if the seller supports ADCP v3. Returns: True if major_versions includes 3. """ for v in self._caps.adcp.major_versions: if (v.root if hasattr(v, "root") else v) == 3: return True return FalseCheck if the seller supports ADCP v3.
Returns
True if major_versions includes 3.
- Protocol support:
class FederatedAgentWithDetails (**data: Any)-
Expand source code
class FederatedAgentWithDetails(RegistryBaseModel): url: str name: str type: AgentType protocol: AgentProtocol | None = None description: str | None = None mcp_endpoint: str | None = None contact: AgentDetailedContact | None = None added_date: str | None = None member: Annotated[ AgentMember | None, Field( description="AAO member that owns this agent record. The registry contains only agents that members have explicitly enrolled on their member profile." ), ] = None health: AgentHealth | None = None stats: AgentStats | None = None capabilities: AgentCapabilities | None = None compliance: AgentCompliance | None = None publisher_domains: list[str] | None = None property_summary: PropertySummary | None = NoneBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var added_date : str | Nonevar capabilities : AgentCapabilities | Nonevar compliance : AgentCompliance | Nonevar contact : AgentDetailedContact | Nonevar description : str | Nonevar health : AgentHealth | Nonevar mcp_endpoint : str | Nonevar member : AgentMember | Nonevar model_configvar name : strvar property_summary : PropertySummary | Nonevar protocol : AgentProtocol | Nonevar publisher_domains : list[str] | Nonevar stats : AgentStats | Nonevar type : AgentTypevar url : str
class FederatedPublisher (**data: Any)-
Expand source code
class FederatedPublisher(RegistryBaseModel): domain: str member: AgentMember | None = None agent_count: int | None = None last_validated: str | None = None has_valid_adagents: bool | None = NoneBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var agent_count : int | Nonevar domain : strvar has_valid_adagents : bool | Nonevar last_validated : str | Nonevar member : AgentMember | Nonevar model_config
class FeedEvent (**data: Any)-
Expand source code
class FeedEvent(RegistryBaseModel): event_id: str event_type: str entity_type: str entity_id: str payload: dict[str, Any] actor: str created_at: strBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var actor : strvar created_at : strvar entity_id : strvar entity_type : strvar event_id : strvar event_type : strvar model_configvar payload : dict[str, typing.Any]
class FeedFormat (*args, **kwds)-
Expand source code
class FeedFormat(StrEnum): google_merchant_center = 'google_merchant_center' facebook_catalog = 'facebook_catalog' shopify = 'shopify' linkedin_jobs = 'linkedin_jobs' tiktok_shop = 'tiktok_shop' pinterest_catalog = 'pinterest_catalog' openai_product_feed = 'openai_product_feed' custom = 'custom'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var customvar facebook_catalogvar google_merchant_centervar linkedin_jobsvar openai_product_feedvar pinterest_catalogvar shopifyvar tiktok_shop
class FeedMirror (client: FeedMirrorClient,
*,
account: AccountReference | None = None,
on_event: EventHandler | None = None,
state_store: FeedStateStore | None = None,
page_limit: int = 100)-
Expand source code
class FeedMirror: """In-memory mirror of an agent's wholesale product and signal feeds. Args: client: An :class:`adcp.ADCPClient` (or any object satisfying :class:`FeedMirrorClient`) bound to the target agent. account: Optional account scope for wholesale reads. Wholesale-feed webhooks are account-anchored, so pass the same account used to register ``notification_configs[]`` so repair reads reconcile the correct public or account overlay. on_event: Optional callback invoked after each webhook event mutates the mirror. Receives the applied :class:`WholesaleFeedEvent`. state_store: Optional :class:`FeedStateStore` for persisting cached feed-version tokens across process restarts. page_limit: Page size for wholesale enumeration (max 100 per spec). """ def __init__( self, client: FeedMirrorClient, *, account: AccountReference | None = None, on_event: EventHandler | None = None, state_store: FeedStateStore | None = None, page_limit: int = _DEFAULT_PAGE_LIMIT, ) -> None: self._client = client self._account = account self._on_event = on_event self._state_store = state_store self._page_limit = page_limit self._products: dict[str, Product] = {} self._signals: dict[str, Signal] = {} self._product_state = FeedState() self._signal_state = FeedState() # ------------------------------------------------------------------ # Read-only views # ------------------------------------------------------------------ @property def products(self) -> dict[str, Product]: """Live view of the product index keyed by ``product_id``.""" return self._products @property def signals(self) -> dict[str, Signal]: """Live view of the signal index keyed by ``signal_agent_segment_id``.""" return self._signals @property def product_state(self) -> FeedState: """Cached version tokens for the wholesale product feed.""" return self._product_state @property def signal_state(self) -> FeedState: """Cached version tokens for the wholesale signal feed.""" return self._signal_state def get_product(self, product_id: str) -> Product | None: """Return a mirrored product by id, or ``None``.""" return self._products.get(product_id) def get_signal(self, signal_agent_segment_id: str) -> Signal | None: """Return a mirrored signal by id, or ``None``.""" return self._signals.get(signal_agent_segment_id) # ------------------------------------------------------------------ # Bootstrap / refresh # ------------------------------------------------------------------ async def bootstrap(self, entities: FeedEntity | Literal["all"] = "all") -> RefreshResult: """Initial full load of the wholesale feed(s). Restores any persisted :class:`FeedState` from the configured ``state_store`` first, so a bootstrap after a restart presents the cached version on the conditional read and short-circuits when the seller has nothing new. Args: entities: ``"all"`` (default), ``"product"``, or ``"signal"`` to bootstrap a single feed. """ if self._state_store is not None: await self._restore_state(entities) return await self._fetch(entities) async def refresh(self, entities: FeedEntity | Literal["all"] = "all") -> RefreshResult: """Conditional re-read of the wholesale feed(s). Presents the cached ``if_wholesale_feed_version`` (and ``if_pricing_version`` when known). When the seller returns ``unchanged: true`` the replica is left untouched and the result's ``*_unchanged`` flag is set. """ return await self._fetch(entities) async def _fetch(self, entities: FeedEntity | Literal["all"]) -> RefreshResult: result = RefreshResult() do_products = entities in ("all", "product") do_signals = entities in ("all", "signal") if do_products: meta = await self._fetch_products() self._commit("product", meta) result.products_unchanged = meta.unchanged else: # Treat a feed we didn't fetch as unchanged so RefreshResult.unchanged # reflects only the feeds the caller asked about. result.products_unchanged = True if do_signals: meta = await self._fetch_signals() self._commit("signal", meta) result.signals_unchanged = meta.unchanged else: result.signals_unchanged = True result.product_count = len(self._products) result.signal_count = len(self._signals) return result async def _fetch_products(self) -> _FeedMetadata: state = self._product_state meta = _FeedMetadata( wholesale_feed_version=state.wholesale_feed_version, pricing_version=state.pricing_version, cache_scope=state.cache_scope, ) cursor: str | None = None first_page = True while True: request = self._build_products_request(cursor, first_page, state) task = await self._client.get_products(request) body = self._require_body(task, "get_products") if body.unchanged: self._merge_metadata(meta, body) meta.unchanged = True return meta for product in body.products or []: meta.items[product.product_id] = product self._merge_metadata(meta, body) cursor = self._next_cursor(body) first_page = False if cursor is None: return meta async def _fetch_signals(self) -> _FeedMetadata: state = self._signal_state meta = _FeedMetadata( wholesale_feed_version=state.wholesale_feed_version, pricing_version=state.pricing_version, cache_scope=state.cache_scope, ) cursor: str | None = None first_page = True while True: request = self._build_signals_request(cursor, first_page, state) task = await self._client.get_signals(request) body = self._require_body(task, "get_signals") if body.unchanged: self._merge_metadata(meta, body) meta.unchanged = True return meta for signal in body.signals or []: meta.items[signal.signal_agent_segment_id] = signal self._merge_metadata(meta, body) cursor = self._next_cursor(body) first_page = False if cursor is None: return meta def _build_products_request( self, cursor: str | None, first_page: bool, state: FeedState ) -> GetProductsRequest: kwargs: dict[str, Any] = { "buying_mode": "wholesale", "pagination": PaginationRequest(max_results=self._page_limit, cursor=cursor), } if self._account is not None: kwargs["account"] = self._account # Conditional fetch on the first page only — pagination.cursor is not # part of the version scoping tuple, so a mid-walk page must not carry # the if_* tokens. if first_page and state.wholesale_feed_version is not None: kwargs["if_wholesale_feed_version"] = state.wholesale_feed_version if state.pricing_version is not None: kwargs["if_pricing_version"] = state.pricing_version return GetProductsRequest(**kwargs) def _build_signals_request( self, cursor: str | None, first_page: bool, state: FeedState ) -> GetSignalsRequest: kwargs: dict[str, Any] = { "discovery_mode": "wholesale", "pagination": PaginationRequest(max_results=self._page_limit, cursor=cursor), } if self._account is not None: kwargs["account"] = self._account if first_page and state.wholesale_feed_version is not None: kwargs["if_wholesale_feed_version"] = state.wholesale_feed_version if state.pricing_version is not None: kwargs["if_pricing_version"] = state.pricing_version return GetSignalsRequest(**kwargs) @staticmethod def _require_body(task: TaskResult[Any], operation: str) -> Any: if not task.success or task.data is None: raise FeedMirrorError( f"{operation} did not return a successful wholesale response: " f"{task.error or task.message or task.status}" ) return task.data @staticmethod def _merge_metadata(meta: _FeedMetadata, body: Any) -> None: if body.wholesale_feed_version is not None: meta.wholesale_feed_version = body.wholesale_feed_version if body.pricing_version is not None: meta.pricing_version = body.pricing_version if body.cache_scope is not None: meta.cache_scope = _scope_str(body.cache_scope) @staticmethod def _next_cursor(body: Any) -> str | None: pagination = body.pagination if pagination is not None and pagination.has_more: return cast("str | None", pagination.cursor) return None def _commit(self, entity: FeedEntity, meta: _FeedMetadata) -> None: state = self._product_state if entity == "product" else self._signal_state state.wholesale_feed_version = meta.wholesale_feed_version state.pricing_version = meta.pricing_version state.cache_scope = meta.cache_scope if not meta.unchanged: # Atomic swap — only replace the live index on a fresh fetch so an # unchanged short-circuit never wipes the replica. if entity == "product": self._products = meta.items else: self._signals = meta.items async def _restore_state(self, entities: FeedEntity | Literal["all"]) -> None: assert self._state_store is not None if entities in ("all", "product"): restored = await self._state_store.load("product") if restored is not None: self._product_state = restored if entities in ("all", "signal"): restored = await self._state_store.load("signal") if restored is not None: self._signal_state = restored async def _persist_state(self, entity: FeedEntity) -> None: if self._state_store is None: return state = self._product_state if entity == "product" else self._signal_state await self._state_store.save(entity, state) # ------------------------------------------------------------------ # Incremental webhook application # ------------------------------------------------------------------ async def apply_webhook(self, webhook: WholesaleFeedWebhook) -> RefreshResult | None: """Apply one wholesale-feed webhook to the local mirror. Call this from your HTTP webhook receiver after signature/auth validation. ``product.*`` / ``signal.*`` events mutate the index in place (events are denormalized — no follow-up read needed) and update the cached feed version for the affected feed. A ``wholesale_feed.bulk_change`` event re-bootstraps only the feed named by ``affected_entity_type`` and returns the :class:`RefreshResult`. Raises: FeedMirrorError: When the webhook envelope is internally inconsistent (``notification_type`` / ``notification_id`` do not match the embedded event). """ event = webhook.event if webhook.notification_type != event.event_type: raise FeedMirrorError("webhook notification_type does not match event.event_type") if str(webhook.notification_id) != str(event.event_id): raise FeedMirrorError("webhook notification_id does not match event.event_id") if event.event_type == "wholesale_feed.bulk_change": affected = self._bulk_change_entity(event) result = await self.refresh(affected) self._dispatch(event) return result self._apply_event(event) self._remember_webhook_version(webhook) self._dispatch(event) await self._persist_state(self._event_entity(event)) return None @staticmethod def _bulk_change_entity(event: WholesaleFeedEvent) -> FeedEntity: affected: Any = getattr(event.payload, "affected_entity_type", None) value = affected.value if hasattr(affected, "value") else affected if value == "product": return "product" if value == "signal": return "signal" raise FeedMirrorError( "wholesale_feed.bulk_change payload missing required affected_entity_type" ) @staticmethod def _event_entity(event: WholesaleFeedEvent) -> FeedEntity: return "product" if str(event.event_type).startswith("product.") else "signal" def _apply_event(self, event: WholesaleFeedEvent) -> None: event_type = str(event.event_type) payload = event.payload if event_type in ("product.created", "product.updated"): product = getattr(payload, "product", None) if product is not None: self._products[product.product_id] = product elif event_type == "product.priced": existing = self._products.get(payload.product_id) if existing is not None: self._products[payload.product_id] = existing.model_copy( update={"pricing_options": payload.pricing_options} ) elif event_type == "product.removed": self._products.pop(payload.product_id, None) elif event_type in ("signal.created", "signal.updated"): signal = getattr(payload, "signal", None) if signal is not None: self._signals[signal.signal_agent_segment_id] = signal elif event_type == "signal.priced": existing_signal = self._signals.get(payload.signal_agent_segment_id) if existing_signal is not None: self._signals[payload.signal_agent_segment_id] = existing_signal.model_copy( update={"pricing_options": payload.pricing_options} ) elif event_type == "signal.removed": self._signals.pop(payload.signal_agent_segment_id, None) def _remember_webhook_version(self, webhook: WholesaleFeedWebhook) -> None: entity = self._event_entity(webhook.event) state = self._product_state if entity == "product" else self._signal_state state.wholesale_feed_version = webhook.wholesale_feed_version state.cache_scope = _scope_str(webhook.cache_scope) def _dispatch(self, event: WholesaleFeedEvent) -> None: if self._on_event is None: return try: self._on_event(event) except Exception: logger.exception("FeedMirror on_event handler raised")In-memory mirror of an agent's wholesale product and signal feeds.
Args
client- An :class:
ADCPClient(or any object satisfying :class:FeedMirrorClient) bound to the target agent. account- Optional account scope for wholesale reads. Wholesale-feed
webhooks are account-anchored, so pass the same account used to
register
notification_configs[]so repair reads reconcile the correct public or account overlay. on_event- Optional callback invoked after each webhook event mutates
the mirror. Receives the applied :class:
WholesaleFeedEvent. state_store- Optional :class:
FeedStateStorefor persisting cached feed-version tokens across process restarts. page_limit- Page size for wholesale enumeration (max 100 per spec).
Instance variables
prop product_state : FeedState-
Expand source code
@property def product_state(self) -> FeedState: """Cached version tokens for the wholesale product feed.""" return self._product_stateCached version tokens for the wholesale product feed.
prop products : dict[str, Product]-
Expand source code
@property def products(self) -> dict[str, Product]: """Live view of the product index keyed by ``product_id``.""" return self._productsLive view of the product index keyed by
product_id. prop signal_state : FeedState-
Expand source code
@property def signal_state(self) -> FeedState: """Cached version tokens for the wholesale signal feed.""" return self._signal_stateCached version tokens for the wholesale signal feed.
prop signals : dict[str, Signal]-
Expand source code
@property def signals(self) -> dict[str, Signal]: """Live view of the signal index keyed by ``signal_agent_segment_id``.""" return self._signalsLive view of the signal index keyed by
signal_agent_segment_id.
Methods
async def apply_webhook(self,
webhook: WholesaleFeedWebhook) ‑> RefreshResult | None-
Expand source code
async def apply_webhook(self, webhook: WholesaleFeedWebhook) -> RefreshResult | None: """Apply one wholesale-feed webhook to the local mirror. Call this from your HTTP webhook receiver after signature/auth validation. ``product.*`` / ``signal.*`` events mutate the index in place (events are denormalized — no follow-up read needed) and update the cached feed version for the affected feed. A ``wholesale_feed.bulk_change`` event re-bootstraps only the feed named by ``affected_entity_type`` and returns the :class:`RefreshResult`. Raises: FeedMirrorError: When the webhook envelope is internally inconsistent (``notification_type`` / ``notification_id`` do not match the embedded event). """ event = webhook.event if webhook.notification_type != event.event_type: raise FeedMirrorError("webhook notification_type does not match event.event_type") if str(webhook.notification_id) != str(event.event_id): raise FeedMirrorError("webhook notification_id does not match event.event_id") if event.event_type == "wholesale_feed.bulk_change": affected = self._bulk_change_entity(event) result = await self.refresh(affected) self._dispatch(event) return result self._apply_event(event) self._remember_webhook_version(webhook) self._dispatch(event) await self._persist_state(self._event_entity(event)) return NoneApply one wholesale-feed webhook to the local mirror.
Call this from your HTTP webhook receiver after signature/auth validation.
product.*/signal.*events mutate the index in place (events are denormalized — no follow-up read needed) and update the cached feed version for the affected feed. Awholesale_feed.bulk_changeevent re-bootstraps only the feed named byaffected_entity_typeand returns the :class:RefreshResult.Raises
FeedMirrorError- When the webhook envelope is internally
inconsistent (
notification_type/notification_iddo not match the embedded event).
async def bootstrap(self, entities: "FeedEntity | Literal['all']" = 'all') ‑> RefreshResult-
Expand source code
async def bootstrap(self, entities: FeedEntity | Literal["all"] = "all") -> RefreshResult: """Initial full load of the wholesale feed(s). Restores any persisted :class:`FeedState` from the configured ``state_store`` first, so a bootstrap after a restart presents the cached version on the conditional read and short-circuits when the seller has nothing new. Args: entities: ``"all"`` (default), ``"product"``, or ``"signal"`` to bootstrap a single feed. """ if self._state_store is not None: await self._restore_state(entities) return await self._fetch(entities)Initial full load of the wholesale feed(s).
Restores any persisted :class:
FeedStatefrom the configuredstate_storefirst, so a bootstrap after a restart presents the cached version on the conditional read and short-circuits when the seller has nothing new.Args
entities"all"(default),"product", or"signal"to bootstrap a single feed.
def get_product(self, product_id: str) ‑> adcp.types.generated_poc.core.product.Product | None-
Expand source code
def get_product(self, product_id: str) -> Product | None: """Return a mirrored product by id, or ``None``.""" return self._products.get(product_id)Return a mirrored product by id, or
None. def get_signal(self, signal_agent_segment_id: str) ‑> adcp.types.generated_poc.core.wholesale_feed_event.Signal | None-
Expand source code
def get_signal(self, signal_agent_segment_id: str) -> Signal | None: """Return a mirrored signal by id, or ``None``.""" return self._signals.get(signal_agent_segment_id)Return a mirrored signal by id, or
None. async def refresh(self, entities: "FeedEntity | Literal['all']" = 'all') ‑> RefreshResult-
Expand source code
async def refresh(self, entities: FeedEntity | Literal["all"] = "all") -> RefreshResult: """Conditional re-read of the wholesale feed(s). Presents the cached ``if_wholesale_feed_version`` (and ``if_pricing_version`` when known). When the seller returns ``unchanged: true`` the replica is left untouched and the result's ``*_unchanged`` flag is set. """ return await self._fetch(entities)Conditional re-read of the wholesale feed(s).
Presents the cached
if_wholesale_feed_version(andif_pricing_versionwhen known). When the seller returnsunchanged: truethe replica is left untouched and the result's*_unchangedflag is set.
class FeedMirrorClient (*args, **kwargs)-
Expand source code
@runtime_checkable class FeedMirrorClient(Protocol): """The subset of :class:`adcp.ADCPClient` a :class:`FeedMirror` calls. Declared as a Protocol so tests can inject a minimal stub and so the mirror does not import the concrete client (avoiding a cycle). """ async def get_products( self, request: GetProductsRequest ) -> TaskResult[GetProductsResponse]: ... async def get_signals(self, request: GetSignalsRequest) -> TaskResult[GetSignalsResponse]: ...The subset of :class:
ADCPClienta :class:FeedMirrorcalls.Declared as a Protocol so tests can inject a minimal stub and so the mirror does not import the concrete client (avoiding a cycle).
Ancestors
- typing.Protocol
- typing.Generic
Methods
async def get_products(self,
request: GetProductsRequest) ‑> TaskResult[GetProductsResponse]-
Expand source code
async def get_products( self, request: GetProductsRequest ) -> TaskResult[GetProductsResponse]: ... async def get_signals(self,
request: GetSignalsRequest) ‑> TaskResult[GetSignalsResponse]-
Expand source code
async def get_signals(self, request: GetSignalsRequest) -> TaskResult[GetSignalsResponse]: ...
class FeedMirrorError (*args, **kwargs)-
Expand source code
class FeedMirrorError(Exception): """Raised when a wholesale-feed read fails or a webhook is inconsistent."""Raised when a wholesale-feed read fails or a webhook is inconsistent.
Ancestors
- builtins.Exception
- builtins.BaseException
class FeedPage (**data: Any)-
Expand source code
class FeedPage(RegistryBaseModel): events: list[FeedEvent] cursor: str | None has_more: boolBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var cursor : str | Nonevar events : list[FeedEvent]var has_more : boolvar model_config
class FeedState (wholesale_feed_version: str | None = None,
pricing_version: str | None = None,
cache_scope: "Literal['public', 'account']" = 'public')-
Expand source code
@dataclass class FeedState: """Cached version tokens for one wholesale feed. The tokens are opaque per the spec — the mirror never inspects or orders them, it only echoes the cached ``wholesale_feed_version`` (and ``pricing_version`` when present) back on the next conditional read. """ wholesale_feed_version: str | None = None pricing_version: str | None = None cache_scope: Literal["public", "account"] = "public"Cached version tokens for one wholesale feed.
The tokens are opaque per the spec — the mirror never inspects or orders them, it only echoes the cached
wholesale_feed_version(andpricing_versionwhen present) back on the next conditional read.Instance variables
var cache_scope : Literal['public', 'account']var pricing_version : str | Nonevar wholesale_feed_version : str | None
class FeedStateStore (*args, **kwargs)-
Expand source code
@runtime_checkable class FeedStateStore(Protocol): """Optional persistence hook for cached feed-version state. Lets adopters survive a process restart without re-bootstrapping from scratch: persist :class:`FeedState` per feed on save, restore on load. All methods are async to allow database-backed implementations. """ async def load(self, entity: FeedEntity) -> FeedState | None: """Return the persisted state for a feed, or ``None`` if absent.""" ... async def save(self, entity: FeedEntity, state: FeedState) -> None: """Persist the state for a feed.""" ...Optional persistence hook for cached feed-version state.
Lets adopters survive a process restart without re-bootstrapping from scratch: persist :class:
FeedStateper feed on save, restore on load. All methods are async to allow database-backed implementations.Ancestors
- typing.Protocol
- typing.Generic
Methods
async def load(self, entity: FeedEntity) ‑> FeedState | None-
Expand source code
async def load(self, entity: FeedEntity) -> FeedState | None: """Return the persisted state for a feed, or ``None`` if absent.""" ...Return the persisted state for a feed, or
Noneif absent. async def save(self,
entity: FeedEntity,
state: FeedState) ‑> None-
Expand source code
async def save(self, entity: FeedEntity, state: FeedState) -> None: """Persist the state for a feed.""" ...Persist the state for a feed.
class FileCursorStore (path: str | Path = '.adcp-sync-cursor.json')-
Expand source code
class FileCursorStore: """Default cursor store using a local JSON file. Args: path: Path to the cursor file. Defaults to .adcp-sync-cursor.json """ def __init__(self, path: str | Path = ".adcp-sync-cursor.json") -> None: self._path = Path(path) async def load(self) -> str | None: try: data = json.loads(self._path.read_text()) return data.get("cursor") # type: ignore[no-any-return] except (FileNotFoundError, json.JSONDecodeError, KeyError): return None async def save(self, cursor: str) -> None: temp = self._path.with_suffix(".tmp") temp.write_text(json.dumps({"cursor": cursor})) temp.replace(self._path) # Atomic renameDefault cursor store using a local JSON file.
Args
path- Path to the cursor file. Defaults to .adcp-sync-cursor.json
Methods
async def load(self) ‑> str | None-
Expand source code
async def load(self) -> str | None: try: data = json.loads(self._path.read_text()) return data.get("cursor") # type: ignore[no-any-return] except (FileNotFoundError, json.JSONDecodeError, KeyError): return None async def save(self, cursor: str) ‑> None-
Expand source code
async def save(self, cursor: str) -> None: temp = self._path.with_suffix(".tmp") temp.write_text(json.dumps({"cursor": cursor})) temp.replace(self._path) # Atomic rename
class FlatRatePricingOption (**data: Any)-
Expand source code
class FlatRatePricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[ Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') ] = 'flat_rate' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None parameters: Annotated[ Parameters | None, Field( description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', title='DoohParameters', ), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar min_spend_per_package : float | Nonevar model_configvar parameters : adcp.types.generated_poc.pricing_options.flat_rate_option.Parameters | Nonevar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['flat_rate']var pricing_option_id : str
Inherited members
class Format (**data: Any)-
Expand source code
class Format(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) format_id: Annotated[ format_id_1.FormatReferenceStructuredObject, Field( description="This format's own identifier — a structured object {agent_url, id}, not a string. See /schemas/core/format-id.json for the full shape." ), ] name: Annotated[str, Field(description='Human-readable format name')] description: Annotated[ str | None, Field( description='Plain text explanation of what this format does and what assets it requires' ), ] = None example_url: Annotated[ AnyUrl | None, Field( description='Optional URL to showcase page with examples and interactive demos of this format' ), ] = None accepts_parameters: Annotated[ list[format_id_parameter.FormatIdParameter] | None, Field( description='List of parameters this format accepts in format_id. Template formats define which parameters (dimensions, duration, etc.) can be specified when instantiating the format. Empty or omitted means this is a concrete format with fixed parameters.' ), ] = None renders: Annotated[ list[Renders | 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 assets: Annotated[ list[ Assets | Assets25 | Assets26 | Assets27 | Assets28 | Assets29 | Assets30 | Assets31 | Assets32 | Assets33 | Assets34 | Assets35 | Assets36 | Assets37 | Assets38 | Assets39 ] | None, Field( description="Array of all assets supported for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Use the 'required' boolean on each asset to indicate whether it's mandatory." ), ] = None delivery: Annotated[ dict[str, Any] | None, Field(description='Delivery method specifications (e.g., hosted, VAST, third-party tags)'), ] = None supported_macros: Annotated[ list[universal_macro.UniversalMacro | str] | None, Field( description='List of universal macros supported by this format (e.g., MEDIA_BUY_ID, CACHEBUSTER, DEVICE_ID). Used for validation and developer tooling. See docs/creative/universal-macros.mdx for full documentation.' ), ] = None input_format_ids: Annotated[ list[format_id_1.FormatReferenceStructuredObject] | None, Field( deprecated=True, description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `input_format_ids`/`output_format_ids`, so build capability is a property of the transformer (the unit you select and that carries pricing), not a relationship hung on a format. Discover build capability via `list_transformers` (optionally filtered by `input_format_ids`/`output_format_ids`).\n\nMigration: sellers that expressed transform capability by hanging `input_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead. Buyers SHOULD discover build capability via `list_transformers` rather than filtering formats.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format accepts as input creative manifests; when present, indicates this format can take existing creatives in these formats as input. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', ), ] = None output_format_ids: Annotated[ list[format_id_1.FormatReferenceStructuredObject] | None, Field( deprecated=True, description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `output_format_ids`, so what a builder can produce is a property of the transformer, not a relationship hung on a format. Discover via `list_transformers`.\n\nMigration: sellers that expressed multi-output build capability (e.g. a multi-publisher template) by hanging `output_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format can produce as output; when present, indicates this format can build creatives in these output formats. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', ), ] = None format_card: Annotated[ FormatCard | None, Field( description='Optional standard visual card (300x400px) for displaying this format in user interfaces. Can be rendered via preview_creative or pre-generated.' ), ] = None accessibility: Annotated[ Accessibility | None, Field( description='Accessibility posture of this format. Declares the WCAG conformance level that creatives produced by this format will meet.' ), ] = None supported_disclosure_positions: Annotated[ list[disclosure_position.DisclosurePosition] | None, Field( description='Disclosure positions this format can render. Buyers use this to determine whether a format can satisfy their compliance requirements before submitting a creative. When omitted, the format makes no disclosure rendering guarantees — creative agents SHOULD treat this as incompatible with briefs that require specific disclosure positions. Values correspond to positions on creative-brief.json required_disclosures.', min_length=1, ), ] = None disclosure_capabilities: Annotated[ list[DisclosureCapability] | None, Field( description='Structured disclosure capabilities per position with persistence modes. Declares which persistence behaviors each disclosure position supports, enabling persistence-aware matching against provenance render guidance and brief requirements. When present, supersedes supported_disclosure_positions for persistence-aware queries. The flat supported_disclosure_positions field is retained for backward compatibility. Each position MUST appear at most once; validators and agents SHOULD reject duplicates.', min_length=1, ), ] = None format_card_detailed: Annotated[ FormatCardDetailed | None, Field( description='Optional detailed card with carousel and full specifications. Provides rich format documentation similar to ad spec pages.' ), ] = None reported_metrics: Annotated[ list[available_metric.AvailableMetric] | None, Field( description='Metrics this format can produce in delivery reporting. Buyers receive the intersection of format reported_metrics and product available_metrics. If omitted, the format defers entirely to product-level metric declarations.', min_length=1, ), ] = None pricing_options: Annotated[ list[vendor_pricing_option.VendorPricingOption] | None, Field( deprecated=True, description='**DEPRECATED in 3.1. Removed at 4.0.** Use `transformer.pricing_options` (via `list_transformers`) instead — pricing belongs on the transformer (the unit selected and billed), exactly as it belongs on a media-buy product. Once formats only describe output shape, format-level pricing is vestigial.\n\nMigration: transformation/generation agents that charged via `format.pricing_options` SHOULD move the same `vendor-pricing-option` entries onto the corresponding transformer. The applied option is echoed per-leaf on the build_creative response and reconciled via report_usage, unchanged.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* pricing options for this format, used by transformation/generation agents that charge per format adapted, per image generated, or per unit of work; present when the request included include_pricing=true and account. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', min_length=1, ), ] = None canonical: Annotated[ canonical_projection_ref.CanonicalProjectionReference | None, Field( description='Optional v2 canonical-projection annotation. Always an object — bare-string shorthand (`canonical: "image"`) is not supported; the minimal form is `canonical: { "kind": "image" }`. Carries `kind` (which canonical the v1 format projects to) plus optional `asset_source` and `slots_override` for cases where the v1 format\'s shape doesn\'t follow the canonical\'s defaults (e.g., generative entries whose input is `generation_prompt: text` instead of `image_main: image`).\n\nWhen set, SDKs use this annotation as the authoritative v1 → v2 mapping for this format, bypassing the [v1 canonical mapping registry](/schemas/registries/v1-canonical-mapping.json) lookup. Combined with the slot-level `asset_group_id` declarations on each `assets[i]` entry, a v1 format declaration with `canonical` set is fully self-describing for v1↔v2 translation.\n\nResolution order for SDK projection from v1 wire shape to v2 (per RFC #3305 amendment #3767):\n1. If this `canonical` field is set, use it (seller-declared, highest priority). Apply `asset_source` and `slots_override` from the projection ref when present; otherwise inherit the canonical\'s defaults.\n2. Else, look up `format_id` in the canonical mapping registry\'s `format_id_glob` entries.\n3. Else, attempt structural match against the registry\'s `structural` entries (asset types, slot shape, vast_versions, etc.).\n4. Else, fail closed: SDK MUST NOT emit `format_options` for products carrying this format. Surface `FORMAT_PROJECTION_FAILED` on the response `errors[]` suggesting the seller add an explicit `canonical` annotation or file a registry entry.\n\nWhen `canonical.kind` is `custom`, the seller MUST also declare `canonical_format_shape` and `canonical_format_schema` (parallel to ProductFormatDeclaration\'s `format_shape` and `format_schema`) so buyer SDKs can fetch the seller\'s custom format schema.\n\nSee `canonical-projection-ref.json` for full projection semantics and examples (default-slot case, generative case, brief-driven case).' ), ] = None canonical_parameters: Annotated[ product_format_declaration.ProductFormatDeclaration | None, Field( deprecated=True, description="**DEPRECATED in 3.1. Removed at 4.0.** Use `v1_format_ref` on the v2 `ProductFormatDeclaration` instead — the seller authors a v2 declaration (in `Product.format_options` or `creative.supported_formats`) and links it back to this v1 format via `v1_format_ref: { agent_url, id }`. The directional link from v2 → v1 is the same fact as `canonical_parameters` without the parallel-shape drift surface (v1 file and `canonical_parameters` were two declarations of the same thing; hand-authored, drifting silently).\n\nMigration: every seller currently authoring `canonical_parameters` SHOULD migrate to authoring a v2 declaration on the corresponding product (or capability) with `v1_format_ref` pointing back at this v1 format. v1 files become pure v1 again — no v2-shape mirroring.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* When `canonical` is set, this field carries the full ProductFormatDeclaration that the SDK projects this v1 format into. The `format_kind` MUST equal the `canonical` field value (validators enforce). When set, this is the authoritative source for SDK v1→v2 projection — the registry's structural-match parameter inference is bypassed. SDKs reading 3.1 catalogs MUST continue to honor `canonical_parameters` when present; 4.0+ SDKs MAY reject the field. New code SHOULD NOT emit this field.\n\n**Drift contract (still normative while supported).** Hand-authored `canonical_parameters` MUST satisfy the *narrows* relation against this v1 format's `requirements` and `assets[*]` shape (see canonical-formats.mdx 'Narrows — formal definition'). SDKs that read this v1 file SHOULD lint-time check the equivalence at build/load and emit `FORMAT_PROJECTION_FAILED` if the two disagree.", ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var accepts_parameters : list[adcp.types.generated_poc.enums.format_id_parameter.FormatIdParameter] | Nonevar accessibility : adcp.types.generated_poc.core.format.Accessibility | Nonevar assets : list[typing.Union[adcp.types.generated_poc.core.format.Assets, adcp.types.generated_poc.core.format.Assets25, adcp.types.generated_poc.core.format.Assets26, adcp.types.generated_poc.core.format.Assets27, adcp.types.generated_poc.core.format.Assets28, adcp.types.generated_poc.core.format.Assets29, adcp.types.generated_poc.core.format.Assets30, adcp.types.generated_poc.core.format.Assets31, adcp.types.generated_poc.core.format.Assets33, adcp.types.generated_poc.core.format.Assets34, adcp.types.generated_poc.core.format.Assets35, adcp.types.generated_poc.core.format.Assets36, adcp.types.generated_poc.core.format.Assets37, adcp.types.generated_poc.core.format.Assets38, adcp.types.generated_poc.core.format.Assets39, UnknownFormatAsset]] | Nonevar canonical : adcp.types.generated_poc.core.canonical_projection_ref.CanonicalProjectionReference | Nonevar delivery : dict[str, typing.Any] | Nonevar description : str | Nonevar disclosure_capabilities : list[adcp.types.generated_poc.core.format.DisclosureCapability] | Nonevar example_url : pydantic.networks.AnyUrl | Nonevar format_card : adcp.types.generated_poc.core.format.FormatCard | Nonevar format_card_detailed : adcp.types.generated_poc.core.format.FormatCardDetailed | Nonevar format_id : adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObjectvar model_configvar name : strvar renders : list[adcp.types.generated_poc.core.format.Renders | adcp.types.generated_poc.core.format.Renders1] | Nonevar reported_metrics : list[adcp.types.generated_poc.enums.available_metric.AvailableMetric] | Nonevar supported_disclosure_positions : list[adcp.types.generated_poc.enums.disclosure_position.DisclosurePosition] | Nonevar supported_macros : list[adcp.types.generated_poc.enums.universal_macro.UniversalMacro | str] | None
Instance variables
var canonical_parameters : adcp.types.generated_poc.core.product_format_declaration.ProductFormatDeclaration | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
var input_format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
var output_format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
var pricing_options : list[adcp.types.generated_poc.core.vendor_pricing_option.VendorPricingOption] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
Inherited members
class FormatOptionReference (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class FormatOptionReference(RootModel[FormatOptionReference1 | FormatOptionReference2]): root: Annotated[ FormatOptionReference1 | FormatOptionReference2, Field( description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', discriminator='scope', title='Format Option Reference', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[FormatOptionReference1, FormatOptionReference2]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.format_option_ref.FormatOptionReference1 | adcp.types.generated_poc.core.format_option_ref.FormatOptionReference2
class FormatId (**data: Any)-
Expand source code
class FormatReferenceStructuredObject(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) agent_url: Annotated[ AnyUrl, Field( description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." ), ] id: Annotated[ str, Field( description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", pattern='^[a-zA-Z0-9_-]+$', ), ] width: Annotated[ int | None, Field( description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', ge=1, ), ] = None height: Annotated[ int | None, Field( description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', ge=1, ), ] = None duration_ms: Annotated[ float | None, Field( description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', ge=1.0, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var agent_url : pydantic.networks.AnyUrlvar duration_ms : float | Nonevar height : int | Nonevar id : strvar model_configvar width : int | None
Inherited members
class GetAccountFinancialsRequest (**data: Any)-
Expand source code
class GetAccountFinancialsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference, Field(description='Account to query financials for. Must be an operator-billed account.'), ] period: Annotated[ date_range.DateRange | None, Field( description='Date range for the spend summary. Defaults to the current billing cycle if omitted.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar period : adcp.types.generated_poc.core.date_range.DateRange | None
Inherited members
class GetAccountFinancialsResponse1 (**data: Any)-
Expand source code
class GetAccountFinancialsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') account: account_ref_1.AccountReference currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] period: date_range_1.DateRange timezone: str spend: Spend | None = None credit: Credit | None = None balance: Balance | None = None payment_status: Literal['current', 'past_due', 'suspended'] | None = None payment_terms: payment_terms_1.PaymentTerms | None = None invoices: list[Invoice] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar balance : adcp.types.generated_poc.account.get_account_financials_response.Balance | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar credit : adcp.types.generated_poc.account.get_account_financials_response.Credit | Nonevar currency : strvar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar invoices : list[adcp.types.generated_poc.account.get_account_financials_response.Invoice] | Nonevar model_configvar payment_status : Literal['current', 'past_due', 'suspended'] | Nonevar payment_terms : adcp.types.generated_poc.enums.payment_terms.PaymentTerms | Nonevar period : adcp.types.generated_poc.core.date_range.DateRangevar spend : adcp.types.generated_poc.account.get_account_financials_response.Spend | Nonevar timezone : str
class GetAccountFinancialsSuccessResponse (**data: Any)-
Expand source code
class GetAccountFinancialsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') account: account_ref_1.AccountReference currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] period: date_range_1.DateRange timezone: str spend: Spend | None = None credit: Credit | None = None balance: Balance | None = None payment_status: Literal['current', 'past_due', 'suspended'] | None = None payment_terms: payment_terms_1.PaymentTerms | None = None invoices: list[Invoice] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar balance : adcp.types.generated_poc.account.get_account_financials_response.Balance | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar credit : adcp.types.generated_poc.account.get_account_financials_response.Credit | Nonevar currency : strvar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar invoices : list[adcp.types.generated_poc.account.get_account_financials_response.Invoice] | Nonevar model_configvar payment_status : Literal['current', 'past_due', 'suspended'] | Nonevar payment_terms : adcp.types.generated_poc.enums.payment_terms.PaymentTerms | Nonevar period : adcp.types.generated_poc.core.date_range.DateRangevar spend : adcp.types.generated_poc.account.get_account_financials_response.Spend | Nonevar timezone : str
Inherited members
class GetAccountFinancialsErrorResponse (**data: Any)-
Expand source code
class GetAccountFinancialsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class GetBrandIdentityRequest (**data: Any)-
Expand source code
class GetBrandIdentityRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) brand_id: Annotated[str, Field(description='Brand identifier from brand.json brands array')] fields: Annotated[ list[FieldModel] | None, Field( description='Optional identity sections to include in the response. When omitted, all sections the caller is authorized to see are returned. Core fields (brand_id, house, names) are always returned and do not need to be requested.', min_length=1, ), ] = None use_case: Annotated[ str | None, Field( description="Intended use case, so the agent can tailor the response. A 'voice_synthesis' use case returns voice configs; a 'likeness' use case returns high-res photos and appearance guidelines." ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var brand_id : strvar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.brand.get_brand_identity_request.FieldModel] | Nonevar model_configvar use_case : str | None
Inherited members
class GetBrandIdentityResponse1 (**data: Any)-
Expand source code
class GetBrandIdentityResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') brand_id: str house: House names: list[dict[str, str]] description: str | None = None industries: Annotated[list[str], Field(min_length=1)] | None = None keller_type: Literal['master', 'sub_brand', 'endorsed', 'independent'] | None = None logos: list[Logo] | None = None colors: Colors | None = None fonts: Fonts | None = None visual_guidelines: dict[str, Any] | None = None tone: Tone | None = None tagline: str | Annotated[list[dict[str, Annotated[str, StringConstraints(min_length=1)]]], Field(min_length=1)] | None = None voice_synthesis: VoiceSynthesis | None = None assets: list[Asset] | None = None rights: Rights | None = None available_fields: list[Literal['description', 'industries', 'keller_type', 'logos', 'colors', 'fonts', 'visual_guidelines', 'tone', 'tagline', 'voice_synthesis', 'assets', 'rights']] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var assets : list[adcp.types.generated_poc.brand.get_brand_identity_response.Asset] | Nonevar available_fields : list[typing.Literal['description', 'industries', 'keller_type', 'logos', 'colors', 'fonts', 'visual_guidelines', 'tone', 'tagline', 'voice_synthesis', 'assets', 'rights']] | Nonevar brand_id : strvar colors : adcp.types.generated_poc.brand.get_brand_identity_response.Colors | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar description : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fonts : adcp.types.generated_poc.brand.get_brand_identity_response.Fonts | Nonevar house : adcp.types.generated_poc.brand.get_brand_identity_response.Housevar industries : list[str] | Nonevar keller_type : Literal['master', 'sub_brand', 'endorsed', 'independent'] | Nonevar logos : list[adcp.types.generated_poc.brand.get_brand_identity_response.Logo] | Nonevar model_configvar names : list[dict[str, str]]var rights : adcp.types.generated_poc.brand.get_brand_identity_response.Rights | Nonevar tagline : str | list[dict[str, str]] | Nonevar tone : adcp.types.generated_poc.brand.get_brand_identity_response.Tone | Nonevar visual_guidelines : dict[str, typing.Any] | Nonevar voice_synthesis : adcp.types.generated_poc.brand.get_brand_identity_response.VoiceSynthesis | None
class GetBrandIdentitySuccessResponse (**data: Any)-
Expand source code
class GetBrandIdentityResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') brand_id: str house: House names: list[dict[str, str]] description: str | None = None industries: Annotated[list[str], Field(min_length=1)] | None = None keller_type: Literal['master', 'sub_brand', 'endorsed', 'independent'] | None = None logos: list[Logo] | None = None colors: Colors | None = None fonts: Fonts | None = None visual_guidelines: dict[str, Any] | None = None tone: Tone | None = None tagline: str | Annotated[list[dict[str, Annotated[str, StringConstraints(min_length=1)]]], Field(min_length=1)] | None = None voice_synthesis: VoiceSynthesis | None = None assets: list[Asset] | None = None rights: Rights | None = None available_fields: list[Literal['description', 'industries', 'keller_type', 'logos', 'colors', 'fonts', 'visual_guidelines', 'tone', 'tagline', 'voice_synthesis', 'assets', 'rights']] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var assets : list[adcp.types.generated_poc.brand.get_brand_identity_response.Asset] | Nonevar available_fields : list[typing.Literal['description', 'industries', 'keller_type', 'logos', 'colors', 'fonts', 'visual_guidelines', 'tone', 'tagline', 'voice_synthesis', 'assets', 'rights']] | Nonevar brand_id : strvar colors : adcp.types.generated_poc.brand.get_brand_identity_response.Colors | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar description : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fonts : adcp.types.generated_poc.brand.get_brand_identity_response.Fonts | Nonevar house : adcp.types.generated_poc.brand.get_brand_identity_response.Housevar industries : list[str] | Nonevar keller_type : Literal['master', 'sub_brand', 'endorsed', 'independent'] | Nonevar logos : list[adcp.types.generated_poc.brand.get_brand_identity_response.Logo] | Nonevar model_configvar names : list[dict[str, str]]var rights : adcp.types.generated_poc.brand.get_brand_identity_response.Rights | Nonevar tagline : str | list[dict[str, str]] | Nonevar tone : adcp.types.generated_poc.brand.get_brand_identity_response.Tone | Nonevar visual_guidelines : dict[str, typing.Any] | Nonevar voice_synthesis : adcp.types.generated_poc.brand.get_brand_identity_response.VoiceSynthesis | None
Inherited members
class GetBrandIdentityErrorResponse (**data: Any)-
Expand source code
class GetBrandIdentityResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class GetContentStandardsSuccessResponse (**data: Any)-
Expand source code
class GetContentStandardsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
class GetContentStandardsResponse1 (**data: Any)-
Expand source code
class GetContentStandardsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class GetContentStandardsErrorResponse (**data: Any)-
Expand source code
class GetContentStandardsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class GetCreativeDeliveryRequest (**data: Any)-
Expand source code
class GetCreativeDeliveryRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description='Account for routing and scoping. Limits results to creatives within this account.' ), ] = None media_buy_ids: Annotated[ list[str] | None, Field( description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.', min_length=1, ), ] = None creative_ids: Annotated[ list[str] | None, Field( description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.', min_length=1, ), ] = None start_date: Annotated[ str | None, Field( description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None end_date: Annotated[ str | None, Field( description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None max_variants: Annotated[ int | None, Field( description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.', ge=1, ), ] = None pagination: Annotated[ pagination_request.PaginationRequest | None, Field( description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_ids : list[str] | Nonevar end_date : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar max_variants : int | Nonevar media_buy_ids : list[str] | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar start_date : str | None
class GetCreativeDeliveryByBuyerRefRequest (**data: Any)-
Expand source code
class GetCreativeDeliveryRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description='Account for routing and scoping. Limits results to creatives within this account.' ), ] = None media_buy_ids: Annotated[ list[str] | None, Field( description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.', min_length=1, ), ] = None creative_ids: Annotated[ list[str] | None, Field( description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.', min_length=1, ), ] = None start_date: Annotated[ str | None, Field( description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None end_date: Annotated[ str | None, Field( description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None max_variants: Annotated[ int | None, Field( description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.', ge=1, ), ] = None pagination: Annotated[ pagination_request.PaginationRequest | None, Field( description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_ids : list[str] | Nonevar end_date : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar max_variants : int | Nonevar media_buy_ids : list[str] | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar start_date : str | None
class GetCreativeDeliveryByCreativeRequest (**data: Any)-
Expand source code
class GetCreativeDeliveryRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description='Account for routing and scoping. Limits results to creatives within this account.' ), ] = None media_buy_ids: Annotated[ list[str] | None, Field( description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.', min_length=1, ), ] = None creative_ids: Annotated[ list[str] | None, Field( description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.', min_length=1, ), ] = None start_date: Annotated[ str | None, Field( description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None end_date: Annotated[ str | None, Field( description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None max_variants: Annotated[ int | None, Field( description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.', ge=1, ), ] = None pagination: Annotated[ pagination_request.PaginationRequest | None, Field( description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_ids : list[str] | Nonevar end_date : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar max_variants : int | Nonevar media_buy_ids : list[str] | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar start_date : str | None
class GetCreativeDeliveryByMediaBuyRequest (**data: Any)-
Expand source code
class GetCreativeDeliveryRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description='Account for routing and scoping. Limits results to creatives within this account.' ), ] = None media_buy_ids: Annotated[ list[str] | None, Field( description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.', min_length=1, ), ] = None creative_ids: Annotated[ list[str] | None, Field( description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.', min_length=1, ), ] = None start_date: Annotated[ str | None, Field( description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None end_date: Annotated[ str | None, Field( description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None max_variants: Annotated[ int | None, Field( description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.', ge=1, ), ] = None pagination: Annotated[ pagination_request.PaginationRequest | None, Field( description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_ids : list[str] | Nonevar end_date : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar max_variants : int | Nonevar media_buy_ids : list[str] | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar start_date : str | None
Inherited members
class GetCreativeDeliveryResponse (**data: Any)-
Expand source code
class GetCreativeDeliveryResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) account_id: Annotated[ str | None, Field( description='Account identifier. Present when the response spans or is scoped to a specific account.' ), ] = None media_buy_id: Annotated[ str | None, Field( description="Publisher's media buy identifier. Present when the request was scoped to a single media buy." ), ] = None currency: Annotated[ str, Field( description="ISO 4217 currency code for monetary values in this response (e.g., 'USD', 'EUR')", pattern='^[A-Z]{3}$', ), ] reporting_period: Annotated[ReportingPeriod, Field(description='Date range for the report.')] creatives: Annotated[ Sequence[Creative], Field(description='Creative delivery data with variant breakdowns') ] pagination: Annotated[ Pagination | None, Field( description='Pagination information. Present when the request included pagination parameters. **Note:** `get_creative_delivery` uses page-based pagination (`limit`/`offset`) for historical reasons, distinct from the cursor-based [`PaginationResponse`](/schemas/v3/core/pagination-response.json) used by `list_*` tools. Field naming aligned with `PaginationResponse.total_count` in 3.1; the legacy `total` field is retained as a deprecated alias until 4.0. Sellers MUST populate both fields identically; buyers SHOULD prefer `total_count` (the canonical name) and ignore `total` if both are present.' ), ] = None errors: Annotated[ list[error.Error] | None, Field(description='Task-specific errors and warnings') ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account_id : str | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creatives : Sequence[adcp.types.generated_poc.creative.get_creative_delivery_response.Creative]var currency : strvar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar media_buy_id : str | Nonevar model_configvar pagination : adcp.types.generated_poc.creative.get_creative_delivery_response.Pagination | Nonevar reporting_period : adcp.types.generated_poc.creative.get_creative_delivery_response.ReportingPeriod
Inherited members
class GetCreativeFeaturesRequest (**data: Any)-
Expand source code
class GetCreativeFeaturesRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) creative_manifest: Annotated[ creative_manifest_1.CreativeManifest, Field(description='The creative manifest to evaluate. Contains format_id and assets.'), ] feature_ids: Annotated[ list[str] | None, Field( description='Optional filter to specific features. If omitted, returns all available features.', min_length=1, ), ] = None account: Annotated[ account_ref.AccountReference | None, Field( description='Account for billing this evaluation. Required when the governance agent charges per evaluation.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_manifest : adcp.types.generated_poc.core.creative_manifest.CreativeManifestvar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar feature_ids : list[str] | Nonevar model_config
Inherited members
class GetCreativeFeaturesResponse1 (**data: Any)-
Expand source code
class GetCreativeFeaturesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') results: list[creative_feature_result_1.CreativeFeatureResult] detail_url: AnyUrl | None = None audit_observations: list[audit_observation_1.CreativeAuditObservation] | None = None pricing_option_id: str | None = None vendor_cost: Annotated[float, Field(ge=0)] | None = None currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None consumption: creative_consumption_1.CreativeConsumption | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var audit_observations : list[adcp.types.generated_poc.creative.audit_observation.CreativeAuditObservation] | Nonevar consumption : adcp.types.generated_poc.core.creative_consumption.CreativeConsumption | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar currency : str | Nonevar detail_url : pydantic.networks.AnyUrl | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar pricing_option_id : str | Nonevar results : list[adcp.types.generated_poc.creative.creative_feature_result.CreativeFeatureResult]var vendor_cost : float | None
class GetCreativeFeaturesSuccessResponse (**data: Any)-
Expand source code
class GetCreativeFeaturesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') results: list[creative_feature_result_1.CreativeFeatureResult] detail_url: AnyUrl | None = None audit_observations: list[audit_observation_1.CreativeAuditObservation] | None = None pricing_option_id: str | None = None vendor_cost: Annotated[float, Field(ge=0)] | None = None currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None consumption: creative_consumption_1.CreativeConsumption | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var audit_observations : list[adcp.types.generated_poc.creative.audit_observation.CreativeAuditObservation] | Nonevar consumption : adcp.types.generated_poc.core.creative_consumption.CreativeConsumption | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar currency : str | Nonevar detail_url : pydantic.networks.AnyUrl | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar pricing_option_id : str | Nonevar results : list[adcp.types.generated_poc.creative.creative_feature_result.CreativeFeatureResult]var vendor_cost : float | None
Inherited members
class GetCreativeFeaturesErrorResponse (**data: Any)-
Expand source code
class GetCreativeFeaturesResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class GetMediaBuyArtifactsSuccessResponse (**data: Any)-
Expand source code
class GetMediaBuyArtifactsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str artifacts: list[Artifact] collection_info: CollectionInfo | None = None pagination: pagination_response_1.PaginationResponse | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var artifacts : list[adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response.Artifact]var collection_info : adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response.CollectionInfo | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar media_buy_id : strvar model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponse | None
class GetMediaBuyArtifactsResponse1 (**data: Any)-
Expand source code
class GetMediaBuyArtifactsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str artifacts: list[Artifact] collection_info: CollectionInfo | None = None pagination: pagination_response_1.PaginationResponse | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var artifacts : list[adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response.Artifact]var collection_info : adcp.types.generated_poc.content_standards.get_media_buy_artifacts_response.CollectionInfo | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar media_buy_id : strvar model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponse | None
Inherited members
class GetMediaBuyArtifactsErrorResponse (**data: Any)-
Expand source code
class GetMediaBuyArtifactsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class GetMediaBuyDeliveryRequest (**data: Any)-
Expand source code
class GetMediaBuyDeliveryRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description='Filter delivery data to a specific account. When omitted, returns data across all accessible accounts.' ), ] = None media_buy_ids: Annotated[ list[str] | None, Field(description='Array of media buy IDs to get delivery data for', min_length=1), ] = None status_filter: Annotated[ media_buy_status.MediaBuyStatus | StatusFilter | None, Field(description='Filter by status. Can be a single status or array of statuses'), ] = None start_date: Annotated[ str | None, Field( description="Start date for reporting period (YYYY-MM-DD). When omitted along with end_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None end_date: Annotated[ str | None, Field( description="End date for reporting period (YYYY-MM-DD). When omitted along with start_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'.", pattern='^\\d{4}-\\d{2}-\\d{2}$', ), ] = None include_package_daily_breakdown: Annotated[ bool | None, Field( description='When true, include daily_breakdown arrays within each package in by_package. Useful for per-package pacing analysis and line-item monitoring. Omit or set false to reduce response size — package daily data can be large for multi-package buys over long flights.' ), ] = False time_granularity: Annotated[ reporting_frequency.ReportingFrequency | None, Field( description="Per-window slice granularity for the pull, using the same vocabulary as reporting_webhook.reporting_frequency. When set, the seller returns per-window delivery slices over the date range — useful for reconstructing data a buyer's webhook receiver missed, since the slice payload is shape-aligned with what reporting_webhook would have delivered for the same window. Capability-scoped: the value MUST be one of the seller's declared reporting_capabilities.windowed_pull_granularities; otherwise the seller MUST return UNSUPPORTED_GRANULARITY. When omitted, behavior is unchanged (cumulative aggregates plus optional daily breakdowns per existing fields)." ), ] = None include_window_breakdown: Annotated[ bool | None, Field( description="When true, the response includes media_buy_deliveries[].windows[] — an array of per-window delivery slices over the date range at the requested time_granularity. Ignored when time_granularity is omitted. Each window's payload mirrors what reporting_webhook would have delivered for the same window, enabling lossless GET-path recovery for buyers who missed webhook fires. Omit or set false to reduce response size when only cumulative aggregates are needed." ), ] = False attribution_window: Annotated[ AttributionWindow | None, Field( description='Attribution window to apply for conversion metrics. When provided, the seller returns conversion data using the requested lookback windows instead of their platform default. The seller echoes the applied window in the response. Sellers that do not support configurable windows ignore this field and return their default. Check get_adcp_capabilities conversion_tracking.attribution_windows for available options.' ), ] = None reporting_dimensions: Annotated[ ReportingDimensions | None, Field( description='Request dimensional breakdowns in delivery reporting. Each key enables a specific breakdown dimension within by_package — include as an empty object (e.g., "device_type": {}) to activate with defaults. Omit entirely for no breakdowns (backward compatible). Unsupported dimensions are silently omitted from the response. Note: keyword, catalog_item, and creative breakdowns are returned automatically when the seller supports them and are not controlled by this object.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar attribution_window : adcp.types.generated_poc.media_buy.get_media_buy_delivery_request.AttributionWindow | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar end_date : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar include_package_daily_breakdown : bool | Nonevar include_window_breakdown : bool | Nonevar media_buy_ids : list[str] | Nonevar model_configvar reporting_dimensions : adcp.types.generated_poc.media_buy.get_media_buy_delivery_request.ReportingDimensions | Nonevar start_date : str | Nonevar status_filter : adcp.types.generated_poc.enums.media_buy_status.MediaBuyStatus | adcp.types.generated_poc.media_buy.get_media_buy_delivery_request.StatusFilter | Nonevar time_granularity : adcp.types.generated_poc.enums.reporting_frequency.ReportingFrequency | None
Inherited members
class GetMediaBuyDeliveryResponse (**data: Any)-
Expand source code
class GetMediaBuyDeliveryResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) notification_type: Annotated[ NotificationType | None, Field( description='Type of webhook notification (only present in webhook deliveries): scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = resending period with corrected data (same window), window_update = resending period with a wider measurement window (e.g., C3 superseding live, C7 superseding C3)' ), ] = None partial_data: Annotated[ bool | None, Field( description='Indicates if any media buys in this webhook have missing/delayed data (only present in webhook deliveries)' ), ] = None unavailable_count: Annotated[ int | None, Field( description='Number of media buys with reporting_delayed or failed status (only present in webhook deliveries when partial_data is true)', ge=0, ), ] = None sequence_number: Annotated[ int | None, Field( description='Sequential notification number (only present in webhook deliveries, starts at 1)', ge=1, ), ] = None next_expected_at: Annotated[ AwareDatetime | None, Field( description="ISO 8601 timestamp for next expected notification (only present in webhook deliveries when notification_type is not 'final')" ), ] = None reporting_period: Annotated[ ReportingPeriod, Field(description='Date range for the report. All periods use UTC timezone.'), ] currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] attribution_window: Annotated[ attribution_window_1.AttributionWindow | None, Field( description='Attribution methodology and lookback windows used for conversion metrics in this response. All media buys from a single seller share the same attribution methodology. Enables cross-platform comparison (e.g., Amazon 14-day click vs. Criteo 30-day click).' ), ] = None aggregated_totals: Annotated[ AggregatedTotals | None, Field( description='Combined metrics across all returned media buys. Only included in API responses (get_media_buy_delivery), not in webhook notifications.' ), ] = None media_buy_deliveries: Annotated[ Sequence[MediaBuyDelivery], Field( description='Array of delivery data for media buys. When used in webhook notifications, may contain multiple media buys aggregated by publisher. When used in get_media_buy_delivery API responses, typically contains requested media buys.' ), ] errors: Annotated[ list[error.Error] | None, Field( description='Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)' ), ] = None sandbox: Annotated[ bool | None, Field(description='When true, this response contains simulated data from sandbox mode.'), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var aggregated_totals : adcp.types.generated_poc.media_buy.get_media_buy_delivery_response.AggregatedTotals | Nonevar attribution_window : adcp.types.generated_poc.core.attribution_window.AttributionWindow | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar currency : strvar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar media_buy_deliveries : Sequence[adcp.types.generated_poc.media_buy.get_media_buy_delivery_response.MediaBuyDelivery]var model_configvar next_expected_at : pydantic.types.AwareDatetime | Nonevar notification_type : adcp.types.generated_poc.media_buy.get_media_buy_delivery_response.NotificationType | Nonevar partial_data : bool | Nonevar reporting_period : adcp.types.generated_poc.media_buy.get_media_buy_delivery_response.ReportingPeriodvar sandbox : bool | Nonevar sequence_number : int | Nonevar status : adcp.types.generated_poc.enums.task_status.TaskStatus | None
Inherited members
class GetMediaBuysRequest (**data: Any)-
Expand source code
class GetMediaBuysRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description='Account to retrieve media buys for. When omitted, returns data across all accessible accounts.' ), ] = None media_buy_ids: Annotated[ list[str] | None, Field( description='Array of media buy IDs to retrieve. When omitted, returns a paginated set of accessible media buys matching status_filter.', min_length=1, ), ] = None status_filter: Annotated[ media_buy_status.MediaBuyStatus | StatusFilter | None, Field( description='Filter by status. Can be a single status or array of statuses. Defaults to ["active"] when media_buy_ids is omitted. When media_buy_ids is provided, no implicit status filter is applied.' ), ] = None include_snapshot: Annotated[ bool | None, Field( description='When true, include a near-real-time delivery snapshot for each package. Snapshots reflect the latest available entity-level stats from the platform (e.g., updated every ~15 minutes on GAM, ~1 hour on batch-only platforms). The staleness_seconds field on each snapshot indicates data freshness. If a snapshot cannot be returned, package.snapshot_unavailable_reason explains why. Defaults to false.' ), ] = False include_history: Annotated[ int | None, Field( description='When present, include the last N revision history entries for each media buy (returns min(N, available entries)). Each entry contains revision number, timestamp, actor, and a summary of what changed. Omit or set to 0 to exclude history (default). Recommended: 5-10 for monitoring, 50+ for audit.', ge=0, le=1000, ), ] = 0 include_webhook_activity: Annotated[ bool | None, Field( description="When true, each returned media buy includes a `webhook_activity` array describing recent delivery-report webhook fires for the calling principal. Used by buyer agents to verify whether a publisher actually fired against the buyer's registered endpoint and what the endpoint returned — closes the operator-ticket loop for webhook debugging. Scoped to the calling principal: a buyer sees only fires targeting its own endpoint, even when multiple principals share visibility into the same media buy. Defaults to false. See `webhook_activity_limit` for the per-buy cap." ), ] = False webhook_activity_limit: Annotated[ int | None, Field( description="Maximum number of webhook delivery records to return per media buy, ordered most-recent first. Ignored when `include_webhook_activity` is false. Sellers that surface webhook activity MUST retain records for at least 30 days from each record's `completed_at` (see `webhook_activity` description in the response schema for the `pending`-status carve-out); sellers unable to honor that floor MUST omit the field entirely rather than truncate. When a buy has more historical fires than the limit, only the most recent are returned — there is no cursor for older fires; this surface is a debug aid, not a full audit log.", ge=1, le=200, ), ] = 50 pagination: Annotated[ pagination_request.PaginationRequest | None, Field( description='Cursor-based pagination controls. Strongly recommended when querying broad scopes (for example, all active media buys in an account).' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar include_history : int | Nonevar include_snapshot : bool | Nonevar include_webhook_activity : bool | Nonevar media_buy_ids : list[str] | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar status_filter : adcp.types.generated_poc.enums.media_buy_status.MediaBuyStatus | adcp.types.generated_poc.media_buy.get_media_buys_request.StatusFilter | Nonevar webhook_activity_limit : int | None
Inherited members
class GetMediaBuysResponse (**data: Any)-
Expand source code
class GetMediaBuysResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) media_buys: Annotated[ Sequence[MediaBuy], Field( description='Array of media buys with status, creative approval state, and optional delivery snapshots' ), ] errors: Annotated[ list[error.Error] | None, Field(description='Task-specific errors (e.g., media buy not found)'), ] = None pagination: Annotated[ pagination_response.PaginationResponse | None, Field(description='Pagination metadata for the media_buys array.'), ] = None sandbox: Annotated[ bool | None, Field(description='When true, this response contains simulated data from sandbox mode.'), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar media_buys : Sequence[adcp.types.generated_poc.media_buy.get_media_buys_response.MediaBuy]var model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponse | Nonevar sandbox : bool | None
Inherited members
class GetPlanAuditLogsRequest (**data: Any)-
Expand source code
class GetPlanAuditLogsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) plan_ids: Annotated[ list[str] | None, Field( description='Plan IDs to retrieve. For a single plan, pass a one-element array. Plans uniquely scope account and operator; do not include a separate `account` field — the governance agent resolves account from each plan. Including `account` is rejected by `additionalProperties: false`.', min_length=1, ), ] = None portfolio_plan_ids: Annotated[ list[str] | None, Field( description='Portfolio plan IDs. The governance agent expands each to its member_plan_ids and returns combined audit data.', min_length=1, ), ] = None governance_contexts: Annotated[ list[str] | None, Field( description='Filter audit entries by governance context. Returns only checks and outcomes that share these governance contexts, enabling lifecycle tracing across purchase types.', min_length=1, ), ] = None purchase_types: Annotated[ list[purchase_type.PurchaseType] | None, Field( description="Filter audit entries by purchase type. Returns only checks and outcomes matching these purchase types (e.g., ['rights_license'] to see all rights activity).", min_length=1, ), ] = None include_entries: Annotated[ bool | None, Field(description='Include the full audit trail. Default: false.') ] = False context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar governance_contexts : list[str] | Nonevar include_entries : bool | Nonevar model_configvar plan_ids : list[str] | Nonevar portfolio_plan_ids : list[str] | Nonevar purchase_types : list[adcp.types.generated_poc.enums.purchase_type.PurchaseType] | None
Inherited members
class GetPlanAuditLogsResponse (**data: Any)-
Expand source code
class GetPlanAuditLogsResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) plans: Annotated[list[Plan], Field(description='Audit data for each requested plan.')] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar plans : list[adcp.types.generated_poc.governance.get_plan_audit_logs_response.Plan]
Inherited members
class GetProductsRequest (**data: Any)-
Expand source code
class GetProductsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) buying_mode: Annotated[ BuyingMode, Field( description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'." ), ] brief: Annotated[ str | None, Field( description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'." ), ] = None refine: Annotated[ list[Refine] | None, Field( description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; the buyer expressing intent to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose intent specifically requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon the intent if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery for that loss of buyer intent beyond sequencing single-finalize calls and accepting the looser commit guarantee.", min_length=1, ), ] = None brand: Annotated[ brand_ref.BrandReference | None, Field( description='Brand reference for product discovery context. Resolved to full brand identity at execution time.' ), ] = None catalog: Annotated[ catalog_1.Catalog | None, Field( description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.' ), ] = None account: Annotated[ account_ref.AccountReference | None, Field( description="Account for product lookup. Returns products with pricing specific to this account's rate card." ), ] = None preferred_delivery_types: Annotated[ list[delivery_type_1.DeliveryType] | None, Field( description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.', min_length=1, ), ] = None filters: product_filters.ProductFilters | None = None property_list: Annotated[ property_list_ref.PropertyListReference | None, Field( description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.' ), ] = None fields: Annotated[ list[Field1] | None, Field( description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.', min_length=1, ), ] = None time_budget: Annotated[ duration.Duration | None, Field( description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async terminal completion/failure notifications on curated discovery. Meaningful only for `buying_mode: "brief"` and `buying_mode: "refine"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief/refine request includes this field and the seller returns a Submitted envelope, the seller MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the seller cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: sellers MUST NOT route `buying_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' ), ] = None pagination: pagination_request.PaginationRequest | None = None if_wholesale_feed_version: Annotated[ str | None, Field( description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_products response from this agent. Only valid when buying_mode is wholesale. When provided, the seller compares against its current wholesale product feed version for the buyer's cache_scope and MAY return an unchanged: true response (with products omitted) if nothing has changed. The token is scope-keyed: buyers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. Backward-compatible: pre-v3.1 agents that ignore this field simply return the full payload, same as the unchanged-server path. See specs/wholesale-feed-webhooks.md for the full sync pattern." ), ] = None if_pricing_version: Annotated[ str | None, Field( description="Opaque pricing_version token from a prior get_products response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → seller returns the full payload (pricing is implicitly stale); (2) if_wholesale_feed_version matches but if_pricing_version mismatches → seller returns the full payload so the buyer sees updated pricing_options; (3) both match → seller MAY return unchanged: true. Agents that don't track pricing separately ignore if_pricing_version and fall back to if_wholesale_feed_version semantics. Useful for storefronts that re-price compositions far more often than they re-render product mirrors." ), ] = None context: context_1.ContextObject | None = None required_policies: Annotated[ list[str] | None, Field( description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar brand : adcp.types.generated_poc.core.brand_ref.BrandReference | Nonevar brief : str | Nonevar buying_mode : adcp.types.generated_poc.media_buy.get_products_request.BuyingMode | Nonevar catalog : adcp.types.generated_poc.core.catalog.Catalog | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.media_buy.get_products_request.Field1] | Nonevar filters : adcp.types.generated_poc.core.product_filters.ProductFilters | Nonevar if_pricing_version : str | Nonevar if_wholesale_feed_version : str | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar preferred_delivery_types : list[adcp.types.generated_poc.enums.delivery_type.DeliveryType] | Nonevar property_list : adcp.types.generated_poc.core.property_list_ref.PropertyListReference | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar refine : list[adcp.types.generated_poc.media_buy.get_products_request.Refine] | Nonevar required_policies : list[str] | Nonevar time_budget : adcp.types.generated_poc.core.duration.Duration | None
class GetProductsBriefRequest (**data: Any)-
Expand source code
class GetProductsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) buying_mode: Annotated[ BuyingMode, Field( description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'." ), ] brief: Annotated[ str | None, Field( description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'." ), ] = None refine: Annotated[ list[Refine] | None, Field( description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; the buyer expressing intent to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose intent specifically requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon the intent if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery for that loss of buyer intent beyond sequencing single-finalize calls and accepting the looser commit guarantee.", min_length=1, ), ] = None brand: Annotated[ brand_ref.BrandReference | None, Field( description='Brand reference for product discovery context. Resolved to full brand identity at execution time.' ), ] = None catalog: Annotated[ catalog_1.Catalog | None, Field( description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.' ), ] = None account: Annotated[ account_ref.AccountReference | None, Field( description="Account for product lookup. Returns products with pricing specific to this account's rate card." ), ] = None preferred_delivery_types: Annotated[ list[delivery_type_1.DeliveryType] | None, Field( description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.', min_length=1, ), ] = None filters: product_filters.ProductFilters | None = None property_list: Annotated[ property_list_ref.PropertyListReference | None, Field( description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.' ), ] = None fields: Annotated[ list[Field1] | None, Field( description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.', min_length=1, ), ] = None time_budget: Annotated[ duration.Duration | None, Field( description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async terminal completion/failure notifications on curated discovery. Meaningful only for `buying_mode: "brief"` and `buying_mode: "refine"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief/refine request includes this field and the seller returns a Submitted envelope, the seller MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the seller cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: sellers MUST NOT route `buying_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' ), ] = None pagination: pagination_request.PaginationRequest | None = None if_wholesale_feed_version: Annotated[ str | None, Field( description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_products response from this agent. Only valid when buying_mode is wholesale. When provided, the seller compares against its current wholesale product feed version for the buyer's cache_scope and MAY return an unchanged: true response (with products omitted) if nothing has changed. The token is scope-keyed: buyers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. Backward-compatible: pre-v3.1 agents that ignore this field simply return the full payload, same as the unchanged-server path. See specs/wholesale-feed-webhooks.md for the full sync pattern." ), ] = None if_pricing_version: Annotated[ str | None, Field( description="Opaque pricing_version token from a prior get_products response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → seller returns the full payload (pricing is implicitly stale); (2) if_wholesale_feed_version matches but if_pricing_version mismatches → seller returns the full payload so the buyer sees updated pricing_options; (3) both match → seller MAY return unchanged: true. Agents that don't track pricing separately ignore if_pricing_version and fall back to if_wholesale_feed_version semantics. Useful for storefronts that re-price compositions far more often than they re-render product mirrors." ), ] = None context: context_1.ContextObject | None = None required_policies: Annotated[ list[str] | None, Field( description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar brand : adcp.types.generated_poc.core.brand_ref.BrandReference | Nonevar brief : str | Nonevar buying_mode : adcp.types.generated_poc.media_buy.get_products_request.BuyingMode | Nonevar catalog : adcp.types.generated_poc.core.catalog.Catalog | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.media_buy.get_products_request.Field1] | Nonevar filters : adcp.types.generated_poc.core.product_filters.ProductFilters | Nonevar if_pricing_version : str | Nonevar if_wholesale_feed_version : str | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar preferred_delivery_types : list[adcp.types.generated_poc.enums.delivery_type.DeliveryType] | Nonevar property_list : adcp.types.generated_poc.core.property_list_ref.PropertyListReference | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar refine : list[adcp.types.generated_poc.media_buy.get_products_request.Refine] | Nonevar required_policies : list[str] | Nonevar time_budget : adcp.types.generated_poc.core.duration.Duration | None
class GetProductsRefineRequest (**data: Any)-
Expand source code
class GetProductsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) buying_mode: Annotated[ BuyingMode, Field( description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'." ), ] brief: Annotated[ str | None, Field( description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'." ), ] = None refine: Annotated[ list[Refine] | None, Field( description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; the buyer expressing intent to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose intent specifically requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon the intent if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery for that loss of buyer intent beyond sequencing single-finalize calls and accepting the looser commit guarantee.", min_length=1, ), ] = None brand: Annotated[ brand_ref.BrandReference | None, Field( description='Brand reference for product discovery context. Resolved to full brand identity at execution time.' ), ] = None catalog: Annotated[ catalog_1.Catalog | None, Field( description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.' ), ] = None account: Annotated[ account_ref.AccountReference | None, Field( description="Account for product lookup. Returns products with pricing specific to this account's rate card." ), ] = None preferred_delivery_types: Annotated[ list[delivery_type_1.DeliveryType] | None, Field( description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.', min_length=1, ), ] = None filters: product_filters.ProductFilters | None = None property_list: Annotated[ property_list_ref.PropertyListReference | None, Field( description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.' ), ] = None fields: Annotated[ list[Field1] | None, Field( description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.', min_length=1, ), ] = None time_budget: Annotated[ duration.Duration | None, Field( description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async terminal completion/failure notifications on curated discovery. Meaningful only for `buying_mode: "brief"` and `buying_mode: "refine"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief/refine request includes this field and the seller returns a Submitted envelope, the seller MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the seller cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: sellers MUST NOT route `buying_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' ), ] = None pagination: pagination_request.PaginationRequest | None = None if_wholesale_feed_version: Annotated[ str | None, Field( description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_products response from this agent. Only valid when buying_mode is wholesale. When provided, the seller compares against its current wholesale product feed version for the buyer's cache_scope and MAY return an unchanged: true response (with products omitted) if nothing has changed. The token is scope-keyed: buyers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. Backward-compatible: pre-v3.1 agents that ignore this field simply return the full payload, same as the unchanged-server path. See specs/wholesale-feed-webhooks.md for the full sync pattern." ), ] = None if_pricing_version: Annotated[ str | None, Field( description="Opaque pricing_version token from a prior get_products response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → seller returns the full payload (pricing is implicitly stale); (2) if_wholesale_feed_version matches but if_pricing_version mismatches → seller returns the full payload so the buyer sees updated pricing_options; (3) both match → seller MAY return unchanged: true. Agents that don't track pricing separately ignore if_pricing_version and fall back to if_wholesale_feed_version semantics. Useful for storefronts that re-price compositions far more often than they re-render product mirrors." ), ] = None context: context_1.ContextObject | None = None required_policies: Annotated[ list[str] | None, Field( description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar brand : adcp.types.generated_poc.core.brand_ref.BrandReference | Nonevar brief : str | Nonevar buying_mode : adcp.types.generated_poc.media_buy.get_products_request.BuyingMode | Nonevar catalog : adcp.types.generated_poc.core.catalog.Catalog | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.media_buy.get_products_request.Field1] | Nonevar filters : adcp.types.generated_poc.core.product_filters.ProductFilters | Nonevar if_pricing_version : str | Nonevar if_wholesale_feed_version : str | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar preferred_delivery_types : list[adcp.types.generated_poc.enums.delivery_type.DeliveryType] | Nonevar property_list : adcp.types.generated_poc.core.property_list_ref.PropertyListReference | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar refine : list[adcp.types.generated_poc.media_buy.get_products_request.Refine] | Nonevar required_policies : list[str] | Nonevar time_budget : adcp.types.generated_poc.core.duration.Duration | None
class GetProductsWholesaleRequest (**data: Any)-
Expand source code
class GetProductsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) buying_mode: Annotated[ BuyingMode, Field( description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'." ), ] brief: Annotated[ str | None, Field( description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'." ), ] = None refine: Annotated[ list[Refine] | None, Field( description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; the buyer expressing intent to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose intent specifically requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon the intent if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery for that loss of buyer intent beyond sequencing single-finalize calls and accepting the looser commit guarantee.", min_length=1, ), ] = None brand: Annotated[ brand_ref.BrandReference | None, Field( description='Brand reference for product discovery context. Resolved to full brand identity at execution time.' ), ] = None catalog: Annotated[ catalog_1.Catalog | None, Field( description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.' ), ] = None account: Annotated[ account_ref.AccountReference | None, Field( description="Account for product lookup. Returns products with pricing specific to this account's rate card." ), ] = None preferred_delivery_types: Annotated[ list[delivery_type_1.DeliveryType] | None, Field( description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.', min_length=1, ), ] = None filters: product_filters.ProductFilters | None = None property_list: Annotated[ property_list_ref.PropertyListReference | None, Field( description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.' ), ] = None fields: Annotated[ list[Field1] | None, Field( description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.', min_length=1, ), ] = None time_budget: Annotated[ duration.Duration | None, Field( description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async terminal completion/failure notifications on curated discovery. Meaningful only for `buying_mode: "brief"` and `buying_mode: "refine"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief/refine request includes this field and the seller returns a Submitted envelope, the seller MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the seller cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: sellers MUST NOT route `buying_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' ), ] = None pagination: pagination_request.PaginationRequest | None = None if_wholesale_feed_version: Annotated[ str | None, Field( description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_products response from this agent. Only valid when buying_mode is wholesale. When provided, the seller compares against its current wholesale product feed version for the buyer's cache_scope and MAY return an unchanged: true response (with products omitted) if nothing has changed. The token is scope-keyed: buyers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. Backward-compatible: pre-v3.1 agents that ignore this field simply return the full payload, same as the unchanged-server path. See specs/wholesale-feed-webhooks.md for the full sync pattern." ), ] = None if_pricing_version: Annotated[ str | None, Field( description="Opaque pricing_version token from a prior get_products response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → seller returns the full payload (pricing is implicitly stale); (2) if_wholesale_feed_version matches but if_pricing_version mismatches → seller returns the full payload so the buyer sees updated pricing_options; (3) both match → seller MAY return unchanged: true. Agents that don't track pricing separately ignore if_pricing_version and fall back to if_wholesale_feed_version semantics. Useful for storefronts that re-price compositions far more often than they re-render product mirrors." ), ] = None context: context_1.ContextObject | None = None required_policies: Annotated[ list[str] | None, Field( description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar brand : adcp.types.generated_poc.core.brand_ref.BrandReference | Nonevar brief : str | Nonevar buying_mode : adcp.types.generated_poc.media_buy.get_products_request.BuyingMode | Nonevar catalog : adcp.types.generated_poc.core.catalog.Catalog | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.media_buy.get_products_request.Field1] | Nonevar filters : adcp.types.generated_poc.core.product_filters.ProductFilters | Nonevar if_pricing_version : str | Nonevar if_wholesale_feed_version : str | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar preferred_delivery_types : list[adcp.types.generated_poc.enums.delivery_type.DeliveryType] | Nonevar property_list : adcp.types.generated_poc.core.property_list_ref.PropertyListReference | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar refine : list[adcp.types.generated_poc.media_buy.get_products_request.Refine] | Nonevar required_policies : list[str] | Nonevar time_budget : adcp.types.generated_poc.core.duration.Duration | None
Inherited members
class GetProductsResponse (**data: Any)-
Expand source code
class GetProductsResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) products: Annotated[ list[product.Product] | None, Field(description='Array of matching products') ] = None extensions: Annotated[ dict[Annotated[str, StringConstraints(pattern=r'^https?://[^@]+@sha256:[a-f0-9]{64}$')], Extensions] | None, Field( description='Bundled platform-extension definitions referenced by any product in `products`. Keyed by `<extension_uri>@<digest>` (e.g., `https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel@sha256:abc...`). When present, lets buyers resolve `platform_extensions` references on product format declarations without a separate fetch. Buyer SDKs cache by URI@digest; subsequent get_products responses MAY omit definitions the buyer already has cached and rely on the digest match. Each value is an extension definition with `extends` (the canonical concept it extends, e.g., `tracking`), `fields` (the schema for additional fields the extension contributes), `version`, and optional `description`.' ), ] = None proposals: Annotated[ list[proposal.Proposal] | None, Field( description='Optional array of proposed media plans with budget allocations across products. Publishers include proposals when they can provide strategic guidance based on the brief. Proposals are actionable - buyers can refine them via follow-up get_products calls within the same session, or execute them directly via create_media_buy.' ), ] = None errors: Annotated[ list[error.Error] | None, Field(description='Task-specific errors and warnings (e.g., product filtering issues)'), ] = None property_list_applied: Annotated[ bool | None, Field( description='[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent.' ), ] = None catalog_applied: Annotated[ bool | None, Field( description='Whether the seller filtered results based on the provided catalog. True if the seller matched catalog items against its inventory. Absent or false if no catalog was provided or the seller does not support catalog matching.' ), ] = None refinement_applied: Annotated[ list[RefinementApplied] | None, Field( description="Seller's response to each change request in the refine array, matched by position. Each entry acknowledges whether the corresponding ask was applied, partially applied, or unable to be fulfilled. MUST contain the same number of entries in the same order as the request's refine array. Only present when the request used buying_mode: 'refine'. Each entry MUST echo the request entry's scope and — for product and proposal scopes — the matching id field (product_id or proposal_id), so orchestrators can cross-validate alignment." ), ] = None incomplete: Annotated[ list[IncompleteItem] | None, Field( description="Declares what the seller could not finish within the buyer's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", min_length=1, ), ] = None filter_diagnostics: Annotated[ FilterDiagnostics | None, Field( description="Optional non-fatal diagnostic block describing how the request's `filters` narrowed the candidate set. Use this to disambiguate empty/small result lists between 'no inventory matches the brief' and 'a specific filter excluded everything', without breaking the filter-not-fail convention (sellers still silently exclude unmatched products; this block is observability, not error reporting). Sellers MAY populate this when meaningful narrowing occurred; buyers MAY use it for triage UX without depending on its presence. Counts only — products are not enumerated by name to avoid leaking competitive intelligence about adjacent campaigns or seller inventory. `total_candidates` and `excluded_by` are independently optional — sellers whose baseline candidate set size is sensitive MAY emit `excluded_by` without `total_candidates`, or vice versa.", examples=[ { 'semantics': 'only', 'total_candidates': 47, 'excluded_by': { 'required_metrics': {'count': 31, 'values': ['completed_views']}, 'required_geo_targeting': {'count': 9}, 'pricing_currencies': {'count': 3, 'values': ['USD']}, 'budget_range': {'count': 7}, }, } ], ), ] = None pagination: pagination_response.PaginationResponse | None = None wholesale_feed_version: Annotated[ str | None, Field( description="Opaque token representing the version of the wholesale product feed state used to compose this response. Sellers that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so buyers can cache and probe later. Buyers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A buyer caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." ), ] = None pricing_version: Annotated[ str | None, Field( description='Opaque token representing the version of the pricing layer, including product pricing_options and nested signal_targeting_options pricing_options. When the seller supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Sellers not separating these MAY omit pricing_version and use wholesale_feed_version for both.' ), ] = None cache_scope: Annotated[ CacheScope | None, Field( description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the seller's published rate card; the buyer MAY dedupe under (agent, buying_mode, filters, property_list, catalog) without scoping by account. 'account': this response includes account-specific overrides; the buyer MUST cache the version under (agent, buying_mode, filters, property_list, catalog, account_id). When the request did NOT include `account`, the seller MUST return `cache_scope: 'public'`. When the request included `account`, the seller MUST return either: 'public' (this account prices off the public rate card — buyer dedupes) or 'account' (account-specific overrides exist — buyer caches under the account key). Sellers MAY return 'public' on an account-scoped request that previously had overrides — buyers SHOULD interpret this as a downgrade and drop their account-overlay for the (agent, filters, mode) tuple. Without schema-required cache_scope, a seller silently omitting the field on an account-scoped response would cause buyers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs that validate strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version` (release-precision version negotiation, 3.1). For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 sellers correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." ), ] = CacheScope.public unchanged: Annotated[ Literal[True] | None, Field( description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the seller's current version for the buyer's cache_scope, in which case products[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Buyers receiving unchanged: true MUST NOT mutate their local wholesale product mirror. **One shape per state:** sellers MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries products. Two shapes ({ unchanged: false, products: [...] } vs. { products: [...] }) for the same state would let some sellers always emit the field and some never would, creating an inconsistency the wire shouldn't carry." ), ] = None sandbox: Annotated[ bool | None, Field(description='When true, this response contains simulated data from sandbox mode.'), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var cache_scope : adcp.types.generated_poc.media_buy.get_products_response.CacheScope | Nonevar catalog_applied : bool | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar extensions : dict[str, adcp.types.generated_poc.media_buy.get_products_response.Extensions] | Nonevar filter_diagnostics : adcp.types.generated_poc.media_buy.get_products_response.FilterDiagnostics | Nonevar incomplete : list[adcp.types.generated_poc.media_buy.get_products_response.IncompleteItem] | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponse | Nonevar pricing_version : str | Nonevar products : list[adcp.types.generated_poc.core.product.Product] | Nonevar property_list_applied : bool | Nonevar proposals : list[adcp.types.generated_poc.core.proposal.Proposal] | Nonevar refinement_applied : list[adcp.types.generated_poc.media_buy.get_products_response.RefinementApplied] | Nonevar sandbox : bool | Nonevar status : adcp.types.generated_poc.enums.task_status.TaskStatus | Nonevar unchanged : Literal[True] | Nonevar wholesale_feed_version : str | None
Inherited members
class GetRightsRequest (**data: Any)-
Expand source code
class GetRightsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) query: Annotated[ str, Field( description='Natural language description of desired rights. The agent interprets intent, budget signals, and compatibility from this text.', max_length=2000, ), ] uses: Annotated[ list[right_use.RightUse], Field( description='Rights uses being requested. The agent returns options covering these uses, potentially bundled into composite pricing.', min_length=1, ), ] buyer_brand: Annotated[ brand_ref.BrandReference | None, Field( description="The buyer's brand. The agent fetches the buyer's brand.json for compatibility filtering (e.g., dietary conflicts, competitor exclusions)." ), ] = None countries: Annotated[ list[Country] | None, Field( description='Countries where rights are needed (ISO 3166-1 alpha-2). Filters to rights available in these markets.' ), ] = None brand_id: Annotated[ str | None, Field( description="Search within a specific brand's rights. If omitted, searches across the agent's full roster." ), ] = None right_type: Annotated[ right_type_1.RightType | None, Field(description='Filter by type of rights (talent, music, stock_media, etc.)'), ] = None include_excluded: Annotated[ bool | None, Field( description='Include filtered-out results in the excluded array with reasons. Defaults to false.' ), ] = False pagination: Annotated[ pagination_request.PaginationRequest | None, Field(description='Pagination parameters for large result sets'), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var brand_id : str | Nonevar buyer_brand : adcp.types.generated_poc.core.brand_ref.BrandReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar countries : list[adcp.types.generated_poc.brand.get_rights_request.Country] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar include_excluded : bool | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar query : strvar right_type : adcp.types.generated_poc.enums.right_type.RightType | Nonevar uses : list[adcp.types.generated_poc.enums.right_use.RightUse]
Inherited members
class GetRightsResponse1 (**data: Any)-
Expand source code
class GetRightsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights: list[Right] excluded: list[Excluded] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar excluded : list[adcp.types.generated_poc.brand.get_rights_response.Excluded] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar rights : list[adcp.types.generated_poc.brand.get_rights_response.Right]
class GetRightsSuccessResponse (**data: Any)-
Expand source code
class GetRightsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') rights: list[Right] excluded: list[Excluded] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar excluded : list[adcp.types.generated_poc.brand.get_rights_response.Excluded] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar rights : list[adcp.types.generated_poc.brand.get_rights_response.Right]
Inherited members
class GetRightsErrorResponse (**data: Any)-
Expand source code
class GetRightsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class GetSignalsRequest (**data: Any)-
Expand source code
class GetSignalsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) discovery_mode: Annotated[ DiscoveryMode | None, Field( description="Declares caller intent for this request. 'brief' (default): semantic discovery — signal_spec, signal_refs, or legacy signal_ids is required and the agent performs inference/RAG. 'wholesale': raw wholesale signals feed enumeration — signal_spec, signal_refs, and signal_ids MUST NOT be provided and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries when present. Sellers receiving requests from pre-v3.1 clients without discovery_mode MUST default to 'brief'. Timing semantics: 'wholesale' is a wholesale signals feed read — agents SHOULD respond synchronously and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field, not via a task-handoff envelope. Agents that do not implement wholesale enumeration MAY return INVALID_REQUEST for wholesale calls; callers SHOULD probe via get_adcp_capabilities (signals.discovery_modes) first." ), ] = DiscoveryMode.brief account: Annotated[ account_ref.AccountReference | None, Field( description="Account for this request. When provided, the signals agent returns per-account pricing options if configured. In 'wholesale' mode, this is the rate-card scope: when omitted in wholesale mode, agents return their default rate-card pricing or omit pricing_options entirely." ), ] = None signal_spec: Annotated[ str | None, Field( description="Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_refs, provides context for the agent but signal_ref matches are returned first. MUST NOT be provided when discovery_mode is 'wholesale'." ), ] = None signal_refs: Annotated[ list[signal_ref_1.SignalRef] | None, Field( description="Specific signals to look up by reference. Returns exact matches for the requested SignalRef values. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments. MUST NOT be provided when discovery_mode is 'wholesale'.", min_length=1, ), ] = None signal_ids: Annotated[ list[signal_id_1.SignalId] | None, Field( deprecated=True, description="DEPRECATED. Use signal_refs instead. Legacy exact lookup field using SignalId objects. MUST NOT be provided when discovery_mode is 'wholesale'.", min_length=1, ), ] = None destinations: Annotated[ list[destination.Destination] | None, Field( description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', min_length=1, ), ] = None countries: Annotated[ list[Country] | None, Field( description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.', min_length=1, ), ] = None filters: signal_filters.SignalFilters | None = None fields: Annotated[ list[Field1] | None, Field( description="Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, data_subject_rights, and last_updated when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, small custom-signal result sets, and private/source-native signals when available. fields is a projection request, not an entitlement grant; agents MAY redact requested definition fields unless the caller is authorized for the underlying lineage, methodology, and rights-routing metadata. When consent_basis or art9_basis is projected for another provider's signal, the value remains provider-declared signal-definition posture; sellers and federating agents MUST NOT substitute their own processing basis. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as resolved URL plus catalog_etag, HTTP ETag/Last-Modified, or taxonomy.etag.", min_length=1, ), ] = None max_results: Annotated[ int | None, Field( deprecated=True, description='DEPRECATED: Use pagination.max_results instead. When both fields are present, agents MUST honor pagination.max_results. When only this field is present without a pagination envelope, agents SHOULD treat it as the page size subject to a maximum of 100 results. This field will be removed in AdCP 4.0.', ge=1, ), ] = None pagination: Annotated[ pagination_request.PaginationRequest | None, Field( description='Pagination parameters. Use pagination.max_results (max: 100, default: 50) and pagination.cursor for cursor-based page walks. When the deprecated top-level max_results field is also present, pagination.max_results takes precedence.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async terminal completion/failure notifications on semantic signal discovery. Meaningful only for `discovery_mode: "brief"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: agents MUST NOT route `discovery_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' ), ] = None if_wholesale_feed_version: Annotated[ str | None, Field( description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_signals response from this agent. Only valid when discovery_mode is wholesale. When provided, the agent compares against its current wholesale signals feed version for the caller's cache_scope and MAY return an unchanged: true response (with signals omitted) if nothing has changed. The token is scope-keyed: callers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full sync pattern." ), ] = None if_pricing_version: Annotated[ str | None, Field( description="Opaque pricing_version token from a prior get_signals response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → agent returns the full payload; (2) if_wholesale_feed_version matches but if_pricing_version mismatches → agent returns the full payload so the caller sees updated pricing_options; (3) both match → agent MAY return unchanged: true. Agents that don't track pricing separately ignore this and fall back to if_wholesale_feed_version semantics." ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar countries : list[adcp.types.generated_poc.signals.get_signals_request.Country] | Nonevar destinations : list[adcp.types.generated_poc.core.destination.Destination] | Nonevar discovery_mode : adcp.types.generated_poc.signals.get_signals_request.DiscoveryMode | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.signals.get_signals_request.Field1] | Nonevar filters : adcp.types.generated_poc.core.signal_filters.SignalFilters | Nonevar if_pricing_version : str | Nonevar if_wholesale_feed_version : str | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar signal_refs : list[adcp.types.generated_poc.core.signal_ref.SignalRef] | Nonevar signal_spec : str | None
Instance variables
var max_results : int | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
var signal_ids : list[adcp.types.generated_poc.core.signal_id.SignalId] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
class GetSignalsDiscoveryRequest (**data: Any)-
Expand source code
class GetSignalsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) discovery_mode: Annotated[ DiscoveryMode | None, Field( description="Declares caller intent for this request. 'brief' (default): semantic discovery — signal_spec, signal_refs, or legacy signal_ids is required and the agent performs inference/RAG. 'wholesale': raw wholesale signals feed enumeration — signal_spec, signal_refs, and signal_ids MUST NOT be provided and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries when present. Sellers receiving requests from pre-v3.1 clients without discovery_mode MUST default to 'brief'. Timing semantics: 'wholesale' is a wholesale signals feed read — agents SHOULD respond synchronously and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field, not via a task-handoff envelope. Agents that do not implement wholesale enumeration MAY return INVALID_REQUEST for wholesale calls; callers SHOULD probe via get_adcp_capabilities (signals.discovery_modes) first." ), ] = DiscoveryMode.brief account: Annotated[ account_ref.AccountReference | None, Field( description="Account for this request. When provided, the signals agent returns per-account pricing options if configured. In 'wholesale' mode, this is the rate-card scope: when omitted in wholesale mode, agents return their default rate-card pricing or omit pricing_options entirely." ), ] = None signal_spec: Annotated[ str | None, Field( description="Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_refs, provides context for the agent but signal_ref matches are returned first. MUST NOT be provided when discovery_mode is 'wholesale'." ), ] = None signal_refs: Annotated[ list[signal_ref_1.SignalRef] | None, Field( description="Specific signals to look up by reference. Returns exact matches for the requested SignalRef values. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments. MUST NOT be provided when discovery_mode is 'wholesale'.", min_length=1, ), ] = None signal_ids: Annotated[ list[signal_id_1.SignalId] | None, Field( deprecated=True, description="DEPRECATED. Use signal_refs instead. Legacy exact lookup field using SignalId objects. MUST NOT be provided when discovery_mode is 'wholesale'.", min_length=1, ), ] = None destinations: Annotated[ list[destination.Destination] | None, Field( description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', min_length=1, ), ] = None countries: Annotated[ list[Country] | None, Field( description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.', min_length=1, ), ] = None filters: signal_filters.SignalFilters | None = None fields: Annotated[ list[Field1] | None, Field( description="Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, data_subject_rights, and last_updated when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, small custom-signal result sets, and private/source-native signals when available. fields is a projection request, not an entitlement grant; agents MAY redact requested definition fields unless the caller is authorized for the underlying lineage, methodology, and rights-routing metadata. When consent_basis or art9_basis is projected for another provider's signal, the value remains provider-declared signal-definition posture; sellers and federating agents MUST NOT substitute their own processing basis. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as resolved URL plus catalog_etag, HTTP ETag/Last-Modified, or taxonomy.etag.", min_length=1, ), ] = None max_results: Annotated[ int | None, Field( deprecated=True, description='DEPRECATED: Use pagination.max_results instead. When both fields are present, agents MUST honor pagination.max_results. When only this field is present without a pagination envelope, agents SHOULD treat it as the page size subject to a maximum of 100 results. This field will be removed in AdCP 4.0.', ge=1, ), ] = None pagination: Annotated[ pagination_request.PaginationRequest | None, Field( description='Pagination parameters. Use pagination.max_results (max: 100, default: 50) and pagination.cursor for cursor-based page walks. When the deprecated top-level max_results field is also present, pagination.max_results takes precedence.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async terminal completion/failure notifications on semantic signal discovery. Meaningful only for `discovery_mode: "brief"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: agents MUST NOT route `discovery_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' ), ] = None if_wholesale_feed_version: Annotated[ str | None, Field( description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_signals response from this agent. Only valid when discovery_mode is wholesale. When provided, the agent compares against its current wholesale signals feed version for the caller's cache_scope and MAY return an unchanged: true response (with signals omitted) if nothing has changed. The token is scope-keyed: callers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full sync pattern." ), ] = None if_pricing_version: Annotated[ str | None, Field( description="Opaque pricing_version token from a prior get_signals response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → agent returns the full payload; (2) if_wholesale_feed_version matches but if_pricing_version mismatches → agent returns the full payload so the caller sees updated pricing_options; (3) both match → agent MAY return unchanged: true. Agents that don't track pricing separately ignore this and fall back to if_wholesale_feed_version semantics." ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar countries : list[adcp.types.generated_poc.signals.get_signals_request.Country] | Nonevar destinations : list[adcp.types.generated_poc.core.destination.Destination] | Nonevar discovery_mode : adcp.types.generated_poc.signals.get_signals_request.DiscoveryMode | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.signals.get_signals_request.Field1] | Nonevar filters : adcp.types.generated_poc.core.signal_filters.SignalFilters | Nonevar if_pricing_version : str | Nonevar if_wholesale_feed_version : str | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar signal_refs : list[adcp.types.generated_poc.core.signal_ref.SignalRef] | Nonevar signal_spec : str | None
Instance variables
var max_results : int | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
var signal_ids : list[adcp.types.generated_poc.core.signal_id.SignalId] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
class GetSignalsLookupRequest (**data: Any)-
Expand source code
class GetSignalsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) discovery_mode: Annotated[ DiscoveryMode | None, Field( description="Declares caller intent for this request. 'brief' (default): semantic discovery — signal_spec, signal_refs, or legacy signal_ids is required and the agent performs inference/RAG. 'wholesale': raw wholesale signals feed enumeration — signal_spec, signal_refs, and signal_ids MUST NOT be provided and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries when present. Sellers receiving requests from pre-v3.1 clients without discovery_mode MUST default to 'brief'. Timing semantics: 'wholesale' is a wholesale signals feed read — agents SHOULD respond synchronously and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field, not via a task-handoff envelope. Agents that do not implement wholesale enumeration MAY return INVALID_REQUEST for wholesale calls; callers SHOULD probe via get_adcp_capabilities (signals.discovery_modes) first." ), ] = DiscoveryMode.brief account: Annotated[ account_ref.AccountReference | None, Field( description="Account for this request. When provided, the signals agent returns per-account pricing options if configured. In 'wholesale' mode, this is the rate-card scope: when omitted in wholesale mode, agents return their default rate-card pricing or omit pricing_options entirely." ), ] = None signal_spec: Annotated[ str | None, Field( description="Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_refs, provides context for the agent but signal_ref matches are returned first. MUST NOT be provided when discovery_mode is 'wholesale'." ), ] = None signal_refs: Annotated[ list[signal_ref_1.SignalRef] | None, Field( description="Specific signals to look up by reference. Returns exact matches for the requested SignalRef values. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments. MUST NOT be provided when discovery_mode is 'wholesale'.", min_length=1, ), ] = None signal_ids: Annotated[ list[signal_id_1.SignalId] | None, Field( deprecated=True, description="DEPRECATED. Use signal_refs instead. Legacy exact lookup field using SignalId objects. MUST NOT be provided when discovery_mode is 'wholesale'.", min_length=1, ), ] = None destinations: Annotated[ list[destination.Destination] | None, Field( description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', min_length=1, ), ] = None countries: Annotated[ list[Country] | None, Field( description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.', min_length=1, ), ] = None filters: signal_filters.SignalFilters | None = None fields: Annotated[ list[Field1] | None, Field( description="Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, data_subject_rights, and last_updated when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, small custom-signal result sets, and private/source-native signals when available. fields is a projection request, not an entitlement grant; agents MAY redact requested definition fields unless the caller is authorized for the underlying lineage, methodology, and rights-routing metadata. When consent_basis or art9_basis is projected for another provider's signal, the value remains provider-declared signal-definition posture; sellers and federating agents MUST NOT substitute their own processing basis. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as resolved URL plus catalog_etag, HTTP ETag/Last-Modified, or taxonomy.etag.", min_length=1, ), ] = None max_results: Annotated[ int | None, Field( deprecated=True, description='DEPRECATED: Use pagination.max_results instead. When both fields are present, agents MUST honor pagination.max_results. When only this field is present without a pagination envelope, agents SHOULD treat it as the page size subject to a maximum of 100 results. This field will be removed in AdCP 4.0.', ge=1, ), ] = None pagination: Annotated[ pagination_request.PaginationRequest | None, Field( description='Pagination parameters. Use pagination.max_results (max: 100, default: 50) and pagination.cursor for cursor-based page walks. When the deprecated top-level max_results field is also present, pagination.max_results takes precedence.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async terminal completion/failure notifications on semantic signal discovery. Meaningful only for `discovery_mode: "brief"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: agents MUST NOT route `discovery_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.' ), ] = None if_wholesale_feed_version: Annotated[ str | None, Field( description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_signals response from this agent. Only valid when discovery_mode is wholesale. When provided, the agent compares against its current wholesale signals feed version for the caller's cache_scope and MAY return an unchanged: true response (with signals omitted) if nothing has changed. The token is scope-keyed: callers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full sync pattern." ), ] = None if_pricing_version: Annotated[ str | None, Field( description="Opaque pricing_version token from a prior get_signals response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → agent returns the full payload; (2) if_wholesale_feed_version matches but if_pricing_version mismatches → agent returns the full payload so the caller sees updated pricing_options; (3) both match → agent MAY return unchanged: true. Agents that don't track pricing separately ignore this and fall back to if_wholesale_feed_version semantics." ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar countries : list[adcp.types.generated_poc.signals.get_signals_request.Country] | Nonevar destinations : list[adcp.types.generated_poc.core.destination.Destination] | Nonevar discovery_mode : adcp.types.generated_poc.signals.get_signals_request.DiscoveryMode | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.signals.get_signals_request.Field1] | Nonevar filters : adcp.types.generated_poc.core.signal_filters.SignalFilters | Nonevar if_pricing_version : str | Nonevar if_wholesale_feed_version : str | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar signal_refs : list[adcp.types.generated_poc.core.signal_ref.SignalRef] | Nonevar signal_spec : str | None
Instance variables
var max_results : int | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
var signal_ids : list[adcp.types.generated_poc.core.signal_id.SignalId] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
Inherited members
class GetSignalsResponse (**data: Any)-
Expand source code
class GetSignalsResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) signals: Annotated[Sequence[Signal] | None, Field(description='Array of matching signals')] = None errors: Annotated[ list[error.Error] | None, Field( description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)' ), ] = None incomplete: Annotated[ list[IncompleteItem] | None, Field( description="Declares what the agent could not finish within the caller's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", min_length=1, ), ] = None wholesale_feed_version: Annotated[ str | None, Field( description="Opaque token representing the version of the wholesale signals feed state used to compose this response. Agents that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so callers can cache and probe later. Callers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A caller caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." ), ] = None pricing_version: Annotated[ str | None, Field( description='Opaque token representing the version of the pricing layer. When the agent supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Agents not separating these MAY omit pricing_version and use wholesale_feed_version for both.' ), ] = None cache_scope: Annotated[ CacheScope | None, Field( description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the agent's published rate card; the caller MAY dedupe under (agent, discovery_mode, filters, destinations, countries) without scoping by account. 'account': this response includes account-specific overrides; the caller MUST cache the version under that tuple plus account_id. When the request did NOT include `account`, the agent MUST return `cache_scope: 'public'`. When the request included `account`, the agent MUST return either 'public' (this account prices off the public rate card — caller dedupes) or 'account' (account-specific overrides exist — caller caches under the account key). Agents MAY return 'public' on an account-scoped request that previously had overrides — callers SHOULD interpret this as a downgrade. Without schema-required cache_scope, an agent silently omitting the field on an account-scoped response would cause callers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs validating strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version`. For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 agents correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." ), ] = CacheScope.public unchanged: Annotated[ Literal[True] | None, Field( description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the agent's current version for the caller's cache_scope, in which case signals[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Callers receiving unchanged: true MUST NOT mutate their local wholesale signals mirror. **One shape per state:** agents MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries signals." ), ] = None pagination: pagination_response.PaginationResponse | None = None sandbox: Annotated[ bool | None, Field(description='When true, this response contains simulated data from sandbox mode.'), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var cache_scope : adcp.types.generated_poc.signals.get_signals_response.CacheScope | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar incomplete : list[adcp.types.generated_poc.signals.get_signals_response.IncompleteItem] | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponse | Nonevar pricing_version : str | Nonevar sandbox : bool | Nonevar signals : collections.abc.Sequence[adcp.types.generated_poc.signals.get_signals_response.Signal] | Nonevar unchanged : Literal[True] | Nonevar wholesale_feed_version : str | None
Inherited members
class GetTaskStatusRequest (**data: Any)-
Expand source code
class GetTaskStatusRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] account: Annotated[ account_ref.AccountReference | None, Field( description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.' ), ] = None include_history: Annotated[ bool | None, Field( description='Include full conversation history for this task (may increase response size)' ), ] = False include_result: Annotated[ bool | None, Field( description="Include the task's result payload when status is completed. Defaults to false for lightweight status-only polls. When true, sellers MUST include result on the response when status is completed." ), ] = False context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar include_history : bool | Nonevar include_result : bool | Nonevar model_configvar task_id : str
Inherited members
class GetTaskStatusResponse (**data: Any)-
Expand source code
class GetTaskStatusResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) task_id: Annotated[str, Field(description='Unique identifier for this task')] task_type: Annotated[task_type_1.TaskType, Field(description='Type of AdCP operation')] protocol: Annotated[ adcp_protocol.AdcpProtocol, Field(description='AdCP protocol this task belongs to') ] status: Annotated[task_status.TaskStatus, Field(description='Current task status')] created_at: Annotated[ AwareDatetime, Field(description='When the task was initially created (ISO 8601)') ] updated_at: Annotated[ AwareDatetime, Field(description='When the task was last updated (ISO 8601)') ] completed_at: Annotated[ AwareDatetime | None, Field( description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' ), ] = None has_webhook: Annotated[ bool | None, Field(description='Whether this task has webhook configuration') ] = None progress: Annotated[ Progress | None, Field(description='Progress information for long-running tasks') ] = None error: Annotated[Error | None, Field(description='Error details for failed tasks')] = None history: Annotated[ list[HistoryItem] | None, Field( description='Complete conversation history for this task (only included if include_history was true in request)' ), ] = None result: Annotated[ async_response_data.AdcpAsyncResponseData | None, Field( description="Task-specific completion payload. Present when status is 'completed' and include_result was true in the request; absent otherwise. For failed tasks, use the error field instead. Uses the same anyOf union as the push-notification webhook result field." ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var completed_at : pydantic.types.AwareDatetime | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar created_at : pydantic.types.AwareDatetimevar error : adcp.types.generated_poc.protocol.get_task_status_response.Error | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar has_webhook : bool | Nonevar history : list[adcp.types.generated_poc.protocol.get_task_status_response.HistoryItem] | Nonevar model_configvar progress : adcp.types.generated_poc.protocol.get_task_status_response.Progress | Nonevar protocol : adcp.types.generated_poc.enums.adcp_protocol.AdcpProtocolvar result : adcp.types.generated_poc.core.async_response_data.AdcpAsyncResponseData | Nonevar status : adcp.types.generated_poc.enums.task_status.TaskStatusvar task_id : strvar task_type : adcp.types.generated_poc.enums.task_type.TaskTypevar updated_at : pydantic.types.AwareDatetime
Inherited members
class Gtin (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class Gtin(RootModel[str]): root: Annotated[str, Field(pattern='^[0-9]{8,14}$')]Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[str]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : str
class IdentityMatchRequest (**data: Any)-
Expand source code
class IdentityMatchRequest(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) field_schema: Annotated[ AnyUrl | None, Field( alias='$schema', description='Optional schema URI for validation. Ignored at runtime.' ), ] = None adcp_version: Annotated[ str | None, Field( description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin. Inlined here (rather than via core/version-envelope.json allOf) so this schema can keep `additionalProperties: false` — the privacy boundary on this endpoint is contract-bearing.', pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', ), ] = None adcp_major_version: Annotated[ int | None, Field( description='DEPRECATED in favor of adcp_version. Removed in 4.0. Inlined alongside adcp_version to preserve strict-mode on this endpoint.', ge=1, le=99, ), ] = None type: Annotated[ Literal['identity_match_request'], Field(description='Message type discriminator for deserialization.'), ] = 'identity_match_request' protocol_version: Annotated[ str | None, Field( description='TMP protocol version. Allows receivers to handle semantic differences across versions.' ), ] = '1.0' request_id: Annotated[ str, Field( description='Unique request identifier. MUST NOT correlate with any context match request_id.' ), ] seller_agent_url: Annotated[ AnyUrl, Field( description="API endpoint URL of the seller agent issuing this request. The buyer's identity-match service uses this to resolve the active package set it has registered for this seller; when `package_ids` is omitted, evaluation occurs against that full set. If `seller_agent_url` does not match any seller for which the buyer has registered active packages, the buyer MUST return an empty `eligible_package_ids` set — it MUST NOT fall back to evaluating against another seller's active set. Compared using the AdCP URL canonicalization rules, not byte-equality — see docs/reference/url-canonicalization. Consistent with `seller_agent.agent_url` on `AvailablePackage` and `agent_url` in `adagents.json`." ), ] identities: Annotated[ list[Identity], Field( description='Identity tokens for the user, each tagged with its type. Publishers SHOULD include every token they have available — the buyer resolves on whichever graph matches. Entry order is not semantically significant; buyers use their own preference order when multiple entries resolve. Duplicate `(uid_type, user_token)` pairs MUST NOT appear; routers MAY reject or dedupe. `maxItems: 3` matches the TMPX plaintext budget (~120 bytes after HPKE overhead fits three 32-byte tokens); exceeding it forces buyer-side truncation.', max_length=3, min_length=1, ), ] consent: Annotated[ Consent | None, Field( description='Privacy consent signals. Buyers in regulated jurisdictions MUST NOT process the user token without consent information.' ), ] = None package_ids: Annotated[ list[str] | None, Field( description="Optional. When omitted, the buyer evaluates eligibility against the full set of active packages it has registered for `seller_agent_url`. When provided, the composition of `package_ids` MUST be statistically independent of the current placement — sending only the page-specific subset would let the buyer correlate Identity Match with Context Match by comparing package sets. Two acceptable modes: (a) **all-active** — include every active package this buyer has at this publisher; (b) **fuzzed** — include a random sample of active packages, optionally padded with synthetic non-existent IDs, drawn from a distribution that does not depend on the current placement. The buyer's silent-drop behavior on unknown IDs (specified below) is what makes synthetic-ID padding safe — they do not affect the response shape and cannot leak registry membership. When both `seller_agent_url` and `package_ids` are present, the buyer evaluates against the intersection of its registered active set and `package_ids`; IDs in `package_ids` that the buyer has not registered for this seller MUST be silently ignored (not surfaced as errors) to avoid leaking registry membership back to the publisher.", min_length=1, ), ] = None country: Annotated[ str | None, Field( description='ISO 3166-1 alpha-2 country code. Routing directive for the TMP Router — used to select the correct regional provider. The router MUST strip this field before forwarding the request to the buyer agent. Not an identity signal.', pattern='^[A-Z]{2}$', ), ] = None sealed_credentials: Annotated[ list[SealedCredential] | None, Field( description='Optional HPKE-sealed credentials addressed to specific audiences — the network-as-RP ("issuer-as-RP"/Mechanism B) carrier. Each payload is opaque to the publisher, who relays it untouched; the inner plaintext is an `attestation` (see identities[].attestation) scoped to the audience\'s relying party. Reuses the TMPX envelope format. Router handling (normative — see docs/trusted-match/specification.mdx): the router forwards each entry only to the provider that owns its `audience_kid` (not broadcast), folds `sealed_credentials` into the per-provider re-signature canonical bytes so an injected/swapped blob breaks the signature, and includes a `sealed_credentials_hash` in the dedup cache key. Receivers decrypt only entries whose `audience_kid` they hold a key for and ignore the rest. Receivers MUST bound count and size to prevent DoS amplification.', max_length=8, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var adcp_major_version : int | Nonevar adcp_version : str | Nonevar consent : adcp.types.generated_poc.tmp.identity_match_request.Consent | Nonevar country : str | Nonevar field_schema : pydantic.networks.AnyUrl | Nonevar identities : list[adcp.types.generated_poc.tmp.identity_match_request.Identity]var model_configvar package_ids : list[str] | Nonevar protocol_version : str | Nonevar request_id : strvar sealed_credentials : list[adcp.types.generated_poc.tmp.identity_match_request.SealedCredential] | Nonevar seller_agent_url : pydantic.networks.AnyUrlvar type : Literal['identity_match_request']
Inherited members
class IdentityMatchResponse (**data: Any)-
Expand source code
class IdentityMatchResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) type: Annotated[ Literal['identity_match_response'], Field(description='Message type discriminator for deserialization.'), ] = 'identity_match_response' request_id: Annotated[ str, Field(description='Echoed request identifier from the identity match request') ] eligible_package_ids: Annotated[ list[str], Field( description='Package IDs the user is eligible for. Packages not listed are ineligible.' ), ] serve_window_sec: Annotated[ int, Field( description="Per-package single-shot fcap window, in seconds. After serving the user one impression on each eligible package within this window, the publisher MUST re-query Identity Match before serving from those packages again. This is NOT a router response cache TTL — it is a buyer-asserted serve throttle. Multi-impression frequency caps are handled separately by the buyer's impression tracker, which writes cap-fire events to the IdentityMatch cap-state store at the boundary regardless of this window. Maximum 300 — longer windows reduce IdentityMatch load but coarsen fcap granularity below what most campaigns require.", ge=1, le=300, ), ] tmpx: Annotated[ str | None, Field( description="HPKE-encrypted exposure token containing the resolved user identity tokens. The publisher substitutes this into creative tracking URLs as {TMPX}. The buyer's impression pixel receives the token at serve time, enabling real-time per-user frequency state updates. Wire format: kid.base64url_nopad(ciphertext) — unpadded base64url per RFC 4648 section 5 (no = characters). Publishers MUST treat this value as opaque pass-through data." ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var eligible_package_ids : list[str]var model_configvar request_id : strvar serve_window_sec : intvar tmpx : str | Nonevar type : Literal['identity_match_response']
Inherited members
class KellerType (*args, **kwds)-
Expand source code
class KellerType(StrEnum): master = 'master' sub_brand = 'sub_brand' endorsed = 'endorsed' independent = 'independent'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var endorsedvar independentvar mastervar sub_brand
class LegacyHmacFallback (options_for: Callable[[Mapping[str, str]], LegacyWebhookHmacOptions | None],
only_when_9421_absent: bool = True)-
Expand source code
@dataclass(frozen=True) class LegacyHmacFallback: """Opt-in policy for accepting HMAC-SHA256 senders during 3.x migration. The default behavior of the receiver is to reject any request that fails 9421 verification. Pass an instance of this class to ``WebhookReceiverConfig`` to accept HMAC-signed webhooks as a fallback. :param options_for: callback that returns a populated :class:`LegacyWebhookHmacOptions` given the incoming request headers. Your implementation resolves the sender (from Bearer, hostname, or legacy shared-secret tag) and returns the secret + sender_identity tuple the verifier needs. Return ``None`` to decline the fallback for this request (rejection follows the 9421-only failure path). :param only_when_9421_absent: when ``True`` (default), HMAC fallback only fires when no 9421 headers are present at all. When a request carries 9421 headers that FAIL verification, it still rejects — preventing a downgrade attack where a MITM strips the 9421 signature and replaces it with a forged HMAC one it knows the secret for. When ``False``, HMAC is tried on any 9421 failure; only set this for testing or known homogenous sender cohorts. """ options_for: Callable[[Mapping[str, str]], LegacyWebhookHmacOptions | None] only_when_9421_absent: bool = True @classmethod def from_shared_secret( cls, *, secret: bytes, sender_identity: str, only_when_9421_absent: bool = True, window_seconds: int = 300, ) -> LegacyHmacFallback: """Convenience constructor for the "one secret, one sender" case. Covers the common 3.x migration setup where the receiver has exactly one publisher on the legacy scheme and binds them to a known ``sender_identity`` (typically a buyer-defined string). For multi- sender or header-derived-identity setups, construct with an ``options_for`` callback directly. """ import time as _time def _options_for(_headers: Mapping[str, str]) -> LegacyWebhookHmacOptions: return LegacyWebhookHmacOptions( secret=secret, sender_identity=sender_identity, now=_time.time(), window_seconds=window_seconds, ) return cls( options_for=_options_for, only_when_9421_absent=only_when_9421_absent, )Opt-in policy for accepting HMAC-SHA256 senders during 3.x migration.
The default behavior of the receiver is to reject any request that fails 9421 verification. Pass an instance of this class to
WebhookReceiverConfigto accept HMAC-signed webhooks as a fallback.:param options_for: callback that returns a populated :class:
LegacyWebhookHmacOptionsgiven the incoming request headers. Your implementation resolves the sender (from Bearer, hostname, or legacy shared-secret tag) and returns the secret + sender_identity tuple the verifier needs. ReturnNoneto decline the fallback for this request (rejection follows the 9421-only failure path). :param only_when_9421_absent: whenTrue(default), HMAC fallback only fires when no 9421 headers are present at all. When a request carries 9421 headers that FAIL verification, it still rejects — preventing a downgrade attack where a MITM strips the 9421 signature and replaces it with a forged HMAC one it knows the secret for. WhenFalse, HMAC is tried on any 9421 failure; only set this for testing or known homogenous sender cohorts.Static methods
-
Convenience constructor for the "one secret, one sender" case.
Covers the common 3.x migration setup where the receiver has exactly one publisher on the legacy scheme and binds them to a known
sender_identity(typically a buyer-defined string). For multi- sender or header-derived-identity setups, construct with anoptions_forcallback directly.
Instance variables
var only_when_9421_absent : boolvar options_for : Callable[[Mapping[str, str]], LegacyWebhookHmacOptions | None]
-
class ListAccountsRequest (**data: Any)-
Expand source code
class ListAccountsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description='Optional exact account filter. Use `account_id` to retrieve one known account from an account_id namespace, or the natural key (`brand` + `operator`, optionally `sandbox`) for buyer-declared account sellers. When present, the seller returns only matching accounts visible to the authenticated caller.' ), ] = None status: Annotated[ Status | None, Field(description='Filter accounts by status. Omit to return accounts in all statuses.'), ] = None pagination: pagination_request.PaginationRequest | None = None sandbox: Annotated[ bool | None, Field( description='Filter by sandbox status. true returns only sandbox accounts, false returns only production accounts. Omit to return all accounts. Primarily used with account-id namespaces where sandbox accounts are pre-existing test accounts on the platform.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar sandbox : bool | Nonevar status : adcp.types.generated_poc.account.list_accounts_request.Status | None
Inherited members
class ListAccountsResponse (**data: Any)-
Expand source code
class ListAccountsResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) accounts: Annotated[ list[account_with_authorization.AccountWithAuthorization], Field( description='Array of accounts accessible to the authenticated agent. Each entry is the full Account object plus an optional `authorization` object describing what the calling agent is permitted to do on that account.' ), ] errors: Annotated[ list[error.Error] | None, Field(description='Task-specific errors and warnings') ] = None pagination: pagination_response.PaginationResponse | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var accounts : list[adcp.types.generated_poc.core.account_with_authorization.AccountWithAuthorization]var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponse | None
Inherited members
class ListContentStandardsSuccessResponse (**data: Any)-
Expand source code
class ListContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
class ListContentStandardsResponse1 (**data: Any)-
Expand source code
class ListContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
class ListContentStandardsErrorResponse (**data: Any)-
Expand source code
class ListContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class ListCreativeFormatsRequest (**data: Any)-
Expand source code
class ListCreativeFormatsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description='Return only these specific format IDs (e.g., from get_products response)', min_length=1, ), ] = None asset_types: Annotated[ list[asset_content_type.AssetContentType] | None, Field( description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. For published-post reference formats, search for 'published_post'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.", min_length=1, ), ] = None max_width: Annotated[ int | None, Field( description='Maximum width in pixels (inclusive). Returns formats where ANY render has width <= this value. For multi-render formats, matches if at least one render fits.' ), ] = None max_height: Annotated[ int | None, Field( description='Maximum height in pixels (inclusive). Returns formats where ANY render has height <= this value. For multi-render formats, matches if at least one render fits.' ), ] = None min_width: Annotated[ int | None, Field( description='Minimum width in pixels (inclusive). Returns formats where ANY render has width >= this value.' ), ] = None min_height: Annotated[ int | None, Field( description='Minimum height in pixels (inclusive). Returns formats where ANY render has height >= this value.' ), ] = None is_responsive: Annotated[ bool | None, Field( description='Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.' ), ] = None name_search: Annotated[ str | None, Field(description='Search for formats by name (case-insensitive partial match)') ] = None publisher_domain: Annotated[ str | None, Field( description='Filter to formats supported by the named publisher. Agents resolve via the three-tier order documented in `docs/creative/canonical-formats.mdx#format-discovery` (publisher\'s hosted adagents.json → AAO community mirror → agent-derived from own products\' format_options). All fetches in the chain MUST follow the same transport contract as `format_schema` (https-only, SSRF guards, ≤5s timeout, 1 MiB cap, no redirects — see `static/schemas/source/core/product-format-declaration.json#format_schema`). Response carries `source: "publisher" | "aao_mirror" | "agent_derived"` so buyers know which tier produced the list. The pattern below is a syntactic floor — NOT an SSRF guard; agents MUST resolve the hostname and reject private/loopback/link-local/metadata IPs before fetching.', pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] = None property_id: Annotated[ property_id_1.PropertyId | None, Field( description="Filter to formats supported on the named property within the publisher's catalog. Resolves to a property in the publisher's `adagents.json` `properties[]`; the agent returns only `formats[]` entries whose `applies_to_property_ids` includes this property (or entries with no scope, which apply to all properties). Typically used in combination with `publisher_domain`." ), ] = None wcag_level: Annotated[ wcag_level_1.WcagLevel | None, Field( description='Filter to formats that meet at least this WCAG conformance level (A < AA < AAA)' ), ] = None disclosure_positions: Annotated[ list[disclosure_position.DisclosurePosition] | None, Field( description="Filter to formats that support all of these disclosure positions. When a format has disclosure_capabilities, match against those positions. Otherwise fall back to supported_disclosure_positions. Use to find formats compatible with a brief's compliance requirements.", min_length=1, ), ] = None disclosure_persistence: Annotated[ list[disclosure_persistence_1.DisclosurePersistence] | None, Field( description='Filter to formats where each requested persistence mode is supported by at least one position in disclosure_capabilities. Different positions may satisfy different modes. Use to find formats compatible with jurisdiction-specific persistence requirements (e.g., continuous for EU AI Act).', min_length=1, ), ] = None output_format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description="Filter to formats whose output_format_ids includes any of these format IDs. Returns formats that can produce these outputs — inspect each result's input_format_ids to see what inputs they accept.", min_length=1, ), ] = None input_format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description="Filter to formats whose input_format_ids includes any of these format IDs. Returns formats that accept these creatives as input — inspect each result's output_format_ids to see what they can produce.", min_length=1, ), ] = None pagination: pagination_request.PaginationRequest | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var asset_types : list[adcp.types.generated_poc.enums.asset_content_type.AssetContentType] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar disclosure_persistence : list[adcp.types.generated_poc.enums.disclosure_persistence.DisclosurePersistence] | Nonevar disclosure_positions : list[adcp.types.generated_poc.enums.disclosure_position.DisclosurePosition] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar input_format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar is_responsive : bool | Nonevar max_height : int | Nonevar max_width : int | Nonevar min_height : int | Nonevar min_width : int | Nonevar model_configvar name_search : str | Nonevar output_format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar property_id : adcp.types.generated_poc.core.property_id.PropertyId | Nonevar publisher_domain : str | Nonevar wcag_level : adcp.types.generated_poc.enums.wcag_level.WcagLevel | None
Inherited members
class ListCreativeFormatsResponse (**data: Any)-
Expand source code
class ListCreativeFormatsResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) formats: Annotated[ list[format.Format], Field( description="Full format definitions for all formats this agent supports. Each format's authoritative source is indicated by its agent_url field." ), ] source: Annotated[ Source | None, Field( description="Which tier of the resolution order produced this `formats[]` list when the request carried a `publisher_domain` filter. `publisher`: agent fetched `<publisher_domain>/.well-known/adagents.json` and returned its `formats[]` directly (publisher-authoritative). `aao_mirror`: publisher's hosted file was 404 / lacked `formats[]`, agent fell back to `https://creative.adcontextprotocol.org/translated/<platform>/adagents.json` (community-curated; lower authority — buyer SHOULD treat as advisory until platform adopts). `agent_derived`: neither tier 1 nor tier 2 returned a catalog, so the agent synthesized `formats[]` from the union of its own products' `format_options[]` for products selling the publisher's inventory (lowest authority — agent's view of what it sells, not the publisher's catalog). When two SDKs query the same agent for the same publisher and the agent-derived tier is in play, results may diverge by product set; buyers SHOULD record `source` for telemetry. When the request did NOT carry `publisher_domain`, this field MAY be omitted." ), ] = 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 pagination: pagination_response.PaginationResponse | None = None sandbox: Annotated[ bool | None, Field(description='When true, this response contains simulated data from sandbox mode.'), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_agents : list[adcp.types.generated_poc.media_buy.list_creative_formats_response.CreativeAgent] | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar formats : list[adcp.types.generated_poc.core.format.Format]var model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponse | Nonevar sandbox : bool | Nonevar source : adcp.types.generated_poc.media_buy.list_creative_formats_response.Source | Nonevar status : adcp.types.generated_poc.enums.task_status.TaskStatus | None
Inherited members
class ListCreativesRequest (**data: Any)-
Expand source code
class ListCreativesRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) filters: creative_filters.CreativeFilters | None = None sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None pagination: pagination_request.PaginationRequest | None = None include_assignments: Annotated[ bool | None, Field(description='Include package assignment information in response') ] = True include_snapshot: Annotated[ bool | None, Field( description='Include a lightweight delivery snapshot per creative (lifetime impressions and last-served date). For detailed performance analytics, use get_creative_delivery.' ), ] = False include_items: Annotated[ bool | None, Field(description='Include items for multi-asset formats like carousels and native ads'), ] = False include_variables: Annotated[ bool | None, Field( description='Include dynamic content variable definitions (DCO slots) for each creative' ), ] = False include_pricing: Annotated[ bool | None, Field( description='Include pricing_options on each creative. Requires account to be provided. When false or omitted, pricing is not computed.' ), ] = False include_purged: Annotated[ bool | None, Field( description="Include soft-purged creative tombstones in the result set. When true, creatives destroyed via `creative.purged` with `purge_kind: soft` surface as tombstone records carrying `purged: true`, `purged_at`, and the purge reason — within the seller's webhook activity retention window (30 days from `purged_at`, MUST match `webhook-activity-record` retention). Hard-purged creatives MUST NOT appear regardless of this flag. When false or omitted, the result set excludes all purged creatives — same default as today." ), ] = False include_webhook_activity: Annotated[ bool | None, Field( description='Include recent webhook activity per creative. When true, each returned creative carries a `webhook_activity[]` array of the most recent fires scoped to that creative — `creative.status_changed` and `creative.purged` deliveries. Adoption of the `webhook_activity[]` pattern per `snapshot-and-log.mdx § Webhook activity log pattern`. Retention is 30 days from `completed_at` (MUST). Three-state presence applies: omitted = seller does not surface; `[]` = persists but no recent fires; non-empty = actual records.' ), ] = False webhook_activity_limit: Annotated[ int | None, Field( description="Maximum number of `webhook_activity[]` records to return per creative. Only meaningful when `include_webhook_activity: true`. Sellers MUST respect the cap; structural enforcement is provided by the response schema's `maxItems: 200` on the array.", ge=1, le=200, ), ] = 50 account: Annotated[ account_ref.AccountReference | None, Field( description="Account reference for pricing and access. When provided with include_pricing, the agent returns pricing_options from this account's rate card on each creative." ), ] = None fields: Annotated[ list[Field1] | None, Field( description="Specific fields to include in response (omit for all fields). The 'concept' value returns both concept_id and concept_name.", min_length=1, ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fields : list[adcp.types.generated_poc.creative.list_creatives_request.Field1] | Nonevar filters : adcp.types.generated_poc.core.creative_filters.CreativeFilters | Nonevar include_assignments : bool | Nonevar include_items : bool | Nonevar include_pricing : bool | Nonevar include_purged : bool | Nonevar include_snapshot : bool | Nonevar include_variables : bool | Nonevar include_webhook_activity : bool | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar sort : adcp.types.generated_poc.creative.list_creatives_request.Sort | Nonevar webhook_activity_limit : int | None
Inherited members
class ListCreativesResponse (**data: Any)-
Expand source code
class ListCreativesResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) query_summary: Annotated[ QuerySummary, Field(description='Summary of the query that was executed') ] pagination: pagination_response.PaginationResponse creatives: Annotated[ Sequence[Creative], Field(description='Array of creative assets matching the query') ] format_summary: Annotated[ dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z0-9_-]+$')], int] | None, Field( description="Breakdown of creatives by format. Keys are agent-defined format identifiers, optionally including dimensions (e.g., 'display_static_300x250', 'video_30s_vast'). Key construction is platform-specific — there is no required format." ), ] = None status_summary: Annotated[ StatusSummary | None, Field(description='Breakdown of creatives by status') ] = None errors: Annotated[ list[error.Error] | None, Field(description='Task-specific errors (e.g., invalid filters, account not found)'), ] = None sandbox: Annotated[ bool | None, Field(description='When true, this response contains simulated data from sandbox mode.'), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creatives : Sequence[adcp.types.generated_poc.creative.list_creatives_response.Creative]var errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_summary : dict[str, int] | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponsevar query_summary : adcp.types.generated_poc.creative.list_creatives_response.QuerySummaryvar sandbox : bool | Nonevar status : adcp.types.generated_poc.enums.task_status.TaskStatus | Nonevar status_summary : adcp.types.generated_poc.creative.list_creatives_response.StatusSummary | None
Inherited members
class ListTasksRequest (**data: Any)-
Expand source code
class ListTasksRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account." ), ] = None filters: Annotated[Filters | None, Field(description='Filter criteria for querying tasks')] = ( None ) sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None pagination: pagination_request.PaginationRequest | None = None include_history: Annotated[ bool | None, Field( description='Include full conversation history for each task (may significantly increase response size)' ), ] = False context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar filters : adcp.types.generated_poc.protocol.list_tasks_request.Filters | Nonevar include_history : bool | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar sort : adcp.types.generated_poc.protocol.list_tasks_request.Sort | None
Inherited members
class ListTasksResponse (**data: Any)-
Expand source code
class ListTasksResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) query_summary: Annotated[ QuerySummary, Field(description='Summary of the query that was executed') ] tasks: Annotated[list[Task], Field(description='Array of tasks matching the query criteria')] pagination: pagination_response.PaginationResponse context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponsevar query_summary : adcp.types.generated_poc.protocol.list_tasks_response.QuerySummaryvar tasks : list[adcp.types.generated_poc.protocol.list_tasks_response.Task]
Inherited members
class ListTransformersRequest (**data: Any)-
Expand source code
class ListTransformersRequestCreativeAgent(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) transformer_ids: Annotated[ list[str] | None, Field(description='Return only these specific transformer IDs.', min_length=1), ] = None input_format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description='Filter to transformers that accept any of these formats as input.', min_length=1, ), ] = None output_format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description='Filter to transformers that can produce any of these output formats.', min_length=1, ), ] = None name_search: Annotated[ str | None, Field(description='Search transformers by name (case-insensitive partial match).'), ] = None brief: Annotated[ str | None, Field( description="Natural-language brief used to rank and filter transformers (and their enumerable option values when expanded) — e.g. 'warm female Spanish-language voiceover'. Curates to intent rather than returning the full set, the way get_products curates inventory." ), ] = None expand_params: Annotated[ list[str] | None, Field( description="Param `field` names for which to return the FIRST page of account-scoped option VALUES inline on each transformer's `params[].options[]`. Omit to return param descriptors without enumerated values (the lean default). When a param's options are truncated, its `params[].options_cursor` is set — fetch the next page via `expand_pagination` (below).", min_length=1, ), ] = None expand_pagination: Annotated[ list[ExpandPaginationItem] | None, Field( description="Fetch the NEXT page of a specific param's account-scoped options, using the `options_cursor` a prior response returned for that `(transformer, param)`. Scoped per `(transformer_id, field)` so multiple params can be paged independently. Use this instead of `expand_params` once you hold a cursor.", min_length=1, ), ] = None include_pricing: Annotated[ bool | None, Field(description='Include `pricing_options` on each transformer. Requires `account`.'), ] = False account: Annotated[ account_ref.AccountReference | None, Field( description='Account reference. Transformers are account-scoped — the returned set, the enumerable option values, and (with include_pricing) the rate card are all resolved for this credential.' ), ] = None pagination: pagination_request.PaginationRequest | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar brief : str | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar expand_pagination : list[adcp.types.generated_poc.creative.list_transformers_request.ExpandPaginationItem] | Nonevar expand_params : list[str] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar include_pricing : bool | Nonevar input_format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar model_configvar name_search : str | Nonevar output_format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar pagination : adcp.types.generated_poc.core.pagination_request.PaginationRequest | Nonevar transformer_ids : list[str] | None
Inherited members
class ListTransformersResponse (**data: Any)-
Expand source code
class ListTransformersResponseCreativeAgent(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) transformers: Annotated[ list[transformer.Transformer], Field(description='Transformer descriptors matching the query.'), ] errors: Annotated[ list[error.Error] | None, Field(description='Task-specific errors and warnings.') ] = None pagination: pagination_response.PaginationResponse | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar pagination : adcp.types.generated_poc.core.pagination_response.PaginationResponse | Nonevar transformers : list[adcp.types.generated_poc.core.transformer.Transformer]
Inherited members
class LogEventRequest (**data: Any)-
Expand source code
class LogEventRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) event_source_id: Annotated[ str, Field(description='Event source configured on the account via sync_event_sources') ] test_event_code: Annotated[ str | None, Field( description="Test event code for validation without affecting production data. Events with this code appear in the platform's test events UI." ), ] = None events: Annotated[ list[event.Event], Field(description='Events to log', max_length=10000, min_length=1) ] idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. Prevents duplicate event logging on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar event_source_id : strvar events : list[adcp.types.generated_poc.core.event.Event]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_configvar test_event_code : str | None
Inherited members
class LogEventSuccessResponse (**data: Any)-
Expand source code
class LogEventResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') events_received: Annotated[int, Field(ge=0)] events_processed: Annotated[int, Field(ge=0)] partial_failures: list[PartialFailure] | None = None warnings: list[str] | None = None match_quality: Annotated[float, Field(ge=0, le=1)] | None = None sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar events_processed : intvar events_received : intvar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar match_quality : float | Nonevar model_configvar partial_failures : list[adcp.types.generated_poc.media_buy.log_event_response.PartialFailure] | Nonevar sandbox : bool | Nonevar warnings : list[str] | None
class LogEventResponse1 (**data: Any)-
Expand source code
class LogEventResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') events_received: Annotated[int, Field(ge=0)] events_processed: Annotated[int, Field(ge=0)] partial_failures: list[PartialFailure] | None = None warnings: list[str] | None = None match_quality: Annotated[float, Field(ge=0, le=1)] | None = None sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar events_processed : intvar events_received : intvar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar match_quality : float | Nonevar model_configvar partial_failures : list[adcp.types.generated_poc.media_buy.log_event_response.PartialFailure] | Nonevar sandbox : bool | Nonevar warnings : list[str] | None
Inherited members
class LogEventErrorResponse (**data: Any)-
Expand source code
class LogEventResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class McpWebhookPayload (**data: Any)-
Expand source code
class McpWebhookPayload(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Sender-generated key stable across retries of the same webhook event. Publishers MUST generate a cryptographically random value (UUID v4 recommended) per distinct event and reuse the same key on every retry of that event. Receivers MUST dedupe by this key, scoped to the authenticated sender identity (HMAC secret or Bearer credential) — keys from different publishers are independent. This is the canonical dedup field — the (task_id, status, timestamp) tuple is insufficient when a single transition is retried with unchanged timestamp or when two transitions share a timestamp.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] notification_id: Annotated[ str | None, Field( description="Event-layer, per-state-event identifier. Stable across re-emissions of the same logical event — distinct from the per-fire `idempotency_key` issued at the transport layer. Receivers MUST track both: `idempotency_key` suppresses transport retries; `notification_id` correlates fires to current snapshot state. Seeing the same `notification_id` under two different `idempotency_key` values is a re-emission signal (e.g., the seller is re-firing because a prior fire was unreachable), not a transport retry — receivers SHOULD treat that as a missed-events warning rather than collapsing it. Population is event-shape-dependent (see notification-type.json enumDescriptions for per-type values): for state-shaped events (e.g., `impairment`), this equals the resource's stable id (e.g., `impairment_id`); for point-in-time data events with no persistent state id (e.g., `scheduled`/`final`/`delayed`/`adjusted` delivery report fires per snapshot-and-log Rule 1), this field is absent — the per-fire `idempotency_key` is all there is. Future notification types declare their per-type population in notification-type.json enumDescriptions. Charset is constrained to `[A-Za-z0-9_.:-]` — the same safe-to-log/safe-to-concat character class as `idempotency_key` — so receivers can write this value into log lines, dashboard URLs, and LLM prompts without escaping.", max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$', ), ] = None operation_id: Annotated[ str | None, Field( description='Client-generated correlation identifier for the operation that produced this webhook. Buyers supply this value at webhook registration time via `push_notification_config.operation_id`; sellers MUST echo it verbatim in every webhook payload. Sellers MUST NOT derive `operation_id` by parsing `push_notification_config.url` — the URL is opaque to the seller. Receivers MAY dispatch endpoints by URL path or query string, but MUST correlate the operation using this payload field, not URL-derived values. See [Webhooks — Operation IDs and URL templates](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates) for the full normative wire contract.' ), ] = None task_id: Annotated[ str, Field( description='Unique identifier for this task. Use this to correlate webhook notifications with the original task submission.' ), ] task_type: Annotated[ task_type_1.TaskType, Field( description='Type of AdCP operation that triggered this webhook. Enables webhook handlers to route to appropriate processing logic.' ), ] protocol: Annotated[ adcp_protocol.AdcpProtocol | None, Field( description='AdCP protocol this task belongs to. Helps classify the operation type at a high level.' ), ] = None status: Annotated[ task_status.TaskStatus, Field( description='Current task status. Webhooks are triggered for status changes after initial submission.' ), ] timestamp: Annotated[ AwareDatetime, Field(description='ISO 8601 timestamp when this webhook was generated.') ] message: Annotated[ str | None, Field( description='Human-readable summary of the current task state. Provides context about what happened and what action may be needed.' ), ] = None context_id: Annotated[ str | None, Field( description='Session/conversation identifier. Use this to continue the conversation if input-required status needs clarification or additional parameters.' ), ] = None token: Annotated[ str | None, Field( description='Authentication token echoed verbatim from [`PushNotificationConfig.token`](/schemas/core/push-notification-config.json). Receivers that configured a token MUST compare it to this value to validate request authenticity, and SHOULD use a constant-time equality check to mitigate timing attacks. Absent when no token was configured at registration. Length bounds mirror the config-side field — receivers MAY reject payloads whose token length falls outside the configured range as a defensive check, provided the length check is performed only after the configured token is known to exist for this subscription, and the length comparison is not used as a fast-path to short-circuit the constant-time compare on equal-length inputs. Receivers MUST NOT treat absence as an authenticity failure when no token was configured.', max_length=4096, min_length=16, ), ] = 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.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context_id : str | Nonevar idempotency_key : strvar message : str | Nonevar model_configvar notification_id : str | Nonevar operation_id : str | Nonevar protocol : adcp.types.generated_poc.enums.adcp_protocol.AdcpProtocol | Nonevar result : adcp.types.generated_poc.core.async_response_data.AdcpAsyncResponseData | Nonevar status : adcp.types.generated_poc.enums.task_status.TaskStatusvar task_id : strvar task_type : adcp.types.generated_poc.enums.task_type.TaskTypevar timestamp : pydantic.types.AwareDatetimevar token : str | None
Inherited members
class MediaBuy (**data: Any)-
Expand source code
class MediaBuy(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) media_buy_id: Annotated[str, Field(description="Seller's unique identifier for the media buy")] account: Annotated[ account_1.Account | None, Field(description='Account billed for this media buy') ] = None status: media_buy_status.MediaBuyStatus health: Annotated[ media_buy_health.MediaBuyHealth | None, Field( description="Aggregate health based on open impairments[]. Orthogonal to status — a paused, pending, or active buy can each be impaired. Defaults to 'ok' when impairments[] is empty." ), ] = media_buy_health.MediaBuyHealth.ok impairments: Annotated[ list[impairment.Impairment] | None, Field( description="Open impairments — upstream dependency state changes that affect delivery for at least one package on this buy. Empty when health is 'ok'. Sellers MUST add an entry on next sync/poll response after a referenced resource transitions to an offline state, and MUST remove the entry (flipping health to 'ok' when the array empties) when the resource returns to a serviceable state. Staleness budget: the snapshot MUST reflect the impairment within 5 minutes of impairment.observed_at regardless of buyer poll cadence — sellers cannot rely on rare buyer polls to defer write propagation. See impairment.coherence assertion for the cross-resource invariant." ), ] = None rejection_reason: Annotated[ str | None, Field( description="Reason provided by the seller when status is 'rejected'. Present only when status is 'rejected'." ), ] = None confirmed_at: Annotated[ AwareDatetime | None, Field( description='ISO 8601 timestamp when the seller committed to this media buy. May be null until seller commitment occurs in deferred/manual approval flows. Once populated, remains stable through later pause, resume, activation, completion, cancellation, and reporting transitions.' ), ] cancellation: Annotated[ Cancellation | None, Field(description="Cancellation metadata. Present only when status is 'canceled'."), ] = None total_budget: Annotated[float, Field(description='Total budget amount', ge=0.0)] packages: Annotated[ list[package.Package], Field(description='Array of packages within this media buy') ] context: Annotated[ context_1.ContextObject | None, Field( description='Opaque media-buy-level correlation data echoed unchanged from the create_media_buy request. Sellers MUST include persisted context on read surfaces such as get_media_buys when the media buy was created through AdCP with context, so buyers can reconcile seller-assigned media_buy_id values with their own tracking state. Sellers MAY omit context for media buys created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.' ), ] = None invoice_recipient: Annotated[ business_entity.BusinessEntity | None, Field( description="Per-buy override for who receives the invoice. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." ), ] = None creative_deadline: Annotated[ AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline') ] = None revision: Annotated[ int, Field( description='Monotonically increasing optimistic concurrency token. Incremented on every mutating state change or update; reads, validation-only calls, and exact idempotency replays do not increment it. Callers SHOULD include this in update_media_buy requests intended to change state — when provided, sellers MUST reject with CONFLICT if the revision does not match the current value, and MUST enforce that comparison atomically with the write.', ge=1, ), ] created_at: Annotated[AwareDatetime | None, Field(description='Creation timestamp')] = None updated_at: Annotated[AwareDatetime | None, Field(description='Last update timestamp')] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account.Account | Nonevar cancellation : adcp.types.generated_poc.core.media_buy.Cancellation | Nonevar confirmed_at : pydantic.types.AwareDatetime | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar created_at : pydantic.types.AwareDatetime | Nonevar creative_deadline : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar health : adcp.types.generated_poc.enums.media_buy_health.MediaBuyHealth | Nonevar impairments : list[adcp.types.generated_poc.core.impairment.Impairment] | Nonevar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar media_buy_id : strvar model_configvar packages : list[adcp.types.generated_poc.core.package.Package]var rejection_reason : str | Nonevar revision : intvar status : adcp.types.generated_poc.enums.media_buy_status.MediaBuyStatusvar total_budget : floatvar updated_at : pydantic.types.AwareDatetime | None
Inherited members
class MediaBuyDeliveryWebhookResult (**data: Any)-
Expand source code
class MediaBuyDeliveryWebhookResult(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) notification_type: Annotated[ NotificationType, Field( description='Type of delivery-report notification: scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = corrected data for the same window, window_update = a wider measurement window supersedes a prior window.' ), ] partial_data: Annotated[ bool | None, Field( description='Indicates if any media buys in this webhook have missing or delayed data.' ), ] = None unavailable_count: Annotated[ int | None, Field( description='Number of media buys with reporting_delayed or failed status when partial_data is true.', ge=0, ), ] = None sequence_number: Annotated[ int | None, Field( description='Sequential notification number for this reporting webhook stream.', ge=1 ), ] = None next_expected_at: Annotated[ AwareDatetime | None, Field( description='ISO 8601 timestamp for the next expected notification. Omitted on final notifications.' ), ] = None reporting_period: Annotated[ ReportingPeriod, Field(description='UTC date range covered by the delivery report.') ] currency: Annotated[str, Field(description='ISO 4217 currency code.', pattern='^[A-Z]{3}$')] attribution_window: Annotated[ attribution_window_1.AttributionWindow | None, Field( description='Attribution methodology and lookback windows used for conversion metrics in this report.' ), ] = None media_buy_deliveries: Annotated[ list[MediaBuyDelivery], Field( description='Delivery rows for one or more media buys included in this notification.' ), ] errors: Annotated[ list[error.Error] | None, Field(description='Task-specific delivery errors or warnings.') ] = None sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var attribution_window : adcp.types.generated_poc.core.attribution_window.AttributionWindow | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar currency : strvar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar media_buy_deliveries : list[adcp.types.generated_poc.media_buy.media_buy_delivery_webhook_result.MediaBuyDelivery]var model_configvar next_expected_at : pydantic.types.AwareDatetime | Nonevar notification_type : adcp.types.generated_poc.media_buy.media_buy_delivery_webhook_result.NotificationTypevar partial_data : bool | Nonevar reporting_period : adcp.types.generated_poc.media_buy.media_buy_delivery_webhook_result.ReportingPeriodvar sandbox : bool | Nonevar sequence_number : int | None
Inherited members
class MediaBuyStatus (*args, **kwds)-
Expand source code
class MediaBuyStatus(StrEnum): pending_creatives = 'pending_creatives' pending_start = 'pending_start' active = 'active' paused = 'paused' completed = 'completed' rejected = 'rejected' canceled = 'canceled'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var activevar canceledvar completedvar pausedvar pending_creativesvar pending_startvar rejected
class MediaChannel (*args, **kwds)-
Expand source code
class MediaChannel(StrEnum): display = 'display' olv = 'olv' social = 'social' search = 'search' ctv = 'ctv' linear_tv = 'linear_tv' radio = 'radio' streaming_audio = 'streaming_audio' podcast = 'podcast' dooh = 'dooh' ooh = 'ooh' print = 'print' cinema = 'cinema' email = 'email' gaming = 'gaming' retail_media = 'retail_media' influencer = 'influencer' affiliate = 'affiliate' product_placement = 'product_placement' sponsored_intelligence = 'sponsored_intelligence'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var affiliatevar cinemavar ctvvar displayvar doohvar emailvar gamingvar influencervar linear_tvvar olvvar oohvar podcastvar printvar product_placementvar radiovar retail_mediavar searchvar sponsored_intelligencevar streaming_audio
class Member (**data: Any)-
Expand source code
class Member(BaseModel): """An organization registered in the AAO member directory.""" model_config = ConfigDict(extra="allow") id: str slug: str display_name: str description: str | None = None tagline: str | None = None logo_url: str | None = None logo_light_url: str | None = None logo_dark_url: str | None = None contact_email: str | None = None contact_website: str | None = None offerings: list[str] = Field(default_factory=list) markets: list[str] = Field(default_factory=list) agents: list[dict[str, Any]] = Field(default_factory=list) brands: list[dict[str, Any]] = Field(default_factory=list) is_public: bool = True is_founding_member: bool = False featured: bool = False si_enabled: bool = FalseAn organization registered in the AAO member directory.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var agents : list[dict[str, typing.Any]]var brands : list[dict[str, typing.Any]]var contact_email : str | Nonevar contact_website : str | Nonevar description : str | Nonevar display_name : strvar featured : boolvar id : strvar is_founding_member : boolvar is_public : boolvar logo_dark_url : str | Nonevar logo_light_url : str | Nonevar logo_url : str | Nonevar markets : list[str]var model_configvar offerings : list[str]var si_enabled : boolvar slug : strvar tagline : str | None
class MemoryBackend (*, clock: Callable[[], float] = <built-in function time>)-
Expand source code
class MemoryBackend(IdempotencyBackend): """In-process dict-backed store. Suitable for tests, single-process reference implementations, and local development. **Not suitable for multi-process deployments** — each worker has its own cache, so a retry that lands on a different worker is treated as a fresh request. Thread safety: the backend uses an :class:`asyncio.Lock` to serialize mutations of the shared dict. Reads go through the lock too; for a pure in-process backend this is cheap and prevents torn reads across concurrent ``get``/``put`` interleaving. :param clock: Callable returning the current epoch seconds. Override for tests that need to advance time deterministically without monkeypatching :mod:`time`. Defaults to :func:`time.time`. """ def __init__(self, *, clock: Callable[[], float] = time.time) -> None: self._store: dict[tuple[str, str], CachedResponse] = {} self._lock = asyncio.Lock() self._clock = clock async def get(self, scope_key: str, key: str) -> CachedResponse | None: async with self._lock: entry = self._store.get((scope_key, key)) if entry is None: return None if entry.expires_at_epoch <= self._clock(): # Lazy expiry — drop the stale entry so the next request # treats the slot as fresh and races to repopulate. del self._store[(scope_key, key)] return None return entry async def put( self, scope_key: str, key: str, entry: CachedResponse, ) -> None: async with self._lock: self._store[(scope_key, key)] = entry async def delete_expired(self, now_epoch: float | None = None) -> int: cutoff = now_epoch if now_epoch is not None else self._clock() async with self._lock: stale = [k for k, v in self._store.items() if v.expires_at_epoch <= cutoff] for k in stale: del self._store[k] return len(stale) async def clear(self) -> None: """Remove all cached entries. Test-suite hook — handy for resetting state between fixtures when a single :class:`MemoryBackend` is shared across multiple tests. """ async with self._lock: self._store.clear() async def _size(self) -> int: """Test-only: return the current entry count.""" async with self._lock: return len(self._store)In-process dict-backed store.
Suitable for tests, single-process reference implementations, and local development. Not suitable for multi-process deployments — each worker has its own cache, so a retry that lands on a different worker is treated as a fresh request.
Thread safety: the backend uses an :class:
asyncio.Lockto serialize mutations of the shared dict. Reads go through the lock too; for a pure in-process backend this is cheap and prevents torn reads across concurrentget/putinterleaving.:param clock: Callable returning the current epoch seconds. Override for tests that need to advance time deterministically without monkeypatching :mod:
time. Defaults to :func:time.time.Ancestors
- IdempotencyBackend
- abc.ABC
Methods
async def clear(self) ‑> None-
Expand source code
async def clear(self) -> None: """Remove all cached entries. Test-suite hook — handy for resetting state between fixtures when a single :class:`MemoryBackend` is shared across multiple tests. """ async with self._lock: self._store.clear()Remove all cached entries.
Test-suite hook — handy for resetting state between fixtures when a single :class:
MemoryBackendis shared across multiple tests.
Inherited members
class NotificationConfig (**data: Any)-
Expand source code
class NotificationConfig(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) subscriber_id: Annotated[ str, Field( description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$', ), ] url: Annotated[ AnyUrl, Field( description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' ), ] event_types: Annotated[ list[notification_type.NotificationType], Field( description='Notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new types are added to `notification-type.json`. When omitted, the seller MUST default to a no-fire policy and surface an `errors[]` entry on `sync_accounts` so the buyer notices the missing filter. Values are drawn from `notification-type.json`, but only types whose contract anchors at the account scope are valid here — creative lifecycle events and wholesale feed change payloads are valid; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `impairment`) and account-lifecycle names not present in the enum (for example, `account.status_changed`) are invalid on this surface; sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', min_length=1, ), ] authentication: Annotated[ Authentication | None, Field( description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads." ), ] = None active: Annotated[ bool | None, Field( description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." ), ] = True ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var active : bool | Nonevar authentication : adcp.types.generated_poc.core.notification_config.Authentication | Nonevar event_types : list[adcp.types.generated_poc.enums.notification_type.NotificationType]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar subscriber_id : strvar url : pydantic.networks.AnyUrl
Inherited members
class OfferingAssetConstraint (**data: Any)-
Expand source code
class OfferingAssetConstraint(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) asset_group_id: Annotated[ str, Field( description="The asset group this constraint applies to. Values are format-defined vocabulary — each format chooses its own group IDs (e.g., 'headlines', 'images', 'videos'). Buyers discover them via list_creative_formats." ), ] asset_type: Annotated[ asset_content_type.AssetContentType, Field(description='The expected content type for this group.'), ] required: Annotated[ bool | None, Field( description='Whether this asset group must be present in each offering. Defaults to true.' ), ] = True min_count: Annotated[ int | None, Field(description='Minimum number of items required in this group.', ge=1) ] = None max_count: Annotated[ int | None, Field(description='Maximum number of items allowed in this group.', ge=1) ] = None asset_requirements: Annotated[ asset_requirements_1.AssetRequirements | None, Field( description='Technical requirements for each item in this group (e.g., max_length for text, min_width/aspect_ratio for images). Applies uniformly to all items in the group.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var asset_group_id : strvar asset_requirements : adcp.types.generated_poc.core.requirements.asset_requirements.AssetRequirements | Nonevar asset_type : adcp.types.generated_poc.enums.asset_content_type.AssetContentTypevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar max_count : int | Nonevar min_count : int | Nonevar model_configvar required : bool | None
Inherited members
class OfferingAssetGroup (**data: Any)-
Expand source code
class OfferingAssetGroup(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) asset_group_id: Annotated[ str, Field( description="Identifies the creative role this group fills. Values are defined by each format's offering_asset_constraints — not protocol constants. Discover them via list_creative_formats (e.g., a format might declare 'headlines', 'images', or 'videos')." ), ] asset_type: Annotated[ asset_content_type.AssetContentType, Field(description='The content type of all items in this group.'), ] items: Annotated[ list[Items], Field( description='The assets in this group. Each item carries an `asset_type` discriminator that selects the matching asset schema. Note: the group-level `asset_type` declares the expected type; individual items must also self-tag so validators can narrow errors. Intentionally excludes `brief-asset` and `catalog-asset` — those are campaign-input metadata types, not delivery-ready creative assets suitable for a pooled offering group. See core/assets/asset-union.json for the full asset-variant union.', min_length=1, ), ] ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var asset_group_id : strvar asset_type : adcp.types.generated_poc.enums.asset_content_type.AssetContentTypevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar items : list[adcp.types.generated_poc.core.offering_asset_group.Items]var model_config
Inherited members
class OptimizationGoal (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class OptimizationGoal(RootModel[OptimizationGoal1 | OptimizationGoal2 | OptimizationGoal3]): root: Annotated[ OptimizationGoal1 | OptimizationGoal2 | OptimizationGoal3, Field( description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', discriminator='kind', title='Optimization Goal', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[OptimizationGoal1, OptimizationGoal2, OptimizationGoal3]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.optimization_goal.OptimizationGoal1 | adcp.types.generated_poc.core.optimization_goal.OptimizationGoal2 | adcp.types.generated_poc.core.optimization_goal.OptimizationGoal3
class Overlay (**data: Any)-
Expand source code
class Overlay(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) id: Annotated[ str, Field( description="Identifier for this overlay (e.g., 'play_pause', 'volume', 'publisher_logo', 'carousel_prev', 'carousel_next')" ), ] description: Annotated[ str | None, Field( description='Human-readable explanation of what this overlay is and how buyers should account for it' ), ] = None visual: Annotated[ Visual | None, Field( description='Optional visual reference for this overlay element. Useful for creative agents compositing previews and for buyers understanding what will appear over their content. Must include at least one of: url, light, or dark.' ), ] = None bounds: Annotated[ Bounds, Field( description="Position and size of the overlay relative to the asset's own top-left corner. See 'unit' for coordinate interpretation." ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var bounds : adcp.types.generated_poc.core.overlay.Boundsvar description : str | Nonevar id : strvar model_configvar visual : adcp.types.generated_poc.core.overlay.Visual | None
Inherited members
class MediaBuyPackage (**data: Any)-
Expand source code
class Package(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) package_id: Annotated[str, Field(description="Seller's package identifier")] product_id: Annotated[ str | None, Field( description="Product identifier this package is purchased from. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." ), ] = None budget: Annotated[ float | None, Field( description='Package budget amount, denominated in package.currency when present, otherwise media_buy.currency', ge=0.0, ), ] = None currency: Annotated[ str | None, Field( description='ISO 4217 currency code for monetary values at this package level (budget, bid_price, snapshot.spend). When absent, inherit media_buy.currency.', pattern='^[A-Z]{3}$', ), ] = None bid_price: Annotated[ float | None, Field( description='Current bid price for auction-based packages. Denominated in package.currency when present, otherwise media_buy.currency. Relevant for automated price optimization loops.', ge=0.0, ), ] = None format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description='Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where another selector won precedence.', min_length=1, ), ] = None format_option_refs: Annotated[ list[format_option_ref.FormatOptionReference] | None, Field( description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it.', min_length=1, ), ] = None format_kind: Annotated[ canonical_format_kind.CanonicalFormatKind | None, Field( description='Direct canonical selector supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including informational-echo cases where another selector won precedence.' ), ] = None params: Annotated[ dict[str, Any] | None, Field( description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`.' ), ] = None impressions: Annotated[ float | None, Field(description='Goal impression count for impression-based packages', ge=0.0), ] = None targeting_overlay: Annotated[ targeting.TargetingOverlay | None, Field( description='Targeting overlay applied to this package, echoed from the most recent create_media_buy or update_media_buy. Sellers SHOULD echo any persisted targeting so buyers can verify what was stored without replaying their own request. Sellers claiming the property-lists or collection-lists specialisms MUST include, within this targeting_overlay, the PropertyListReference / CollectionListReference they persisted.' ), ] = None start_time: Annotated[ AwareDatetime | None, Field( description='ISO 8601 flight start time for this package. Use to determine whether the package is within its scheduled flight before interpreting delivery status.' ), ] = None end_time: Annotated[ AwareDatetime | None, Field(description='ISO 8601 flight end time for this package') ] = None paused: Annotated[ bool | None, Field(description='Whether this package is currently paused by the buyer') ] = None canceled: Annotated[ bool | None, Field( description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated.' ), ] = None cancellation: Annotated[ Cancellation1 | None, Field(description='Cancellation metadata. Present only when canceled is true.'), ] = None creative_deadline: Annotated[ AwareDatetime | None, Field( description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." ), ] = None context: Annotated[ context_1.ContextObject | None, Field( description='Opaque package-level correlation data echoed unchanged from the create_media_buy package request. Sellers MUST include persisted package context on read surfaces when the package was created through AdCP with context, so buyers can reconcile seller-assigned package_id values with their own line items; this is the legacy-safe fallback when an older seller did not echo product_id on the create response. Sellers MAY omit context for packages created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.' ), ] = None creative_approvals: Annotated[ list[CreativeApproval] | None, Field( description='Approval status for each creative assigned to this package. Absent when no creatives have been assigned.' ), ] = None format_ids_pending: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description='Format IDs from the original create_media_buy format_ids_to_provide that have not yet been uploaded via sync_creatives. When empty or absent, all required formats have been provided.' ), ] = None snapshot_unavailable_reason: Annotated[ snapshot_unavailable_reason_1.SnapshotUnavailableReason | None, Field( description='Machine-readable reason the snapshot is omitted. Present only when include_snapshot was true and snapshot is unavailable for this package.' ), ] = None snapshot: Annotated[ Snapshot | None, Field( description='Near-real-time delivery snapshot for this package. Only present when include_snapshot was true in the request. Represents the latest available entity-level stats from the platform — not billing-grade data.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var bid_price : float | Nonevar budget : float | Nonevar canceled : bool | Nonevar cancellation : adcp.types.generated_poc.media_buy.get_media_buys_response.Cancellation1 | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_approvals : list[adcp.types.generated_poc.media_buy.get_media_buys_response.CreativeApproval] | Nonevar creative_deadline : pydantic.types.AwareDatetime | Nonevar currency : str | Nonevar end_time : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar format_ids_pending : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar format_kind : adcp.types.generated_poc.core.canonical_format_kind.CanonicalFormatKind | Nonevar format_option_refs : list[adcp.types.generated_poc.core.format_option_ref.FormatOptionReference] | Nonevar impressions : float | Nonevar model_configvar package_id : strvar params : dict[str, typing.Any] | Nonevar paused : bool | Nonevar product_id : str | Nonevar snapshot : adcp.types.generated_poc.media_buy.get_media_buys_response.Snapshot | Nonevar start_time : pydantic.types.AwareDatetime | Nonevar targeting_overlay : adcp.types.generated_poc.core.targeting.TargetingOverlay | None
class Package (**data: Any)-
Expand source code
class Package(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) package_id: Annotated[str, Field(description="Seller's unique identifier for the package")] product_id: Annotated[ str | None, Field( description="ID of the product this package is based on. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." ), ] = None budget: Annotated[ float | None, Field( description='Budget allocation for this package in the currency specified by the pricing option', ge=0.0, ), ] = None pacing: pacing_1.Pacing | None = None pricing_option_id: Annotated[ str | None, Field( description="ID of the selected pricing option from the product's pricing_options array" ), ] = None bid_price: Annotated[ float | None, Field( description="Bid price for auction-based pricing. This is the exact bid/price to honor unless the selected pricing option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description="Breakdown of the effective price for this package. On fixed-price packages, echoes the pricing option's breakdown. On auction packages, shows the clearing price breakdown including any commission or settlement terms." ), ] = None impressions: Annotated[ float | None, Field(description='Impression goal for this package', ge=0.0) ] = None catalogs: Annotated[ list[catalog.Catalog] | None, Field( description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Echoed from the create_media_buy request.' ), ] = None format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description='Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where `format_option_refs` was the winning selector, so read surfaces preserve the original wire contract. Omitted means the request did not carry legacy format_ids unless the seller cannot reconstruct legacy requests created before this field was persisted.' ), ] = None format_option_refs: Annotated[ list[format_option_ref.FormatOptionReference] | None, Field( description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it. Publisher-catalog-backed options are identified by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options are identified by `{ scope: "product", format_option_id }` and resolve only against this package\'s target product. Omitted means the request did not carry format_option_refs unless the seller cannot reconstruct legacy requests created before this field was persisted.', min_length=1, ), ] = None format_kind: Annotated[ canonical_format_kind.CanonicalFormatKind | None, Field( description='Direct canonical selector supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including informational-echo cases where `format_ids` was the winning selector, so read surfaces preserve the original wire contract.' ), ] = None params: Annotated[ dict[str, Any] | None, Field( description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`; omitted only when the request did not carry direct canonical params or when the seller cannot reconstruct legacy requests created before this field was persisted.' ), ] = None targeting_overlay: targeting.TargetingOverlay | None = None measurement_terms: Annotated[ measurement_terms_1.MeasurementTerms | None, Field( description="Agreed billing measurement and makegood terms for this package. Reflects what was negotiated — may differ from the buyer's proposal or the product's defaults. When present, these terms are binding for the package's duration." ), ] = None performance_standards: Annotated[ list[performance_standard.PerformanceStandard] | None, Field( description='Agreed performance standards for this package. When any entry specifies a vendor, creatives assigned to this package MUST include corresponding tracker_script or tracker_pixel assets from that vendor.', min_length=1, ), ] = None committed_metrics: Annotated[ list[committed_metric.CommittedMetric] | None, Field( description="The binding reporting contract for this package — what the seller has agreed to populate in delivery reports. Each entry carries an explicit `committed_at` timestamp, so the array also serves as the contract amendment ledger: day-1 commitments share `committed_at = create_media_buy.confirmed_at`; mid-flight additions carry their own timestamps. When `create_media_buy.confirmed_at` is null for a provisional buy, sellers MUST omit `committed_metrics` until commitment. The first response that sets `confirmed_at` MAY include the initial committed-metrics set, and each such entry's `committed_at` MUST equal `confirmed_at`. The `missing_metrics` field on `get_media_buy_delivery` reconciles against this list, filtering to entries where `committed_at < reporting_period.end` (a metric committed mid-flight is only audited from its commitment timestamp forward). Sellers stamp the day-1 set on the `create_media_buy` response; mid-flight additions are appended via `update_media_buy` (append-only — sellers MUST reject attempts to modify or remove existing entries with `validation_error`, suggested code: `IMMUTABLE_FIELD`). Optional in v1; absence means the seller does not provide an audit-grade contract and `missing_metrics` falls back to the product's live `available_metrics` (a known audit gap — buyers SHOULD treat absence as 'no audit-grade contract' rather than 'clean delivery'). Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. The unified shape is symmetric with `missing_metrics` and `aggregated_totals.metric_aggregates` — same atomic unit `(scope, metric_id, qualifier)` across contract, diff, and delivery, so reconciliation collapses to a row-level join on the tuple. Replaces the parallel-array design that shipped briefly in #3510.", examples=[ [ { 'scope': 'standard', 'metric_id': 'impressions', 'committed_at': '2026-04-29T10:53:00Z', }, { 'scope': 'standard', 'metric_id': 'spend', 'committed_at': '2026-04-29T10:53:00Z', }, { 'scope': 'standard', 'metric_id': 'completed_views', 'committed_at': '2026-04-29T10:53:00Z', }, { 'scope': 'vendor', 'vendor': {'domain': 'attentionvendor.example'}, 'metric_id': 'attention_units', 'committed_at': '2026-04-29T10:53:00Z', }, { 'scope': 'standard', 'metric_id': 'viewable_rate', 'qualifier': {'viewability_standard': 'mrc'}, 'committed_at': '2026-05-30T14:22:00Z', }, ] ], min_length=1, ), ] = None creative_assignments: Annotated[ list[creative_assignment.CreativeAssignment] | None, Field(description='Creative assets assigned to this package'), ] = None format_ids_to_provide: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field(description='Format IDs that creative assets will be provided for this package'), ] = None optimization_goals: Annotated[ list[optimization_goal.OptimizationGoal] | None, Field( description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', min_length=1, ), ] = None start_time: Annotated[ AwareDatetime | None, Field( description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Sellers SHOULD always include the resolved value in responses, even when inherited." ), ] = None end_time: Annotated[ AwareDatetime | None, Field( description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Sellers SHOULD always include the resolved value in responses, even when inherited." ), ] = None paused: Annotated[ bool | None, Field( description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.' ), ] = False canceled: Annotated[ bool | None, Field( description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.' ), ] = False cancellation: Annotated[ Cancellation | None, Field(description='Cancellation metadata. Present only when canceled is true.'), ] = None agency_estimate_number: Annotated[ str | None, Field( description="Agency estimate or authorization number for this package. Echoed from the buyer's request. When present on the package, takes precedence over the media buy-level estimate number.", max_length=100, ), ] = None creative_deadline: Annotated[ AwareDatetime | None, Field( description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." ), ] = None context: Annotated[ context_1.ContextObject | None, Field( description='Opaque package-level correlation data echoed unchanged in responses, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Sellers MUST preserve this object unchanged and MUST NOT parse it for business logic.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var agency_estimate_number : str | Nonevar bid_price : float | Nonevar budget : float | Nonevar canceled : bool | Nonevar cancellation : adcp.types.generated_poc.core.package.Cancellation | Nonevar catalogs : list[adcp.types.generated_poc.core.catalog.Catalog] | Nonevar committed_metrics : list[adcp.types.generated_poc.core.committed_metric.CommittedMetric] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_assignments : list[adcp.types.generated_poc.core.creative_assignment.CreativeAssignment] | Nonevar creative_deadline : pydantic.types.AwareDatetime | Nonevar end_time : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar format_ids_to_provide : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar format_kind : adcp.types.generated_poc.core.canonical_format_kind.CanonicalFormatKind | Nonevar format_option_refs : list[adcp.types.generated_poc.core.format_option_ref.FormatOptionReference] | Nonevar impressions : float | Nonevar measurement_terms : adcp.types.generated_poc.core.measurement_terms.MeasurementTerms | Nonevar model_configvar optimization_goals : list[adcp.types.generated_poc.core.optimization_goal.OptimizationGoal] | Nonevar pacing : adcp.types.generated_poc.enums.pacing.Pacing | Nonevar package_id : strvar params : dict[str, typing.Any] | Nonevar paused : bool | Nonevar performance_standards : list[adcp.types.generated_poc.core.performance_standard.PerformanceStandard] | Nonevar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar pricing_option_id : str | Nonevar product_id : str | Nonevar start_time : pydantic.types.AwareDatetime | Nonevar targeting_overlay : adcp.types.generated_poc.core.targeting.TargetingOverlay | None
Inherited members
class PackageRequest (**data: Any)-
Expand source code
class PackageRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) product_id: Annotated[ str, Field( description='Product ID for this package. Sellers MUST echo this value on every response package object that represents this requested package.' ), ] format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description="Legacy named-format selector. Array of format IDs that will be used for this package - must be supported by the product. If omitted (and no 3.1+ format-option selector or direct canonical selector is present), defaults to all formats supported by the product.\n\nSellers comparing this selector to a product's `format_options[]` MUST first normalize each legacy `format_id` through the canonical mapping path (`canonical`, `v1_format_ref`, or registry projection). Exact `(agent_url, id)` comparison after projection is insufficient: a legacy fixed-size display ID can satisfy a canonical `image` product declaration with matching `width`/`height`. Product gating remains directional: if the product declares fixed dimensions or duration, the selected format must declare and match those constraints; an under-specified canonical request is not a wildcard for a fixed-size or fixed-duration product. Range constraints use containment, not overlap: a range-based request satisfies the product only when every value it permits falls within the product's accepted range.", min_length=1, ), ] = None format_option_refs: Annotated[ list[format_option_ref.FormatOptionReference] | None, Field( description='3.1+ format-option selector. Array of structured format option references, each matching one of the target product\'s `format_options[]` entries. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. If omitted along with `format_ids` and direct `format_kind`, all product formats are active.\n\n**Resolution rules (normative).**\n- **Both `format_option_refs` and `format_ids` present.** `format_option_refs` wins; the seller routes by structured references and MUST NOT validate `format_ids` for consistency with the resolved declarations. The `format_ids` value is a legacy-compat hint for intermediaries on the wire path; the resolving seller ignores it.\n- **`format_option_refs` only.** Seller looks up each entry against the package\'s target product `format_options[]` and uses the matching declaration (and that declaration\'s `v1_format_ref[]` when projecting to legacy named-format surfaces). This is the 3.1+ format-option authoring path. `scope: "product"` is scoped only by this target product; it is not a seller-wide identifier.\n- **`format_ids` only.** Existing named-format behavior; unchanged.\n- **`format_kind` only.** Direct canonical selector behavior; seller compares `{ format_kind, params }` against the product\'s `format_options[]` declarations using directional product satisfaction.\n- **None of `format_option_refs`, `format_ids`, or `format_kind`.** Default — all formats supported by the product are active.\n\n**Failure modes (normative).** Sellers MUST reject with `UNSUPPORTED_FEATURE` (with `field` pointing at the failing package and entry, e.g. `packages[0].format_option_refs[1]`) when:\n- Any entry references a format option not present in the target product\'s `format_options[]`, OR\n- The target product carries `format_ids` but no `format_options[]` (legacy-format-only product — there is no closed set to resolve against), OR\n- The target product carries `format_options[]` but none of the entries publish selectable `format_option_id` values. Sellers SHOULD set `error.details.reason` to `format_option_refs_not_published` in this case so buyers can distinguish it from an outright mismatch and fall back to `format_ids[]`.\n\n**Seller obligation.** For buyers to use the 3.1+ format-option path against a product, the seller MUST publish a selectable `format_option_id` on each `format_options[]` entry it expects buyers to select; if the option is publisher-catalog backed, include `publisher_domain` on the product declaration and require buyers to use `scope: "publisher"` in `FormatOptionRef`.\n\n**No legacy capability selector.** `capability_ids` was removed before GA; schemas reject it instead of treating it as an extension.\n\n**Dual emission.** Format-option-aware buyer SDKs targeting a heterogeneous seller population SHOULD emit `format_ids` alongside `format_option_refs` so legacy-format-only sellers — which ignore unknown fields per `additionalProperties: true` — still receive an explicit format set rather than silently defaulting to all formats supported by the product.', min_length=1, ), ] = None format_kind: Annotated[ canonical_format_kind.CanonicalFormatKind | None, Field( description='3.1+ direct canonical selector. Names the canonical format shape this package targets when the buyer is authoring by canonical kind rather than by a product-local `format_option_ref` or legacy `format_id`. Pair with `params` when the product declaration requires dimensions, duration, sizes, codecs, or other canonical parameters. If `format_option_refs` is present, it wins over this direct selector. If `format_ids` is present without `format_option_refs`, sellers MUST normalize and validate the legacy selector; buyers SHOULD NOT also send `format_kind` unless it is an informational echo of the same normalized shape.\n\nProduct satisfaction is directional: a broad selector such as `{ format_kind: "image" }` does not satisfy a product whose `format_options[]` fixes `params.width` and `params.height`. Sellers MUST reject under-specified direct selectors with `UNSUPPORTED_FEATURE` or an equivalent format-selector validation error.' ), ] = None params: Annotated[ dict[str, Any] | None, Field( description="Parameters for the direct canonical selector in `format_kind`. Shape follows the selected canonical's parameter vocabulary: dimensions (`width`, `height`, `sizes`), duration (`duration_ms_exact`, `duration_ms_range`), codecs, asset-source and slot narrowing, or other canonical-specific constraints. Omit when selecting by `format_option_refs` or `format_ids`; those selectors resolve their parameters from the product declaration or legacy catalog projection." ), ] = None budget: Annotated[ float, Field(description="Budget allocation for this package in the media buy's currency", ge=0.0), ] pacing: pacing_1.Pacing | None = None pricing_option_id: Annotated[ str, Field( description="ID of the selected pricing option from the product's pricing_options array" ), ] bid_price: Annotated[ float | None, Field( description="Bid price for auction-based pricing options. This is the exact bid/price to honor unless selected pricing_option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", ge=0.0, ), ] = None impressions: Annotated[ float | None, Field(description='Impression goal for this package', ge=0.0) ] = None start_time: Annotated[ AwareDatetime | None, Field( description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Must fall within the media buy's date range." ), ] = None end_time: Annotated[ AwareDatetime | None, Field( description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Must fall within the media buy's date range." ), ] = None paused: Annotated[ bool | None, Field( description='Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.' ), ] = False catalogs: Annotated[ list[catalog.Catalog] | None, Field( description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Makes the package catalog-driven: one budget envelope, platform optimizes across items.' ), ] = None optimization_goals: Annotated[ list[optimization_goal.OptimizationGoal] | None, Field( description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', min_length=1, ), ] = None targeting_overlay: targeting.TargetingOverlay | None = None measurement_terms: Annotated[ measurement_terms_1.MeasurementTerms | None, Field( description="Buyer's proposed billing measurement and makegood terms. Overrides product defaults. Seller accepts (echoed on confirmed package), rejects with TERMS_REJECTED, or adjusts. When absent, product's measurement_terms apply." ), ] = None performance_standards: Annotated[ list[performance_standard.PerformanceStandard] | None, Field( description="Buyer's proposed performance standards for this package. Overrides product defaults. Seller accepts, rejects with TERMS_REJECTED, or adjusts. When absent, product's performance_standards apply.", min_length=1, ), ] = None committed_metrics: Annotated[ list[CommittedMetrics6] | None, Field( description="Buyer's proposed reporting contract for this package — the metrics the buyer wants the seller to commit to populating in delivery reports. Same negotiation pattern as `measurement_terms` and `performance_standards`: seller accepts (echoes on confirmed package with `committed_at` stamped), rejects with `TERMS_REJECTED` (with explanation of which entries were unworkable), or normalizes (echoes a different but compatible list — buyer can accept by retrying with the normalized terms). When absent, the seller decides what to commit based on the product's `available_metrics` and the buyer's `required_metrics` filter on `get_products`. Each entry uses an explicit `scope` discriminator (`standard` or `vendor`) and identifies the metric — request-side entries do NOT carry `committed_at`; that timestamp is stamped by the seller on accept. Constraints on what the buyer MAY propose: each `scope: standard` entry's `metric_id` MUST be in the product's `available_metrics`, and each `scope: vendor` entry's `(vendor, metric_id)` MUST appear in the product's `vendor_metrics` — sellers SHOULD reject with `TERMS_REJECTED` and reference the offending entry when the proposal exceeds product capability.", min_length=1, ), ] = None creative_assignments: Annotated[ list[creative_assignment.CreativeAssignment] | None, Field( description='Assign existing library creatives to this package with optional weights and placement targeting', min_length=1, ), ] = None creatives: Annotated[ Sequence[creative_asset.CreativeAsset] | None, Field( description="Upload creative assets inline and assign to this package. When the seller also advertises creative.has_creative_library: true, these creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", max_length=100, min_length=1, ), ] = None agency_estimate_number: Annotated[ str | None, Field( description='Agency estimate or authorization number for this package. Overrides the media buy-level estimate number when different packages correspond to different agency estimates (e.g., different stations or flights within the same buy).', max_length=100, ), ] = None context: Annotated[ context_1.ContextObject | None, Field( description='Opaque package-level correlation data echoed unchanged in the package response, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context_1.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Do not use deprecated top-level buyer_ref for v3 correlation.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var agency_estimate_number : str | Nonevar bid_price : float | Nonevar budget : floatvar catalogs : list[adcp.types.generated_poc.core.catalog.Catalog] | Nonevar committed_metrics : list[adcp.types.generated_poc.media_buy.package_request.CommittedMetrics6] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_assignments : list[adcp.types.generated_poc.core.creative_assignment.CreativeAssignment] | Nonevar creatives : collections.abc.Sequence[adcp.types.generated_poc.core.creative_asset.CreativeAsset] | Nonevar end_time : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar format_kind : adcp.types.generated_poc.core.canonical_format_kind.CanonicalFormatKind | Nonevar format_option_refs : list[adcp.types.generated_poc.core.format_option_ref.FormatOptionReference] | Nonevar impressions : float | Nonevar measurement_terms : adcp.types.generated_poc.core.measurement_terms.MeasurementTerms | Nonevar model_configvar optimization_goals : list[adcp.types.generated_poc.core.optimization_goal.OptimizationGoal] | Nonevar pacing : adcp.types.generated_poc.enums.pacing.Pacing | Nonevar params : dict[str, typing.Any] | Nonevar paused : bool | Nonevar performance_standards : list[adcp.types.generated_poc.core.performance_standard.PerformanceStandard] | Nonevar pricing_option_id : strvar product_id : strvar start_time : pydantic.types.AwareDatetime | Nonevar targeting_overlay : adcp.types.generated_poc.core.targeting.TargetingOverlay | None
Inherited members
class PackageSignalTargeting (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class PackageSignalTargeting( RootModel[PackageSignalTargeting5 | PackageSignalTargeting6 | PackageSignalTargeting7] ): root: Annotated[ PackageSignalTargeting5 | PackageSignalTargeting6 | PackageSignalTargeting7, Field( description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", title='Package Signal Targeting', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[PackageSignalTargeting5, PackageSignalTargeting6, PackageSignalTargeting7]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.package_signal_targeting.PackageSignalTargeting5 | adcp.types.generated_poc.core.package_signal_targeting.PackageSignalTargeting6 | adcp.types.generated_poc.core.package_signal_targeting.PackageSignalTargeting7
class PackageSignalTargetingGroup (**data: Any)-
Expand source code
class PackageSignalTargetingGroup(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) operator: Annotated[ Operator, Field( description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." ), ] signals: Annotated[ list[package_signal_targeting.PackageSignalTargeting], Field( description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', min_length=1, ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar operator : adcp.types.generated_poc.core.package_signal_targeting_group.Operatorvar signals : list[adcp.types.generated_poc.core.package_signal_targeting.PackageSignalTargeting]
Inherited members
class PackageSignalTargetingGroups (**data: Any)-
Expand source code
class PackageSignalTargetingGroups(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) operator: Annotated[ Literal['all'], Field( description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." ), ] = 'all' groups: Annotated[ list[package_signal_targeting_group.PackageSignalTargetingGroup], Field( description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", min_length=1, ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var groups : list[adcp.types.generated_poc.core.package_signal_targeting_group.PackageSignalTargetingGroup]var model_configvar operator : Literal['all']
Inherited members
class PaginationRequest (**data: Any)-
Expand source code
class PaginationRequest(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) max_results: Annotated[ int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) ] = 50 cursor: Annotated[ str | None, Field(description='Opaque cursor from a previous response to fetch the next page'), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var cursor : str | Nonevar max_results : int | Nonevar model_config
Inherited members
class Placement (**data: Any)-
Expand source code
class Placement(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) kind: Annotated[ Kind, Field( description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." ), ] placement_id: Annotated[ str, Field( description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." ), ] publisher_domain: Annotated[ str | None, Field( description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] = None name: Annotated[ str | None, Field( description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' ), ] = None description: Annotated[ str | None, Field(description='Detailed description of where and how the placement appears') ] = None mode: Annotated[ Mode, Field( description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." ), ] tags: Annotated[ list[str] | None, Field( description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." ), ] = None format_ids: Annotated[ Sequence[format_id.FormatReferenceStructuredObject] | None, Field( description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', min_length=1, ), ] = None format_options: Annotated[ list[product_format_declaration.ProductFormatDeclaration] | None, Field( description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', min_length=1, ), ] = None video_placement_types: Annotated[ list[video_placement_type.VideoPlacementType] | None, Field( description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', min_length=1, ), ] = None audio_distribution_types: Annotated[ list[audio_distribution_type.AudioDistributionType] | None, Field( description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', min_length=1, ), ] = None sponsored_placement_types: Annotated[ list[sponsored_placement_type.SponsoredPlacementType] | None, Field( description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', min_length=1, ), ] = None social_placement_surfaces: Annotated[ list[social_placement_surface.SocialPlacementSurface] | None, Field( description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', min_length=1, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var audio_distribution_types : list[adcp.types.generated_poc.enums.audio_distribution_type.AudioDistributionType] | Nonevar description : str | Nonevar format_ids : collections.abc.Sequence[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar format_options : list[adcp.types.generated_poc.core.product_format_declaration.ProductFormatDeclaration] | Nonevar kind : adcp.types.generated_poc.core.placement.Kindvar mode : adcp.types.generated_poc.core.placement.Modevar model_configvar name : str | Nonevar placement_id : strvar publisher_domain : str | Nonevar sponsored_placement_types : list[adcp.types.generated_poc.enums.sponsored_placement_type.SponsoredPlacementType] | Nonevar video_placement_types : list[adcp.types.generated_poc.enums.video_placement_type.VideoPlacementType] | None
Inherited members
class PlacementReference (**data: Any)-
Expand source code
class PlacementReference(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) publisher_domain: Annotated[ str | None, Field( description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] = None placement_id: Annotated[ str, Field( description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar placement_id : strvar publisher_domain : str | None
Inherited members
class Policy (**data: Any)-
Expand source code
class Policy(PolicySummary): """Full governance policy including policy text and calibration exemplars.""" policy: str guidance: str | None = None exemplars: PolicyExemplars | None = None ext: dict[str, Any] | None = NoneFull governance policy including policy text and calibration exemplars.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- PolicySummary
- pydantic.main.BaseModel
Class variables
var exemplars : PolicyExemplars | Nonevar ext : dict[str, typing.Any] | Nonevar guidance : str | Nonevar model_configvar policy : str
class PolicyExemplar (**data: Any)-
Expand source code
class PolicyExemplar(BaseModel): """A pass/fail scenario used to calibrate governance agent interpretation.""" model_config = ConfigDict(extra="allow") scenario: str explanation: strA pass/fail scenario used to calibrate governance agent interpretation.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var explanation : strvar model_configvar scenario : str
class PolicyExemplars (**data: Any)-
Expand source code
class PolicyExemplars(BaseModel): """Collection of pass/fail exemplars for a policy.""" model_config = ConfigDict(extra="allow") pass_: list[PolicyExemplar] = Field(default_factory=list, alias="pass") fail: list[PolicyExemplar] = Field(default_factory=list)Collection of pass/fail exemplars for a policy.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var fail : list[PolicyExemplar]var model_configvar pass_ : list[PolicyExemplar]
class PolicyHistory (**data: Any)-
Expand source code
class PolicyHistory(BaseModel): """Edit history for a policy.""" model_config = ConfigDict(extra="allow") policy_id: str total: int revisions: list[PolicyRevision] = Field(default_factory=list)Edit history for a policy.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var model_configvar policy_id : strvar revisions : list[PolicyRevision]var total : int
class PolicyRevision (**data: Any)-
Expand source code
class PolicyRevision(BaseModel): """A single revision in a policy's edit history.""" model_config = ConfigDict(extra="allow") revision_number: int editor_name: str edit_summary: str is_rollback: bool rolled_back_to: int | None = None created_at: strA single revision in a policy's edit history.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var created_at : strvar edit_summary : strvar editor_name : strvar is_rollback : boolvar model_configvar revision_number : intvar rolled_back_to : int | None
class PolicySummary (**data: Any)-
Expand source code
class PolicySummary(BaseModel): """Summary of a governance policy from the registry.""" model_config = ConfigDict(extra="allow", populate_by_name=True) policy_id: str version: str name: str description: str | None = None category: str enforcement: str jurisdictions: list[str] = Field(default_factory=list) region_aliases: dict[str, list[str]] = Field(default_factory=dict) verticals: list[str] = Field(default_factory=list) channels: list[str] | None = None governance_domains: list[str] = Field(default_factory=list) effective_date: str | None = None sunset_date: str | None = None source_url: str | None = None source_name: str | None = None source_type: str | None = None review_status: str | None = None created_at: str | None = None updated_at: str | None = NoneSummary of a governance policy from the registry.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Subclasses
Class variables
var category : strvar channels : list[str] | Nonevar created_at : str | Nonevar description : str | Nonevar effective_date : str | Nonevar enforcement : strvar governance_domains : list[str]var jurisdictions : list[str]var model_configvar name : strvar policy_id : strvar region_aliases : dict[str, list[str]]var review_status : str | Nonevar source_name : str | Nonevar source_type : str | Nonevar source_url : str | Nonevar sunset_date : str | Nonevar updated_at : str | Nonevar version : strvar verticals : list[str]
class PreviewCreativeRequest (**data: Any)-
Expand source code
class PreviewCreativeRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) request_type: Annotated[ RequestType, Field( description="Preview mode. 'single' previews one creative manifest. 'batch' previews multiple creatives in one call. 'variant' replays a post-flight variant by ID." ), ] creative_manifest: Annotated[ creative_manifest_1.CreativeManifest | None, Field( description="Complete creative manifest with all required assets for the format. Required when request_type is 'single'. Also accepted per item in batch mode." ), ] = None format_id: Annotated[ format_id_1.FormatReferenceStructuredObject | None, Field( description='Always a structured object {agent_url, id} — never a plain string. Format identifier for rendering the preview. Defaults to creative_manifest_1.format_id if omitted. Used in single mode.' ), ] = None inputs: Annotated[ list[Input] | None, Field( description='Array of input sets for generating multiple preview variants. Each input set defines macros and context values for one preview rendering. Used in single mode.', min_length=1, ), ] = None template_id: Annotated[ str | None, Field(description='Specific template ID for custom format rendering. Used in single mode.'), ] = None quality: Annotated[ creative_quality.CreativeQuality | None, Field( description="Render quality. 'draft' produces fast, lower-fidelity renderings. 'production' produces full-quality renderings. In batch mode, sets the default for all requests (individual items can override)." ), ] = None output_format: Annotated[ preview_output_format.PreviewOutputFormat | None, Field( description="Output format. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). In batch mode, sets the default for all requests (individual items can override). Default: 'url'." ), ] = preview_output_format.PreviewOutputFormat.url item_limit: Annotated[ int | None, Field( description='Maximum number of catalog items to render per preview variant. Used in single mode. Creative agents SHOULD default to a reasonable sample when omitted and the catalog is large.', ge=1, ), ] = None requests: Annotated[ list[Request] | None, Field( description="Array of preview requests (1-50 items). Required when request_type is 'batch'. Each item follows the single request structure.", max_length=50, min_length=1, ), ] = None variant_id: Annotated[ str | None, Field( description="Platform-assigned variant identifier from get_creative_delivery response. Required when request_type is 'variant'." ), ] = None creative_id: Annotated[ str | None, Field(description='Creative identifier for context. Used in variant mode.') ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_id : str | Nonevar creative_manifest : adcp.types.generated_poc.core.creative_manifest.CreativeManifest | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_id : adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject | Nonevar inputs : list[adcp.types.generated_poc.creative.preview_creative_request.Input] | Nonevar item_limit : int | Nonevar model_configvar output_format : adcp.types.generated_poc.enums.preview_output_format.PreviewOutputFormat | Nonevar quality : adcp.types.generated_poc.enums.creative_quality.CreativeQuality | Nonevar request_type : adcp.types.generated_poc.creative.preview_creative_request.RequestTypevar requests : list[adcp.types.generated_poc.creative.preview_creative_request.Request] | Nonevar template_id : str | Nonevar variant_id : str | None
Inherited members
class PreviewCreativeResponse1 (**data: Any)-
Expand source code
class PreviewCreativeResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') response_type: Literal['single'] = 'single' previews: Annotated[list[Preview], Field(min_length=1)] interactive_url: AnyUrl | None = None expires_at: AwareDatetime | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar interactive_url : pydantic.networks.AnyUrl | Nonevar model_configvar previews : list[adcp.types.generated_poc.creative.preview_creative_response.Preview]var response_type : Literal['single']
class PreviewCreativeSingleResponse (**data: Any)-
Expand source code
class PreviewCreativeResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') response_type: Literal['single'] = 'single' previews: Annotated[list[Preview], Field(min_length=1)] interactive_url: AnyUrl | None = None expires_at: AwareDatetime | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar interactive_url : pydantic.networks.AnyUrl | Nonevar model_configvar previews : list[adcp.types.generated_poc.creative.preview_creative_response.Preview]var response_type : Literal['single']
class PreviewCreativeStaticResponse (**data: Any)-
Expand source code
class PreviewCreativeResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') response_type: Literal['single'] = 'single' previews: Annotated[list[Preview], Field(min_length=1)] interactive_url: AnyUrl | None = None expires_at: AwareDatetime | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar interactive_url : pydantic.networks.AnyUrl | Nonevar model_configvar previews : list[adcp.types.generated_poc.creative.preview_creative_response.Preview]var response_type : Literal['single']
Inherited members
class PreviewCreativeBatchResponse (**data: Any)-
Expand source code
class PreviewCreativeResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') response_type: Literal['batch'] = 'batch' results: Annotated[list[Result], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar response_type : Literal['batch']var results : list[adcp.types.generated_poc.creative.preview_creative_response.Result]
class PreviewCreativeInteractiveResponse (**data: Any)-
Expand source code
class PreviewCreativeResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') response_type: Literal['batch'] = 'batch' results: Annotated[list[Result], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar response_type : Literal['batch']var results : list[adcp.types.generated_poc.creative.preview_creative_response.Result]
Inherited members
class PreviewCreativeVariantResponse (**data: Any)-
Expand source code
class PreviewCreativeResponse3(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') response_type: Literal['variant'] = 'variant' variant_id: str creative_id: str | None = None previews: Annotated[list[Preview3], Field(min_length=1)] manifest: creative_manifest_1.CreativeManifest | None = None expires_at: AwareDatetime | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_id : str | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar manifest : adcp.types.generated_poc.core.creative_manifest.CreativeManifest | Nonevar model_configvar previews : list[adcp.types.generated_poc.creative.preview_creative_response.Preview3]var response_type : Literal['variant']var variant_id : str
Inherited members
class UrlPreviewRender (**data: Any)-
Expand source code
class PreviewRender1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) render_id: Annotated[ str, Field(description='Unique identifier for this rendered piece within the variant') ] output_format: Annotated[ Literal['url'], Field(description='Discriminator indicating preview_url is provided') ] = 'url' preview_url: Annotated[ AnyUrl, Field( description='URL to an HTML page that renders this piece. Can be embedded in an iframe.' ), ] role: Annotated[ str, Field( description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." ), ] dimensions: Annotated[ Dimensions | None, Field(description='Dimensions for this rendered piece') ] = None embedding: Annotated[ Embedding | None, Field(description='Optional security and embedding metadata for safe iframe integration'), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var dimensions : adcp.types.generated_poc.creative.preview_render.Dimensions | Nonevar embedding : adcp.types.generated_poc.creative.preview_render.Embedding | Nonevar model_configvar output_format : Literal['url']var preview_url : pydantic.networks.AnyUrlvar render_id : strvar role : str
Inherited members
class HtmlPreviewRender (**data: Any)-
Expand source code
class PreviewRender2(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) render_id: Annotated[ str, Field(description='Unique identifier for this rendered piece within the variant') ] output_format: Annotated[ Literal['html'], Field(description='Discriminator indicating preview_html is provided') ] = 'html' preview_html: Annotated[ str, Field( description='Raw HTML for this rendered piece. Can be embedded directly in the page without iframe. Security warning: Only use with trusted creative agents as this bypasses iframe sandboxing.' ), ] role: Annotated[ str, Field( description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." ), ] dimensions: Annotated[ Dimensions | None, Field(description='Dimensions for this rendered piece') ] = None embedding: Annotated[ Embedding | None, Field(description='Optional security and embedding metadata') ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var dimensions : adcp.types.generated_poc.creative.preview_render.Dimensions | Nonevar embedding : adcp.types.generated_poc.creative.preview_render.Embedding | Nonevar model_configvar output_format : Literal['html']var preview_html : strvar render_id : strvar role : str
Inherited members
class BothPreviewRender (**data: Any)-
Expand source code
class PreviewRender3(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) render_id: Annotated[ str, Field(description='Unique identifier for this rendered piece within the variant') ] output_format: Annotated[ Literal['both'], Field( description='Discriminator indicating both preview_url and preview_html are provided' ), ] = 'both' preview_url: Annotated[ AnyUrl, Field( description='URL to an HTML page that renders this piece. Can be embedded in an iframe.' ), ] preview_html: Annotated[ str, Field( description='Raw HTML for this rendered piece. Can be embedded directly in the page without iframe. Security warning: Only use with trusted creative agents as this bypasses iframe sandboxing.' ), ] role: Annotated[ str, Field( description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." ), ] dimensions: Annotated[ Dimensions | None, Field(description='Dimensions for this rendered piece') ] = None embedding: Annotated[ Embedding | None, Field(description='Optional security and embedding metadata for safe iframe integration'), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var dimensions : adcp.types.generated_poc.creative.preview_render.Dimensions | Nonevar embedding : adcp.types.generated_poc.creative.preview_render.Embedding | Nonevar model_configvar output_format : Literal['both']var preview_html : strvar preview_url : pydantic.networks.AnyUrlvar render_id : strvar role : str
Inherited members
class PriceGuidance (**data: Any)-
Expand source code
class PriceGuidance(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) p25: Annotated[ float | None, Field(description='25th percentile of recent winning bids', ge=0.0) ] = None p50: Annotated[float | None, Field(description='Median of recent winning bids', ge=0.0)] = None p75: Annotated[ float | None, Field(description='75th percentile of recent winning bids', ge=0.0) ] = None p90: Annotated[ float | None, Field(description='90th percentile of recent winning bids', ge=0.0) ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar p25 : float | Nonevar p50 : float | Nonevar p75 : float | Nonevar p90 : float | None
Inherited members
class PricingCurrency (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class PricingCurrency(RootModel[str]): root: Annotated[ str, Field( description="ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP')", pattern='^[A-Z]{3}$' ), ]Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[str]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : str
class PricingModel (*args, **kwds)-
Expand source code
class PricingModel(StrEnum): cpm = 'cpm' vcpm = 'vcpm' cpc = 'cpc' cpcv = 'cpcv' cpv = 'cpv' cpp = 'cpp' cpa = 'cpa' flat_rate = 'flat_rate' time = 'time'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var cpavar cpcvar cpcvvar cpmvar cppvar cpvvar flat_ratevar timevar vcpm
class Product (**data: Any)-
Expand source code
class Product(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) @model_validator(mode='before') @classmethod def _coerce_publisher_property_models(cls, data: Any) -> Any: if isinstance(data, dict) and isinstance(data.get('publisher_properties'), list): coerced = [] changed = False for item in data['publisher_properties']: if hasattr(item, 'model_dump'): coerced.append(item.model_dump(mode='json', exclude_none=True)) changed = True else: coerced.append(item) if changed: data = dict(data) data['publisher_properties'] = coerced return data product_id: Annotated[str, Field(description='Unique identifier for the product')] name: Annotated[str, Field(description='Human-readable product name')] description: Annotated[ str, Field(description='Detailed description of the product and its inventory') ] publisher_properties: Annotated[ list[PublisherProperty], Field( description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. 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. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", min_length=1, ), ] channels: Annotated[ list[channels_1.MediaChannel] | None, Field( description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." ), ] = None format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." ), ] = None format_options: Annotated[ list[product_format_declaration.ProductFormatDeclaration] | None, Field( description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", min_length=1, ), ] = None placements: Annotated[ list[placement.Placement] | None, Field( description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", min_length=1, ), ] = None video_placement_types: Annotated[ list[video_placement_type.VideoPlacementType] | None, Field( description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', min_length=1, ), ] = None audio_distribution_types: Annotated[ list[audio_distribution_type.AudioDistributionType] | None, Field( description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', min_length=1, ), ] = None sponsored_placement_types: Annotated[ list[sponsored_placement_type.SponsoredPlacementType] | None, Field( description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', min_length=1, ), ] = None social_placement_surfaces: Annotated[ list[social_placement_surface.SocialPlacementSurface] | None, Field( description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', min_length=1, ), ] = None delivery_type: delivery_type_1.DeliveryType exclusivity: Annotated[ exclusivity_1.Exclusivity | None, Field( description="Whether this product offers exclusive access to its inventory. Defaults to 'none' when absent. Most relevant for guaranteed products tied to specific collections or placements." ), ] = None pricing_options: Annotated[ list[pricing_option.PricingOption], Field(description='Available pricing models for this product', min_length=1), ] forecast: Annotated[ delivery_forecast.DeliveryForecast | None, Field( description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.' ), ] = None outcome_measurement: Annotated[ outcome_measurement_1.OutcomeMeasurementDeprecated | None, Field( description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.' ), ] = None delivery_measurement: Annotated[ DeliveryMeasurement | None, Field( description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' ), ] = None measurement_terms: Annotated[ measurement_terms_1.MeasurementTerms | None, Field( description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy." ), ] = None performance_standards: Annotated[ list[performance_standard.PerformanceStandard] | None, Field( description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", min_length=1, ), ] = None cancellation_policy: Annotated[ cancellation_policy_1.CancellationPolicy | None, Field( description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.' ), ] = None allowed_actions: Annotated[ list[product_allowed_action.ProductAllowedAction] | None, Field( description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', min_length=1, ), ] = None reporting_capabilities: reporting_capabilities_1.ReportingCapabilities creative_policy: creative_policy_1.CreativePolicy | None = None is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None property_targeting_allowed: Annotated[ bool | None, Field( description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." ), ] = False data_provider_signals: Annotated[ list[data_provider_signal_selector.DataProviderSignalSelector] | None, Field( deprecated=True, description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', ), ] = None included_signals: Annotated[ list[signal_listing.SignalListing] | None, Field( description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", min_length=1, ), ] = None signal_targeting_options: Annotated[ list[product_signal_targeting_option.ProductSignalTargetingOption] | None, Field( description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", min_length=1, ), ] = None signal_targeting_rules: Annotated[ signal_targeting_rules_1.SignalTargetingRules | None, Field( description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.' ), ] = None signal_targeting_allowed: Annotated[ bool | None, Field( description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' ), ] = False catalog_types: Annotated[ list[catalog_type.CatalogType] | None, Field( description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', min_length=1, ), ] = None metric_optimization: Annotated[ MetricOptimization | None, Field( description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." ), ] = None vendor_metric_optimization: Annotated[ vendor_metric_optimization_1.VendorMetricOptimization | None, Field( description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level." ), ] = None max_optimization_goals: Annotated[ int | None, Field( description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', ge=1, ), ] = None measurement_readiness: Annotated[ measurement_readiness_1.MeasurementReadiness | None, Field( description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals." ), ] = None conversion_tracking: Annotated[ ConversionTracking | None, Field( description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." ), ] = None catalog_match: Annotated[ CatalogMatch | None, Field( description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' ), ] = None brief_relevance: Annotated[ str | None, Field( description='Explanation of why this product matches the brief (only included when brief is provided)' ), ] = None expires_at: Annotated[ AwareDatetime | None, Field( description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' ), ] = None product_card: Annotated[ ProductCard | None, Field( description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' ), ] = None product_card_detailed: Annotated[ ProductCardDetailed | None, Field( description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' ), ] = None collections: Annotated[ list[collection_selector.CollectionSelector] | None, Field( description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', min_length=1, ), ] = None collection_targeting_allowed: Annotated[ bool | None, Field( description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." ), ] = False installments: Annotated[ list[installment.Installment] | None, Field( description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' ), ] = None enforced_policies: Annotated[ list[str] | None, Field( description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' ), ] = None trusted_match: Annotated[ TrustedMatch | None, Field( description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' ), ] = None material_submission: Annotated[ MaterialSubmission | None, Field( description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var allowed_actions : list[adcp.types.generated_poc.core.product_allowed_action.ProductAllowedAction] | Nonevar audio_distribution_types : list[adcp.types.generated_poc.enums.audio_distribution_type.AudioDistributionType] | Nonevar brief_relevance : str | Nonevar cancellation_policy : adcp.types.generated_poc.core.cancellation_policy.CancellationPolicy | Nonevar catalog_match : adcp.types.generated_poc.core.product.CatalogMatch | Nonevar catalog_types : list[adcp.types.generated_poc.enums.catalog_type.CatalogType] | Nonevar channels : list[adcp.types.generated_poc.enums.channels.MediaChannel] | Nonevar collection_targeting_allowed : bool | Nonevar collections : list[adcp.types.generated_poc.core.collection_selector.CollectionSelector] | Nonevar conversion_tracking : adcp.types.generated_poc.core.product.ConversionTracking | Nonevar creative_policy : adcp.types.generated_poc.core.creative_policy.CreativePolicy | Nonevar delivery_measurement : adcp.types.generated_poc.core.product.DeliveryMeasurement | Nonevar delivery_type : adcp.types.generated_poc.enums.delivery_type.DeliveryTypevar description : strvar enforced_policies : list[str] | Nonevar exclusivity : adcp.types.generated_poc.enums.exclusivity.Exclusivity | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar forecast : adcp.types.generated_poc.core.delivery_forecast.DeliveryForecast | Nonevar format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar format_options : list[adcp.types.generated_poc.core.product_format_declaration.ProductFormatDeclaration] | Nonevar included_signals : list[adcp.types.generated_poc.core.signal_listing.SignalListing] | Nonevar installments : list[adcp.types.generated_poc.core.installment.Installment] | Nonevar is_custom : bool | Nonevar material_submission : adcp.types.generated_poc.core.product.MaterialSubmission | Nonevar max_optimization_goals : int | Nonevar measurement_readiness : adcp.types.generated_poc.core.measurement_readiness.MeasurementReadiness | Nonevar measurement_terms : adcp.types.generated_poc.core.measurement_terms.MeasurementTerms | Nonevar metric_optimization : adcp.types.generated_poc.core.product.MetricOptimization | Nonevar model_configvar name : strvar outcome_measurement : adcp.types.generated_poc.core.outcome_measurement.OutcomeMeasurementDeprecated | Nonevar performance_standards : list[adcp.types.generated_poc.core.performance_standard.PerformanceStandard] | Nonevar placements : list[adcp.types.generated_poc.core.placement.Placement] | Nonevar pricing_options : list[adcp.types.generated_poc.core.pricing_option.PricingOption]var product_card : adcp.types.generated_poc.core.product.ProductCard | Nonevar product_card_detailed : adcp.types.generated_poc.core.product.ProductCardDetailed | Nonevar product_id : strvar property_targeting_allowed : bool | Nonevar publisher_properties : list[adcp.types.generated_poc.core.product.PublisherProperty]var reporting_capabilities : adcp.types.generated_poc.core.reporting_capabilities.ReportingCapabilitiesvar signal_targeting_allowed : bool | Nonevar signal_targeting_options : list[adcp.types.generated_poc.core.product_signal_targeting_option.ProductSignalTargetingOption] | Nonevar signal_targeting_rules : adcp.types.generated_poc.core.signal_targeting_rules.SignalTargetingRules | Nonevar sponsored_placement_types : list[adcp.types.generated_poc.enums.sponsored_placement_type.SponsoredPlacementType] | Nonevar trusted_match : adcp.types.generated_poc.core.product.TrustedMatch | Nonevar vendor_metric_optimization : adcp.types.generated_poc.core.vendor_metric_optimization.VendorMetricOptimization | Nonevar video_placement_types : list[adcp.types.generated_poc.enums.video_placement_type.VideoPlacementType] | None
Instance variables
var data_provider_signals : list[adcp.types.generated_poc.core.data_provider_signal_selector.DataProviderSignalSelector] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
Inherited members
class ProductFilters (**data: Any)-
Expand source code
class ProductFilters(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) delivery_type: delivery_type_1.DeliveryType | None = None exclusivity: Annotated[ exclusivity_1.Exclusivity | None, Field( description="Filter by exclusivity level. Returns products matching the specified exclusivity (e.g., 'exclusive' returns only sole-sponsorship products)." ), ] = None is_fixed_price: Annotated[ bool | None, Field( description='Filter by pricing availability and returned pricing options: true = products offering fixed pricing (at least one option with fixed_price), false = products offering auction pricing (at least one option without fixed_price). Products with both fixed and auction options match both true and false, but sellers MUST return only the pricing_options entries matching the requested pricing type so buyers can deterministically select from the returned options.' ), ] = None pricing_currencies: Annotated[ list[PricingCurrency] | None, Field( description='Filter by currencies the buyer can use for the media product transaction, using ISO 4217 currency codes. Products match when they offer at least one product-level pricing_options entry in one of the requested currencies and any seller-applied or otherwise mandatory product-scoped signal charges are satisfiable in one of those currencies or have no incremental price. Mandatory custom signal pricing without currency is not satisfiable for this filter unless the seller can truthfully treat it as having no incremental price. Sellers MUST return only product pricing_options entries whose currency is in this list so buyers can select deterministically from discovery. This filter does not require pruning optional signal or vendor add-on pricing; buyers should avoid optional add-ons priced only in unsupported currencies.', min_length=1, ), ] = None format_ids: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field(description='Filter by specific format IDs', min_length=1), ] = None standard_formats_only: Annotated[ bool | None, Field(description='Only return products accepting IAB standard formats') ] = None min_exposures: Annotated[ int | None, Field(description='Minimum exposures/impressions needed for measurement validity', ge=1), ] = None start_date: Annotated[ date_aliased | None, Field( description='Campaign start date (ISO 8601 date format: YYYY-MM-DD) for availability checks' ), ] = None end_date: Annotated[ date_aliased | None, Field( description='Campaign end date (ISO 8601 date format: YYYY-MM-DD) for availability checks' ), ] = None budget_range: Annotated[ BudgetRange | None, Field(description='Budget range to filter appropriate products') ] = None countries: Annotated[ list[Country] | None, Field( description="Filter by country coverage using ISO 3166-1 alpha-2 codes (e.g., ['US', 'CA', 'GB']). Works for all inventory types.", min_length=1, ), ] = None regions: Annotated[ list[Region] | None, Field( description="Filter by region coverage using ISO 3166-2 codes (e.g., ['US-NY', 'US-CA', 'GB-SCT']). Use for locally-bound inventory (regional OOH, local TV) where products have region-specific coverage.", min_length=1, ), ] = None metros: Annotated[ list[Metro] | None, Field( description='Filter by metro coverage for locally-bound inventory (radio, DOOH, local TV). Use when products have DMA/metro-specific coverage. For digital inventory where products have broad coverage, use required_geo_targeting instead to filter by seller capability.', min_length=1, ), ] = None channels: Annotated[ list[channels_1.MediaChannel] | None, Field( description="Filter by advertising channels (e.g., ['display', 'ctv', 'dooh'])", min_length=1, ), ] = None video_placement_types: Annotated[ list[video_placement_type.VideoPlacementType] | None, Field( description='Filter video products by acceptable declared video placement types, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Sellers SHOULD return only products they can satisfy with at least one requested type. Products whose only available delivery is a mixed, non-targetable bundle that includes unrequested video placement types SHOULD NOT match unless the seller can constrain delivery to the requested type during planning or purchase. This filter has set semantics for wholesale feed canonicalization.', min_length=1, ), ] = None audio_distribution_types: Annotated[ list[audio_distribution_type.AudioDistributionType] | None, Field( description='Filter audio products by acceptable declared audio distribution types, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Sellers SHOULD return only products they can satisfy with at least one requested type. Products whose only available delivery is a mixed, non-targetable bundle that includes unrequested audio distribution types SHOULD NOT match unless the seller can constrain delivery to the requested type during planning or purchase. This filter has set semantics for wholesale feed canonicalization.', min_length=1, ), ] = None sponsored_placement_types: Annotated[ list[sponsored_placement_type.SponsoredPlacementType] | None, Field( description='Filter retail-media products by acceptable declared sponsored-placement types (sponsored search, sponsored display, or sponsored native). Sellers SHOULD return only products they can satisfy with at least one requested type. Products whose only available delivery is a mixed, non-targetable bundle that includes unrequested sponsored-placement types SHOULD NOT match unless the seller can constrain delivery to the requested type during planning or purchase. This filter has set semantics for wholesale feed canonicalization.', min_length=1, ), ] = None social_placement_surfaces: Annotated[ list[social_placement_surface.SocialPlacementSurface] | None, Field( description='Filter social products by acceptable declared social-placement surfaces (feed, stories, short_video, explore, or search). Sellers SHOULD return only products they can satisfy with at least one requested surface. Products whose only available delivery is a mixed, non-targetable bundle that includes unrequested surfaces SHOULD NOT match unless the seller can constrain delivery to the requested surface during planning or purchase. This filter has set semantics for wholesale feed canonicalization.', min_length=1, ), ] = None required_axe_integrations: Annotated[ list[AnyUrl] | None, Field( deprecated=True, description='Deprecated: Use trusted_match filter instead. Filter to products executable through specific agentic ad exchanges. URLs are canonical identifiers.', ), ] = None trusted_match: Annotated[ TrustedMatch | None, Field( description='Filter products by Trusted Match Protocol capabilities. Only products with matching TMP support are returned.' ), ] = None required_features: Annotated[ media_buy_features.MediaBuyFeatures | None, Field( description='Filter to products from sellers supporting specific protocol features. Only features set to true are used for filtering.' ), ] = None required_geo_targeting: Annotated[ list[RequiredGeoTargetingItem] | None, Field( description='Filter to products from sellers supporting specific geo targeting capabilities. Each entry specifies a targeting level (country, region, metro, postal_area) and optionally a system for levels that have multiple classification systems. For native postal_area filters, include country plus the country-local postal system.', min_length=1, ), ] = None signal_targeting: Annotated[ list[SignalTargetingItem] | None, Field( description="Filter to products where the requested signals are buyer-selectable and jointly composable: the signals are available through inline signal_targeting_options and/or through get_signals for wholesale products that allow signal targeting but omit inline options, signal_targeting_allowed is true, and the requested set can coexist under the product's signal_targeting_rules. Each filter entry uses signal_ref, with deprecated signal_id accepted during the SignalRef migration window, and may include targeting_mode='include' or 'exclude' to require the product option or product rules to support that use. When targeting_mode is omitted, include is assumed. SignalRef scope 'product' is seller-local exact option matching only, not a portable semantic identifier across products or sellers; buyers wanting portable discovery should use scope 'data_provider' or get_signals. included_signals and deprecated bundled/non-selectable data_provider_signals do not satisfy this filter because they cannot be selected on create_media_buy.", min_length=1, ), ] = None postal_areas: Annotated[ list[postal_area.PostalArea] | None, Field( description='Filter by postal area coverage for locally-bound inventory (direct mail, DOOH, local campaigns). Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility. For digital inventory where products have broad coverage, use required_geo_targeting instead to filter by seller capability.', min_length=1, ), ] = None geo_proximity: Annotated[ list[GeoProximityItem] | None, Field( description='Filter by proximity to geographic points. Returns products with inventory coverage near these locations. Follows the same format as the targeting overlay — each entry uses exactly one method: travel_time + transport_mode, radius, or geometry. For locally-bound inventory (DOOH, radio), filters to products with coverage in the area. For digital inventory, filters to products from sellers supporting geo_proximity targeting.', min_length=1, ), ] = None required_performance_standards: Annotated[ list[performance_standard.PerformanceStandard] | None, Field( description="Filter to products that can meet the buyer's performance standard requirements. Each entry specifies a metric, minimum threshold, and optionally a required vendor and standard. Products that cannot meet these thresholds or do not support the specified vendors are excluded. Use this to tell the seller upfront: 'I need DoubleVerify for viewability at 70% MRC.'", min_length=1, ), ] = None required_metrics: Annotated[ list[available_metric.AvailableMetric] | None, Field( description="Filter to products whose `reporting_capabilities.available_metrics` is a superset of these metrics — i.e., products that commit to reporting all listed metrics in delivery responses. Use this for capability-level discovery (e.g., 'I need products that report `completed_views` for a CTV CPCV buy'); guarantee-level requirements with thresholds belong in `required_performance_standards` and `measurement_terms`. Sellers MUST silently exclude products that cannot meet this list (filter-not-fail; do not return an error). The product's declared `available_metrics` becomes the binding reporting contract carried into the resulting media buy — the same metric vocabulary is used to compute `missing_metrics` on `get_media_buy_delivery`.", examples=[ ['completed_views'], ['completed_views', 'completion_rate'], ['impressions', 'spend', 'engagements'], ], min_length=1, ), ] = None required_vendor_metrics: Annotated[ list[RequiredVendorMetric] | None, Field( description="Filter to products whose `reporting_capabilities.vendor_metrics` matches these criteria. Each entry pins a `vendor` (matches any metric from that vendor), a `metric_id` (matches the metric across any vendor that uses that identifier), or both (specific vendor's specific metric). A product matches if its declared `vendor_metrics` covers ALL listed entries (AND across entries; pins within an entry are conjunctive). Cross-vendor discovery (e.g., 'I need attention measurement from any vendor that does it') is the buyer agent's responsibility — the agent resolves which vendors offer a category via the vendors' `brand.json` records, then enumerates them as filter entries. AdCP does not carry vendor-side metric metadata (category, methodology, standard alignment) in the filter surface; that lives at the vendor and is queried out-of-band. Sellers MUST silently exclude non-matching products (filter-not-fail; do not return an error) — same convention as the other `required_*` filters.", examples=[ [{'vendor': {'domain': 'attentionvendor.example'}}], [ { 'vendor': {'domain': 'panelmeasurement.example'}, 'metric_id': 'demographic_reach', } ], [ {'vendor': {'domain': 'attentionvendor.example'}}, {'vendor': {'domain': 'secondattentionvendor.example'}}, ], ], min_length=1, ), ] = None keywords: Annotated[ list[Keyword] | None, Field( description='Filter by keyword relevance for search and retail media platforms. Returns products that support keyword targeting for these terms. Allows the sell-side agent to assess keyword availability and recommend appropriate products. Use match_type to indicate the desired precision.', min_length=1, ), ] = None ext: Annotated[ ext_1.ExtensionObject | None, Field( description='Vendor-namespaced extension parameters for seller-specific filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; do not interpolate into LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var audio_distribution_types : list[adcp.types.generated_poc.enums.audio_distribution_type.AudioDistributionType] | Nonevar budget_range : adcp.types.generated_poc.core.product_filters.BudgetRange | Nonevar channels : list[adcp.types.generated_poc.enums.channels.MediaChannel] | Nonevar countries : list[adcp.types.generated_poc.core.product_filters.Country] | Nonevar delivery_type : adcp.types.generated_poc.enums.delivery_type.DeliveryType | Nonevar end_date : datetime.date | Nonevar exclusivity : adcp.types.generated_poc.enums.exclusivity.Exclusivity | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | Nonevar geo_proximity : list[adcp.types.generated_poc.core.product_filters.GeoProximityItem] | Nonevar is_fixed_price : bool | Nonevar keywords : list[adcp.types.generated_poc.core.product_filters.Keyword] | Nonevar metros : list[adcp.types.generated_poc.core.product_filters.Metro] | Nonevar min_exposures : int | Nonevar model_configvar postal_areas : list[adcp.types.generated_poc.core.postal_area.PostalArea] | Nonevar pricing_currencies : list[adcp.types.generated_poc.core.product_filters.PricingCurrency] | Nonevar regions : list[adcp.types.generated_poc.core.product_filters.Region] | Nonevar required_features : adcp.types.generated_poc.core.media_buy_features.MediaBuyFeatures | Nonevar required_geo_targeting : list[adcp.types.generated_poc.core.product_filters.RequiredGeoTargetingItem] | Nonevar required_metrics : list[adcp.types.generated_poc.enums.available_metric.AvailableMetric] | Nonevar required_performance_standards : list[adcp.types.generated_poc.core.performance_standard.PerformanceStandard] | Nonevar required_vendor_metrics : list[adcp.types.generated_poc.core.product_filters.RequiredVendorMetric] | Nonevar signal_targeting : list[adcp.types.generated_poc.core.product_filters.SignalTargetingItem] | Nonevar sponsored_placement_types : list[adcp.types.generated_poc.enums.sponsored_placement_type.SponsoredPlacementType] | Nonevar standard_formats_only : bool | Nonevar start_date : datetime.date | Nonevar trusted_match : adcp.types.generated_poc.core.product_filters.TrustedMatch | Nonevar video_placement_types : list[adcp.types.generated_poc.enums.video_placement_type.VideoPlacementType] | None
Instance variables
var required_axe_integrations : list[pydantic.networks.AnyUrl] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
Inherited members
class ProductSignalTargetingOption (**data: Any)-
Expand source code
class ProductSignalTargetingOption(SignalListing): model_config = ConfigDict( extra='allow', ) signal_agent_segment_id: Annotated[ str | None, Field( description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' ), ] = None activation_status: Annotated[ ActivationStatus | None, Field( description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." ), ] = ActivationStatus.ready allowed_targeting_modes: Annotated[ list[AllowedTargetingMode] | None, Field( description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", min_length=1, ), ] = [AllowedTargetingMode.include] default_selected: Annotated[ bool | None, Field( description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." ), ] = False selection_group: Annotated[ str | None, Field( description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' ), ] = None pricing_options: Annotated[ list[vendor_pricing_option.VendorPricingOption] | None, Field( description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', min_length=1, ), ] = None signal_ref: Annotated[ signal_ref.SignalRef, Field( description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal." ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.signal_listing.SignalListing
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var activation_status : adcp.types.generated_poc.core.product_signal_targeting_option.ActivationStatus | Nonevar allowed_targeting_modes : list[adcp.types.generated_poc.core.product_signal_targeting_option.AllowedTargetingMode] | Nonevar default_selected : bool | Nonevar model_configvar pricing_options : list[adcp.types.generated_poc.core.vendor_pricing_option.VendorPricingOption] | Nonevar selection_group : str | Nonevar signal_agent_segment_id : str | Nonevar signal_ref : adcp.types.generated_poc.core.signal_ref.SignalRef
Instance variables
var signal_id : adcp.types.generated_poc.core.signal_id.SignalId | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
Inherited members
class Property (**data: Any)-
Expand source code
class Property(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) property_id: Annotated[ property_id_1.PropertyId | None, Field( description='Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects.' ), ] = None property_type: Annotated[ property_type_1.PropertyType, Field(description='Type of advertising property') ] name: Annotated[str, Field(description='Human-readable property name')] identifiers: Annotated[ list[Identifier], Field(description='Array of identifiers for this property', min_length=1) ] tags: Annotated[ list[property_tag.PropertyTag] | None, Field( description='Tags for categorization and grouping (e.g., network membership, content categories)' ), ] = None supported_channels: Annotated[ list[channels.MediaChannel] | None, Field( description="Advertising channels this property supports (e.g., ['display', 'olv', 'social']). Publishers declare which channels their inventory aligns with. Properties may support multiple channels. See the Media Channel Taxonomy for definitions." ), ] = None publisher_domain: Annotated[ str | None, Field( description='Domain where adagents.json should be checked for authorization validation. Optional in adagents.json (file location implies domain).' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var identifiers : list[adcp.types.generated_poc.core.property.Identifier]var model_configvar name : strvar property_id : adcp.types.generated_poc.core.property_id.PropertyId | Nonevar property_type : adcp.types.generated_poc.enums.property_type.PropertyTypevar publisher_domain : str | Nonevar supported_channels : list[adcp.types.generated_poc.enums.channels.MediaChannel] | None
Inherited members
class PropertyActivity (**data: Any)-
Expand source code
class PropertyActivity(RegistryBaseModel): domain: Annotated[str, Field(examples=["examplepub.com"])] total: Annotated[int, Field(examples=[3])] revisions: list[ActivityRevision]Base model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var domain : strvar model_configvar revisions : list[ActivityRevision]var total : int
class PropertyId (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class PropertyId(RootModel[str]): root: Annotated[ str, Field( description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], pattern='^[a-z0-9_]+$', title='Property ID', ), ]Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[str]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : str
class PropertyIdentifier (**data: Any)-
Expand source code
class PropertyIdentifier(RegistryBaseModel): type: Annotated[str, Field(examples=["domain"])] value: Annotated[str, Field(examples=["examplepub.com"])]Base model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar type : strvar value : str
class PropertyRegistry (client: RegistryClient,
*,
auth_token: str | None = None,
poll_interval: float = 60.0,
cursor_store: CursorStore | None = None)-
Expand source code
class PropertyRegistry: """Local cache of property/agent authorization relationships. Queries are synchronous dict lookups — no network calls. Background sync is opt-in via ``auth_token``. Args: client: RegistryClient for API calls. auth_token: Bearer token for change feed access. If omitted, background sync is disabled (load-only mode). poll_interval: Seconds between feed polls (default 60). cursor_store: Optional CursorStore for feed cursor persistence. """ def __init__( self, client: RegistryClient, *, auth_token: str | None = None, poll_interval: float = 60.0, cursor_store: CursorStore | None = None, ) -> None: self._client = client self._auth_token = auth_token self._poll_interval = poll_interval self._cursor_store = cursor_store self._domain_to_agents: dict[str, set[str]] = {} self._agent_to_domains: dict[str, set[str]] = {} self._loaded = False self._sync: RegistrySync | None = None self._task: asyncio.Task[None] | None = None # ------------------------------------------------------------------ # Queries (synchronous, no network) # ------------------------------------------------------------------ def is_authorized(self, agent_url: str, domain: str) -> bool: """Check if an agent is authorized for a domain.""" return agent_url in self._domain_to_agents.get(domain, set()) def get_domains(self, agent_url: str) -> frozenset[str]: """Get all domains authorized for an agent.""" return frozenset(self._agent_to_domains.get(agent_url, set())) def get_agents(self, domain: str) -> frozenset[str]: """Get all agents authorized for a domain.""" return frozenset(self._domain_to_agents.get(domain, set())) @property def agent_count(self) -> int: """Number of agents in the index.""" return len(self._agent_to_domains) @property def domain_count(self) -> int: """Number of domains in the index.""" return len(self._domain_to_agents) @property def loaded(self) -> bool: """Whether initial data has been loaded.""" return self._loaded # ------------------------------------------------------------------ # Lifecycle # ------------------------------------------------------------------ async def load(self) -> None: """Fetch initial state from the registry API. Calls ``list_agents()`` and builds the bidirectional authorization index from each agent's ``publisher_domains``. """ agents = await self._client.list_agents(properties=True) domain_to_agents: dict[str, set[str]] = {} agent_to_domains: dict[str, set[str]] = {} for agent in agents: domains = agent.publisher_domains or [] if domains: agent_to_domains[agent.url] = set(domains) for domain in domains: domain_to_agents.setdefault(domain, set()).add(agent.url) self._domain_to_agents = domain_to_agents self._agent_to_domains = agent_to_domains self._loaded = True logger.info( "PropertyRegistry loaded: %d agents, %d domains", len(agent_to_domains), len(domain_to_agents), ) async def start(self) -> None: """Load initial state and start background sync. If ``auth_token`` was not provided, only loads initial state without starting the polling loop. """ if not self._loaded: await self.load() if self._auth_token is None: logger.info( "PropertyRegistry: no auth_token, background sync disabled" ) return self._sync = RegistrySync( self._client, auth_token=self._auth_token, poll_interval=self._poll_interval, cursor_store=self._cursor_store, types="authorization.*,agent.*,property.*", ) self._sync.on_all(self._handle_event) self._task = asyncio.create_task(self._sync.start()) async def stop(self) -> None: """Stop background sync.""" if self._sync is not None: await self._sync.stop() if self._task is not None: await self._task self._task = None self._sync = None async def __aenter__(self) -> PropertyRegistry: await self.start() return self async def __aexit__(self, *args: object) -> None: await self.stop() async def refresh(self) -> None: """Force a full reload from the API.""" self._domain_to_agents.clear() self._agent_to_domains.clear() self._loaded = False await self.load() # ------------------------------------------------------------------ # Event handling # # Trust model: events are fetched over HTTPS from the registry API # using a Bearer token. The events are not cryptographically signed. # A compromised transport or registry could inject forged events. # ------------------------------------------------------------------ async def _handle_event(self, event: FeedEvent) -> None: """Route feed events to the appropriate handler.""" et = event.event_type if et.startswith("authorization."): self._apply_authorization(event) elif et == "agent.deleted": self._remove_agent(event.entity_id) elif et in ("agent.created", "agent.updated"): await self._refresh_agent(event.payload.get("url", event.entity_id)) elif et == "property.deleted": self._remove_domain(event.entity_id) # Unknown event types: ignore silently (forward compatible) _ADD_TYPES = {"authorization.created", "authorization.granted"} _REMOVE_TYPES = {"authorization.revoked", "authorization.deleted"} def _apply_authorization(self, event: FeedEvent) -> None: """Add or remove an authorization edge.""" agent_url = event.payload.get("agent_url", "") domain = event.payload.get("domain", "") if not agent_url or not domain: return if event.event_type in self._ADD_TYPES: self._domain_to_agents.setdefault(domain, set()).add(agent_url) self._agent_to_domains.setdefault(agent_url, set()).add(domain) elif event.event_type in self._REMOVE_TYPES: self._domain_to_agents.get(domain, set()).discard(agent_url) self._agent_to_domains.get(agent_url, set()).discard(domain) def _remove_agent(self, agent_url: str) -> None: """Remove all authorization edges for an agent.""" domains = self._agent_to_domains.pop(agent_url, set()) for domain in domains: agents = self._domain_to_agents.get(domain) if agents is not None: agents.discard(agent_url) if not agents: del self._domain_to_agents[domain] def _remove_domain(self, domain: str) -> None: """Remove all authorization edges for a domain.""" agents = self._domain_to_agents.pop(domain, set()) for agent_url in agents: domains = self._agent_to_domains.get(agent_url) if domains is not None: domains.discard(domain) if not domains: del self._agent_to_domains[agent_url] async def _refresh_agent(self, agent_url: str) -> None: """Re-fetch a single agent's domains and update indexes.""" try: data = await self._client.get_agent_domains(agent_url) new_domains = { p["domain"] for p in data.get("properties", []) if "domain" in p } except Exception as exc: logger.warning("Failed to refresh agent %s: %s", agent_url, exc) return # Remove old edges old_domains = self._agent_to_domains.get(agent_url, set()) for d in old_domains: s = self._domain_to_agents.get(d) if s is not None: s.discard(agent_url) if not s: del self._domain_to_agents[d] # Add new edges if new_domains: self._agent_to_domains[agent_url] = new_domains for d in new_domains: self._domain_to_agents.setdefault(d, set()).add(agent_url) else: self._agent_to_domains.pop(agent_url, None)Local cache of property/agent authorization relationships.
Queries are synchronous dict lookups — no network calls. Background sync is opt-in via
auth_token.Args
client- RegistryClient for API calls.
auth_token- Bearer token for change feed access. If omitted, background sync is disabled (load-only mode).
poll_interval- Seconds between feed polls (default 60).
cursor_store- Optional CursorStore for feed cursor persistence.
Instance variables
prop agent_count : int-
Expand source code
@property def agent_count(self) -> int: """Number of agents in the index.""" return len(self._agent_to_domains)Number of agents in the index.
prop domain_count : int-
Expand source code
@property def domain_count(self) -> int: """Number of domains in the index.""" return len(self._domain_to_agents)Number of domains in the index.
prop loaded : bool-
Expand source code
@property def loaded(self) -> bool: """Whether initial data has been loaded.""" return self._loadedWhether initial data has been loaded.
Methods
def get_agents(self, domain: str) ‑> frozenset[str]-
Expand source code
def get_agents(self, domain: str) -> frozenset[str]: """Get all agents authorized for a domain.""" return frozenset(self._domain_to_agents.get(domain, set()))Get all agents authorized for a domain.
def get_domains(self, agent_url: str) ‑> frozenset[str]-
Expand source code
def get_domains(self, agent_url: str) -> frozenset[str]: """Get all domains authorized for an agent.""" return frozenset(self._agent_to_domains.get(agent_url, set()))Get all domains authorized for an agent.
-
Expand source code
def is_authorized(self, agent_url: str, domain: str) -> bool: """Check if an agent is authorized for a domain.""" return agent_url in self._domain_to_agents.get(domain, set())Check if an agent is authorized for a domain.
async def load(self) ‑> None-
Expand source code
async def load(self) -> None: """Fetch initial state from the registry API. Calls ``list_agents()`` and builds the bidirectional authorization index from each agent's ``publisher_domains``. """ agents = await self._client.list_agents(properties=True) domain_to_agents: dict[str, set[str]] = {} agent_to_domains: dict[str, set[str]] = {} for agent in agents: domains = agent.publisher_domains or [] if domains: agent_to_domains[agent.url] = set(domains) for domain in domains: domain_to_agents.setdefault(domain, set()).add(agent.url) self._domain_to_agents = domain_to_agents self._agent_to_domains = agent_to_domains self._loaded = True logger.info( "PropertyRegistry loaded: %d agents, %d domains", len(agent_to_domains), len(domain_to_agents), )Fetch initial state from the registry API.
Calls
list_agents()and builds the bidirectional authorization index from each agent'spublisher_domains. async def refresh(self) ‑> None-
Expand source code
async def refresh(self) -> None: """Force a full reload from the API.""" self._domain_to_agents.clear() self._agent_to_domains.clear() self._loaded = False await self.load()Force a full reload from the API.
async def start(self) ‑> None-
Expand source code
async def start(self) -> None: """Load initial state and start background sync. If ``auth_token`` was not provided, only loads initial state without starting the polling loop. """ if not self._loaded: await self.load() if self._auth_token is None: logger.info( "PropertyRegistry: no auth_token, background sync disabled" ) return self._sync = RegistrySync( self._client, auth_token=self._auth_token, poll_interval=self._poll_interval, cursor_store=self._cursor_store, types="authorization.*,agent.*,property.*", ) self._sync.on_all(self._handle_event) self._task = asyncio.create_task(self._sync.start())Load initial state and start background sync.
If
auth_tokenwas not provided, only loads initial state without starting the polling loop. async def stop(self) ‑> None-
Expand source code
async def stop(self) -> None: """Stop background sync.""" if self._sync is not None: await self._sync.stop() if self._task is not None: await self._task self._task = None self._sync = NoneStop background sync.
class PropertyRegistryItem (**data: Any)-
Expand source code
class PropertyRegistryItem(RegistryBaseModel): domain: Annotated[str, Field(examples=["examplepub.com"])] source: PropertyRegistrySource property_count: int agent_count: int verified: boolBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var agent_count : intvar domain : strvar model_configvar property_count : intvar source : PropertyRegistrySourcevar verified : bool
class PropertySummary (**data: Any)-
Expand source code
class PropertySummary(RegistryBaseModel): total_count: int count_by_type: dict[str, int] tags: list[str] publisher_count: intBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var count_by_type : dict[str, int]var model_configvar publisher_count : intvar total_count : int
class PropertyTag (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class PropertyTag(RootModel[str]): root: Annotated[ str, Field( description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], pattern='^[a-z0-9_]+$', title='Property Tag', ), ]Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[str]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : str
class Proposal (**data: Any)-
Expand source code
class Proposal(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) proposal_id: Annotated[ str, Field( description='Unique identifier for this proposal. Used to finalize a draft proposal and to execute a committed proposal via create_media_buy.', max_length=255, ), ] name: Annotated[ str, Field(description='Human-readable name for this media plan proposal', max_length=500) ] description: Annotated[ str | None, Field( description='Explanation of the proposal strategy and what it achieves', max_length=2000 ), ] = None allocations: Annotated[ list[product_allocation.ProductAllocation], Field( description='Budget allocations across products. Allocation percentages MUST sum to 100. Publishers are responsible for ensuring the sum equals 100; buyers SHOULD validate this before execution.', min_length=1, ), ] proposal_status: Annotated[ proposal_status_1.ProposalStatus | None, Field( description="Lifecycle status of this proposal and the per-proposal source of truth for whether finalization is required before create_media_buy. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at and executable via create_media_buy." ), ] = None expires_at: Annotated[ AwareDatetime | None, Field( description='When this proposal expires and can no longer be executed. For draft proposals, indicates when indicative pricing becomes stale. For committed proposals, indicates when the inventory hold lapses — the buyer must call create_media_buy before this time.' ), ] = None insertion_order: Annotated[ insertion_order_1.InsertionOrder | None, Field( description='Formal insertion order attached to a committed proposal. Present when the seller requires a signed agreement before the media buy can proceed. The buyer references the io_id in io_acceptance on create_media_buy.' ), ] = None total_budget_guidance: Annotated[ TotalBudgetGuidance | None, Field(description='Optional budget guidance for this proposal') ] = None brief_alignment: Annotated[ str | None, Field( description='Explanation of how this proposal aligns with the campaign brief', max_length=2000, ), ] = None forecast: Annotated[ delivery_forecast.DeliveryForecast | None, Field( description='Aggregate forecasted delivery metrics for the entire proposal. When both proposal-level and allocation-level forecasts are present, the proposal-level forecast is authoritative for total delivery estimation.' ), ] = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var allocations : list[adcp.types.generated_poc.core.product_allocation.ProductAllocation]var brief_alignment : str | Nonevar description : str | Nonevar expires_at : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar forecast : adcp.types.generated_poc.core.delivery_forecast.DeliveryForecast | Nonevar insertion_order : adcp.types.generated_poc.core.insertion_order.InsertionOrder | Nonevar model_configvar name : strvar proposal_id : strvar proposal_status : adcp.types.generated_poc.enums.proposal_status.ProposalStatus | Nonevar total_budget_guidance : adcp.types.generated_poc.core.proposal.TotalBudgetGuidance | None
Inherited members
class Protocol (*args, **kwds)-
Expand source code
class Protocol(str, Enum): """Supported protocols.""" A2A = "a2a" MCP = "mcp"Supported protocols.
Ancestors
- builtins.str
- enum.Enum
Class variables
var A2Avar MCP
class ProvidePerformanceFeedbackRequest (**data: Any)-
Expand source code
class ProvidePerformanceFeedbackRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) media_buy_id: Annotated[str, Field(description="Seller's media buy identifier", min_length=1)] idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. Prevents duplicate feedback submissions on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] measurement_period: Annotated[ datetime_range.DatetimeRange, Field(description='Time period for performance measurement') ] performance_index: Annotated[ float, Field( description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)', ge=0.0, ), ] package_id: Annotated[ str | None, Field( description='Specific package within the media buy (if feedback is package-specific)', min_length=1, ), ] = None creative_id: Annotated[ str | None, Field( description='Specific creative asset (if feedback is creative-specific)', min_length=1 ), ] = None metric_type: Annotated[ metric_type_1.MetricTypeDeprecated | None, Field(description='The business metric being measured'), ] = metric_type_1.MetricTypeDeprecated.overall_performance feedback_source: Annotated[ feedback_source_1.FeedbackSource | None, Field(description='Source of the performance data') ] = feedback_source_1.FeedbackSource.buyer_attribution context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_id : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar feedback_source : adcp.types.generated_poc.enums.feedback_source.FeedbackSource | Nonevar idempotency_key : strvar measurement_period : adcp.types.generated_poc.core.datetime_range.DatetimeRangevar media_buy_id : strvar metric_type : adcp.types.generated_poc.enums.metric_type.MetricTypeDeprecated | Nonevar model_configvar package_id : str | Nonevar performance_index : float
class ProvidePerformanceFeedbackByBuyerRefRequest (**data: Any)-
Expand source code
class ProvidePerformanceFeedbackRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) media_buy_id: Annotated[str, Field(description="Seller's media buy identifier", min_length=1)] idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. Prevents duplicate feedback submissions on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] measurement_period: Annotated[ datetime_range.DatetimeRange, Field(description='Time period for performance measurement') ] performance_index: Annotated[ float, Field( description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)', ge=0.0, ), ] package_id: Annotated[ str | None, Field( description='Specific package within the media buy (if feedback is package-specific)', min_length=1, ), ] = None creative_id: Annotated[ str | None, Field( description='Specific creative asset (if feedback is creative-specific)', min_length=1 ), ] = None metric_type: Annotated[ metric_type_1.MetricTypeDeprecated | None, Field(description='The business metric being measured'), ] = metric_type_1.MetricTypeDeprecated.overall_performance feedback_source: Annotated[ feedback_source_1.FeedbackSource | None, Field(description='Source of the performance data') ] = feedback_source_1.FeedbackSource.buyer_attribution context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_id : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar feedback_source : adcp.types.generated_poc.enums.feedback_source.FeedbackSource | Nonevar idempotency_key : strvar measurement_period : adcp.types.generated_poc.core.datetime_range.DatetimeRangevar media_buy_id : strvar metric_type : adcp.types.generated_poc.enums.metric_type.MetricTypeDeprecated | Nonevar model_configvar package_id : str | Nonevar performance_index : float
class ProvidePerformanceFeedbackByMediaBuyRequest (**data: Any)-
Expand source code
class ProvidePerformanceFeedbackRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) media_buy_id: Annotated[str, Field(description="Seller's media buy identifier", min_length=1)] idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. Prevents duplicate feedback submissions on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] measurement_period: Annotated[ datetime_range.DatetimeRange, Field(description='Time period for performance measurement') ] performance_index: Annotated[ float, Field( description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)', ge=0.0, ), ] package_id: Annotated[ str | None, Field( description='Specific package within the media buy (if feedback is package-specific)', min_length=1, ), ] = None creative_id: Annotated[ str | None, Field( description='Specific creative asset (if feedback is creative-specific)', min_length=1 ), ] = None metric_type: Annotated[ metric_type_1.MetricTypeDeprecated | None, Field(description='The business metric being measured'), ] = metric_type_1.MetricTypeDeprecated.overall_performance feedback_source: Annotated[ feedback_source_1.FeedbackSource | None, Field(description='Source of the performance data') ] = feedback_source_1.FeedbackSource.buyer_attribution context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_id : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar feedback_source : adcp.types.generated_poc.enums.feedback_source.FeedbackSource | Nonevar idempotency_key : strvar measurement_period : adcp.types.generated_poc.core.datetime_range.DatetimeRangevar media_buy_id : strvar metric_type : adcp.types.generated_poc.enums.metric_type.MetricTypeDeprecated | Nonevar model_configvar package_id : str | Nonevar performance_index : float
Inherited members
class ProvidePerformanceFeedbackSuccessResponse (**data: Any)-
Expand source code
class ProvidePerformanceFeedbackResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') success: Literal[True] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | Nonevar success : Literal[True]
class ProvidePerformanceFeedbackResponse1 (**data: Any)-
Expand source code
class ProvidePerformanceFeedbackResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') success: Literal[True] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | Nonevar success : Literal[True]
Inherited members
class ProvidePerformanceFeedbackErrorResponse (**data: Any)-
Expand source code
class ProvidePerformanceFeedbackResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class PublisherDivergence (**data: Any)-
Expand source code
class PublisherDivergence(AdCPBaseModel): """Divergence record for a single publisher domain. ``missing_in_inline``: property IDs the federated fetch found in the publisher's own adagents.json that the directory did not surface (publisher has properties the directory doesn't know about yet). ``missing_in_federated``: property IDs the directory claims the agent is authorized for but the publisher's own adagents.json does not include (stale directory entry or publisher revocation). Both fields are None in count-only fallback mode (directory did not return ``property_ids[]``). In count-only mode, count-equality does NOT guarantee set-equality — same-count substitutions are undetectable. Use ``?include=properties`` (adcp#4894) on directories that support it for full set-diff precision. ``child_fetch_error`` is non-None when the publisher's adagents.json could not be fetched or parsed; other fields carry no meaning. """ publisher_domain: str directory_properties_authorized: int = Field(ge=0) federated_properties_found: int = Field(ge=0) missing_in_inline: list[str] | None = None missing_in_federated: list[str] | None = None child_fetch_error: str | None = NoneDivergence record for a single publisher domain.
missing_in_inline: property IDs the federated fetch found in the publisher's own adagents.json that the directory did not surface (publisher has properties the directory doesn't know about yet).missing_in_federated: property IDs the directory claims the agent is authorized for but the publisher's own adagents.json does not include (stale directory entry or publisher revocation).Both fields are None in count-only fallback mode (directory did not return
property_ids[]). In count-only mode, count-equality does NOT guarantee set-equality — same-count substitutions are undetectable. Use?include=properties(adcp#4894) on directories that support it for full set-diff precision.child_fetch_erroris non-None when the publisher's adagents.json could not be fetched or parsed; other fields carry no meaning.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var child_fetch_error : str | Nonevar federated_properties_found : intvar missing_in_federated : list[str] | Nonevar missing_in_inline : list[str] | Nonevar model_configvar publisher_domain : str
Inherited members
class PublisherPropertiesAll (**data: Any)-
Expand source code
class PublisherPropertySelector1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) publisher_domain: Annotated[ str | None, Field( description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] = None publisher_domains: Annotated[ list[PublisherDomain] | None, Field( description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", min_length=1, ), ] = None selection_type: Annotated[ Literal['all'], Field( description='Discriminator indicating all properties from each addressed publisher are included' ), ] = 'all'Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar publisher_domain : str | Nonevar publisher_domains : list[adcp.types.generated_poc.core.publisher_property_selector.PublisherDomain] | Nonevar selection_type : Literal['all']
Inherited members
class PublisherPropertiesById (**data: Any)-
Expand source code
class PublisherPropertySelector2(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) publisher_domain: Annotated[ str, Field( description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] selection_type: Annotated[ Literal['by_id'], Field(description='Discriminator indicating selection by specific property IDs'), ] = 'by_id' property_ids: Annotated[ list[property_id.PropertyId], Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar property_ids : list[adcp.types.generated_poc.core.property_id.PropertyId]var publisher_domain : strvar selection_type : Literal['by_id']
Inherited members
class PublisherPropertiesByTag (**data: Any)-
Expand source code
class PublisherPropertySelector3(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) publisher_domain: Annotated[ str | None, Field( description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] = None publisher_domains: Annotated[ list[PublisherDomain] | None, Field( description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", min_length=1, ), ] = None selection_type: Annotated[ Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') ] = 'by_tag' property_tags: Annotated[ list[property_tag.PropertyTag], Field( description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", min_length=1, ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar publisher_domain : str | Nonevar publisher_domains : list[adcp.types.generated_poc.core.publisher_property_selector.PublisherDomain] | Nonevar selection_type : Literal['by_tag']
Inherited members
class PushNotificationConfig (**data: Any)-
Expand source code
class PushNotificationConfig(AdCPBaseModel): url: Annotated[ AnyUrl, Field( description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' ), ] operation_id: Annotated[ str | None, Field( description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", max_length=255, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,255}$', ), ] = None token: Annotated[ str | None, Field( description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", max_length=4096, min_length=16, ), ] = None authentication: Annotated[ Authentication | None, Field( description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var authentication : adcp.types.generated_poc.core.push_notification_config.Authentication | Nonevar model_configvar operation_id : str | Nonevar token : str | Nonevar url : pydantic.networks.AnyUrl
Inherited members
class Refine (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class Refine(RootModel[Refine1 | Refine2 | Refine3]): root: Annotated[Refine1 | Refine2 | Refine3, Field(discriminator='scope')] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[Refine1, Refine2, Refine3]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.media_buy.get_products_request.Refine1 | adcp.types.generated_poc.media_buy.get_products_request.Refine2 | adcp.types.generated_poc.media_buy.get_products_request.Refine3
class RefreshResult (products_unchanged: bool = False,
signals_unchanged: bool = False,
product_count: int = 0,
signal_count: int = 0)-
Expand source code
@dataclass class RefreshResult: """Outcome of a :meth:`FeedMirror.refresh` / :meth:`bootstrap` call. ``unchanged`` is ``True`` when the seller short-circuited every requested feed with ``unchanged: true`` (the replica was not mutated). """ products_unchanged: bool = False signals_unchanged: bool = False product_count: int = 0 signal_count: int = 0 @property def unchanged(self) -> bool: """True when no requested feed reported a change.""" return self.products_unchanged and self.signals_unchangedOutcome of a :meth:
FeedMirror.refresh()/ :meth:bootstrapcall.unchangedisTruewhen the seller short-circuited every requested feed withunchanged: true(the replica was not mutated).Instance variables
var product_count : intvar products_unchanged : boolvar signal_count : intvar signals_unchanged : boolprop unchanged : bool-
Expand source code
@property def unchanged(self) -> bool: """True when no requested feed reported a change.""" return self.products_unchanged and self.signals_unchangedTrue when no requested feed reported a change.
class RegistryClient (base_url: str = 'https://agenticadvertising.org',
timeout: float = 10.0,
client: httpx.AsyncClient | None = None,
user_agent: str = 'adcp-client-python')-
Expand source code
class RegistryClient: """Client for the AdCP registry API. Provides brand, property, and member lookups against the central AdCP registry. Args: base_url: Registry API base URL. timeout: Request timeout in seconds. client: Optional httpx.AsyncClient for connection pooling. If provided, caller is responsible for client lifecycle. user_agent: User-Agent header for requests. """ def __init__( self, base_url: str = DEFAULT_REGISTRY_URL, timeout: float = 10.0, client: httpx.AsyncClient | None = None, user_agent: str = "adcp-client-python", ): self._base_url = base_url.rstrip("/") self._timeout = timeout self._external_client = client self._owned_client: httpx.AsyncClient | None = None self._user_agent = user_agent async def _get_client(self) -> httpx.AsyncClient: """Get or create httpx client.""" if self._external_client is not None: return self._external_client if self._owned_client is None: self._owned_client = httpx.AsyncClient( limits=httpx.Limits( max_keepalive_connections=10, max_connections=20, ), ) return self._owned_client async def close(self) -> None: """Close owned HTTP client. No-op if using external client.""" if self._owned_client is not None: await self._owned_client.aclose() self._owned_client = None async def __aenter__(self) -> RegistryClient: return self async def __aexit__(self, *args: Any) -> None: await self.close() async def _request( self, method: str, path: str, *, params: dict[str, Any] | None = None, json_body: dict[str, Any] | None = None, auth_token: str | None = None, operation: str = "Registry request", allow_404: bool = False, expected_status: int | set[int] = 200, ) -> httpx.Response | None: """Execute a registry API request with standard error handling. Returns None if allow_404=True and the server returns 404. Raises RegistryError for all other non-expected status codes. """ client = await self._get_client() headers: dict[str, str] = {"User-Agent": self._user_agent} if auth_token is not None: headers["Authorization"] = f"Bearer {auth_token}" expected = {expected_status} if isinstance(expected_status, int) else expected_status try: url = f"{self._base_url}{path}" if method == "GET": response = await client.get( url, params=params, headers=headers, timeout=self._timeout, ) elif method == "POST": response = await client.post( url, params=params, json=json_body, headers=headers, timeout=self._timeout, ) else: response = await client.request( method, url, params=params, json=json_body, headers=headers, timeout=self._timeout, ) if allow_404 and response.status_code == 404: return None if response.status_code not in expected: raise RegistryError( f"{operation} failed: HTTP {response.status_code}", status_code=response.status_code, ) return response except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"{operation} timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"{operation} failed: {e}") from e async def _request_ok( self, method: str, path: str, **kwargs: Any, ) -> httpx.Response: """Like _request but guarantees a non-None response. Use for endpoints that never return 404-as-None. """ resp = await self._request(method, path, **kwargs) if resp is None: raise RegistryError( f"{kwargs.get('operation', 'Request')} failed: unexpected empty response" ) return resp async def _request_json( self, method: str, path: str, *, params: dict[str, Any] | None = None, json_body: dict[str, Any] | None = None, auth_token: str | None = None, operation: str = "Registry request", expected_status: int | set[int] = 200, ) -> dict[str, Any]: """Execute a registry request and return the JSON object body.""" resp = await self._request_ok( method, path, params=params, json_body=json_body, auth_token=auth_token, operation=operation, expected_status=expected_status, ) return cast(dict[str, Any], resp.json()) async def _request_text( self, method: str, path: str, *, params: dict[str, Any] | None = None, json_body: dict[str, Any] | None = None, auth_token: str | None = None, operation: str = "Registry request", expected_status: int | set[int] = 200, ) -> str: """Execute a registry request and return the text body.""" resp = await self._request_ok( method, path, params=params, json_body=json_body, auth_token=auth_token, operation=operation, expected_status=expected_status, ) return resp.text @staticmethod def _parse(model_cls: type[_T], data: Any, operation: str) -> _T: """Validate data against a Pydantic model, wrapping errors.""" try: return model_cls.model_validate(data) except (ValidationError, ValueError) as e: raise RegistryError(f"{operation} failed: invalid response: {e}") from e async def lookup_brand(self, domain: str) -> ResolvedBrand | None: """Resolve a domain to its brand identity. Works for any domain — brand houses, sub-brands, and operators (agencies, DSPs) are all brands in the registry. Args: domain: Domain to resolve (e.g., "nike.com", "wpp.com"). Returns: ResolvedBrand if found, None if not in the registry. Raises: RegistryError: On HTTP or parsing errors. Example: brand = await registry.lookup_brand(request.brand.domain) """ client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/brands/resolve", params={"domain": domain}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Brand lookup failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return ResolvedBrand.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Brand lookup timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Brand lookup failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Brand lookup failed: invalid response: {e}") from e async def lookup_brands(self, domains: list[str]) -> dict[str, ResolvedBrand | None]: """Bulk resolve domains to brand identities. Automatically chunks requests exceeding 100 domains. Args: domains: List of domains to resolve. Returns: Dict mapping each domain to its ResolvedBrand, or None if not found. Raises: RegistryError: On HTTP or parsing errors. """ if not domains: return {} chunks = [ domains[i : i + MAX_BULK_DOMAINS] for i in range(0, len(domains), MAX_BULK_DOMAINS) ] chunk_results = await asyncio.gather( *[self._lookup_brands_chunk(chunk) for chunk in chunks] ) merged: dict[str, ResolvedBrand | None] = {} for result in chunk_results: merged.update(result) return merged async def _lookup_brands_chunk(self, domains: list[str]) -> dict[str, ResolvedBrand | None]: """Resolve a single chunk of brand domains (max 100).""" client = await self._get_client() try: response = await client.post( f"{self._base_url}/api/brands/resolve/bulk", json={"domains": domains}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Bulk brand lookup failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() results_raw = data.get("results", {}) results: dict[str, ResolvedBrand | None] = {d: None for d in domains} for domain, brand_data in results_raw.items(): if brand_data is not None: results[domain] = ResolvedBrand.model_validate(brand_data) return results except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Bulk brand lookup timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Bulk brand lookup failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Bulk brand lookup failed: invalid response: {e}") from e async def lookup_property(self, domain: str) -> ResolvedProperty | None: """Resolve a publisher domain to its property info. Args: domain: Publisher domain to resolve (e.g., "nytimes.com"). Returns: ResolvedProperty if found, None if the domain is not in the registry. Raises: RegistryError: On HTTP or parsing errors. """ client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/properties/resolve", params={"domain": domain}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Property lookup failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return ResolvedProperty.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Property lookup timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Property lookup failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Property lookup failed: invalid response: {e}") from e async def lookup_properties(self, domains: list[str]) -> dict[str, ResolvedProperty | None]: """Bulk resolve publisher domains to property info. Automatically chunks requests exceeding 100 domains. Args: domains: List of publisher domains to resolve. Returns: Dict mapping each domain to its ResolvedProperty, or None if not found. Raises: RegistryError: On HTTP or parsing errors. """ if not domains: return {} chunks = [ domains[i : i + MAX_BULK_DOMAINS] for i in range(0, len(domains), MAX_BULK_DOMAINS) ] chunk_results = await asyncio.gather( *[self._lookup_properties_chunk(chunk) for chunk in chunks] ) merged: dict[str, ResolvedProperty | None] = {} for result in chunk_results: merged.update(result) return merged async def _lookup_properties_chunk( self, domains: list[str] ) -> dict[str, ResolvedProperty | None]: """Resolve a single chunk of property domains (max 100).""" client = await self._get_client() try: response = await client.post( f"{self._base_url}/api/properties/resolve/bulk", json={"domains": domains}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Bulk property lookup failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() results_raw = data.get("results", {}) results: dict[str, ResolvedProperty | None] = {d: None for d in domains} for domain, prop_data in results_raw.items(): if prop_data is not None: results[domain] = ResolvedProperty.model_validate(prop_data) return results except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Bulk property lookup timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Bulk property lookup failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Bulk property lookup failed: invalid response: {e}") from e async def list_members(self, limit: int = 100) -> list[Member]: """List organizations registered in the AAO member directory. Args: limit: Maximum number of members to return. Returns: List of Member objects. Raises: RegistryError: On HTTP or parsing errors. """ if limit < 1: raise ValueError(f"limit must be at least 1, got {limit}") client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/members", params={"limit": limit}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Member list failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() return [Member.model_validate(m) for m in data.get("members", [])] except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Member list timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Member list failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Member list failed: invalid response: {e}") from e async def get_member(self, slug: str) -> Member | None: """Get a single AAO member by their slug. Args: slug: Member slug (e.g., "adgentek"). Returns: Member if found, None if not in the registry. Raises: RegistryError: On HTTP or parsing errors. ValueError: If slug contains path-traversal characters. """ if not slug or not re.fullmatch(r"[a-zA-Z0-9_-]+", slug): raise ValueError(f"Invalid member slug: {slug!r}") client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/members/{slug}", headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Member lookup failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return Member.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Member lookup timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Member lookup failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Member lookup failed: invalid response: {e}") from e # ======================================================================== # Policy Registry Operations # ======================================================================== async def list_policies( self, search: str | None = None, category: str | None = None, enforcement: str | None = None, jurisdiction: str | None = None, vertical: str | None = None, domain: str | None = None, limit: int = 20, offset: int = 0, ) -> list[PolicySummary]: """List governance policies with optional filtering. Args: search: Full-text search on policy name and description. category: Filter by category ("regulation" or "standard"). enforcement: Filter by enforcement level ("must", "should", "may"). jurisdiction: Filter by jurisdiction with region alias matching. vertical: Filter by industry vertical. domain: Filter by governance domain ("campaign", "creative", etc.). limit: Results per page (default 20, max 1000). offset: Pagination offset. Returns: List of PolicySummary objects. Raises: RegistryError: On HTTP or parsing errors. """ client = await self._get_client() params: dict[str, str | int] = {"limit": limit, "offset": offset} if search is not None: params["search"] = search if category is not None: params["category"] = category if enforcement is not None: params["enforcement"] = enforcement if jurisdiction is not None: params["jurisdiction"] = jurisdiction if vertical is not None: params["vertical"] = vertical if domain is not None: params["domain"] = domain try: response = await client.get( f"{self._base_url}/api/policies/registry", params=params, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Policy list failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() return [PolicySummary.model_validate(p) for p in data.get("policies", [])] except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Policy list timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Policy list failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Policy list failed: invalid response: {e}") from e async def resolve_policy( self, policy_id: str, version: str | None = None, ) -> Policy | None: """Resolve a single policy by ID. Args: policy_id: Policy identifier (e.g., "gdpr_consent"). version: Optional version pin; returns None if current version differs. Returns: Policy if found, None if not in the registry. Raises: RegistryError: On HTTP or parsing errors. """ client = await self._get_client() params: dict[str, str] = {"policy_id": policy_id} if version is not None: params["version"] = version try: response = await client.get( f"{self._base_url}/api/policies/resolve", params=params, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Policy resolve failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return Policy.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Policy resolve timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Policy resolve failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Policy resolve failed: invalid response: {e}") from e async def resolve_policies( self, policy_ids: list[str], ) -> dict[str, Policy | None]: """Bulk resolve policies by ID. Automatically chunks requests exceeding 100 policy IDs. Args: policy_ids: List of policy identifiers to resolve. Returns: Dict mapping each policy_id to its Policy, or None if not found. Raises: RegistryError: On HTTP or parsing errors. """ if not policy_ids: return {} chunks = [ policy_ids[i : i + MAX_BULK_POLICIES] for i in range(0, len(policy_ids), MAX_BULK_POLICIES) ] chunk_results = await asyncio.gather( *[self._resolve_policies_chunk(chunk) for chunk in chunks] ) merged: dict[str, Policy | None] = {} for result in chunk_results: merged.update(result) return merged async def _resolve_policies_chunk(self, policy_ids: list[str]) -> dict[str, Policy | None]: """Resolve a single chunk of policy IDs (max 100).""" client = await self._get_client() try: response = await client.post( f"{self._base_url}/api/policies/resolve/bulk", json={"policy_ids": policy_ids}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Bulk policy resolve failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() results_raw = data.get("results", {}) results: dict[str, Policy | None] = {pid: None for pid in policy_ids} for pid, policy_data in results_raw.items(): if policy_data is not None: results[pid] = Policy.model_validate(policy_data) return results except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Bulk policy resolve timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Bulk policy resolve failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Bulk policy resolve failed: invalid response: {e}") from e async def policy_history( self, policy_id: str, limit: int = 20, offset: int = 0, ) -> PolicyHistory | None: """Retrieve edit history for a policy. Args: policy_id: Policy identifier. limit: Maximum revisions to return (default 20, max 100). offset: Pagination offset. Returns: PolicyHistory if found, None if the policy doesn't exist. Raises: RegistryError: On HTTP or parsing errors. """ client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/policies/history", params={"policy_id": policy_id, "limit": limit, "offset": offset}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Policy history failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return PolicyHistory.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Policy history timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Policy history failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Policy history failed: invalid response: {e}") from e async def save_policy( self, policy_id: str, version: str, name: str, category: str, enforcement: str, policy: str, *, auth_token: str, description: str | None = None, jurisdictions: list[str] | None = None, region_aliases: dict[str, list[str]] | None = None, verticals: list[str] | None = None, channels: list[str] | None = None, effective_date: str | None = None, sunset_date: str | None = None, governance_domains: list[str] | None = None, source_url: str | None = None, source_name: str | None = None, guidance: str | None = None, exemplars: dict[str, Any] | None = None, ext: dict[str, Any] | None = None, ) -> dict[str, Any]: """Create or update a community-contributed policy. Requires authentication. Cannot edit registry-sourced or pending policies. Args: policy_id: Policy identifier (lowercase alphanumeric with underscores). version: Semantic version string. name: Human-readable policy name. category: "regulation" or "standard". enforcement: "must", "should", or "may". policy: Natural language policy text. auth_token: API key for authentication. description: Policy description. jurisdictions: ISO jurisdiction codes. region_aliases: Region alias mappings (e.g., {"EU": ["DE", "FR"]}). verticals: Industry verticals. channels: Media channels. effective_date: ISO 8601 date when enforcement begins. sunset_date: ISO 8601 date when enforcement ends. governance_domains: Applicable domains ("campaign", "creative", etc.). source_url: URL of the source regulation/standard. source_name: Name of the source. guidance: Implementation guidance text. exemplars: Pass/fail calibration scenarios. ext: Extension data. Returns: Dict with success, message, policy_id, and revision_number. Raises: RegistryError: On HTTP or parsing errors (400, 401, 409, 429). """ client = await self._get_client() body: dict[str, Any] = { "policy_id": policy_id, "version": version, "name": name, "category": category, "enforcement": enforcement, "policy": policy, } for key, value in [ ("description", description), ("jurisdictions", jurisdictions), ("region_aliases", region_aliases), ("verticals", verticals), ("channels", channels), ("effective_date", effective_date), ("sunset_date", sunset_date), ("governance_domains", governance_domains), ("source_url", source_url), ("source_name", source_name), ("guidance", guidance), ("exemplars", exemplars), ("ext", ext), ]: if value is not None: body[key] = value try: response = await client.post( f"{self._base_url}/api/policies/save", json=body, headers={ "User-Agent": self._user_agent, "Authorization": f"Bearer {auth_token}", }, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Policy save failed: HTTP {response.status_code}", status_code=response.status_code, ) result: dict[str, Any] = response.json() return result except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Policy save timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Policy save failed: {e}") from e # ======================================================================== # Brand Registry Operations # ======================================================================== async def get_brand_json(self, domain: str, *, fresh: bool = False) -> dict[str, Any] | None: """Fetch raw brand.json for a domain.""" params: dict[str, Any] = {"domain": domain} if fresh: params["fresh"] = "true" resp = await self._request( "GET", "/api/brands/brand-json", params=params, allow_404=True, operation="Brand JSON fetch", ) if resp is None: return None return cast(dict[str, Any], resp.json()) async def save_brand( self, domain: str, brand_name: str, *, auth_token: str, brand_manifest: dict[str, Any] | None = None, ) -> dict[str, Any]: """Save or update a brand in the registry (auth required).""" body: dict[str, Any] = {"domain": domain, "brand_name": brand_name} if brand_manifest is not None: body["brand_manifest"] = brand_manifest resp = await self._request_ok( "POST", "/api/brands/save", json_body=body, auth_token=auth_token, operation="Brand save", ) return cast(dict[str, Any], resp.json()) async def list_brands( self, search: str | None = None, limit: int = 100, offset: int = 0, ) -> list[BrandRegistryItem]: """List brands in the registry.""" params: dict[str, Any] = {"limit": limit, "offset": offset} if search is not None: params["search"] = search resp = await self._request_ok( "GET", "/api/brands/registry", params=params, operation="Brand list", ) data = resp.json() return [self._parse(BrandRegistryItem, b, "Brand list") for b in data.get("brands", [])] async def brand_history( self, domain: str, limit: int = 20, offset: int = 0, ) -> BrandActivity | None: """Get edit history for a brand.""" resp = await self._request( "GET", "/api/brands/history", params={"domain": domain, "limit": limit, "offset": offset}, allow_404=True, operation="Brand history", ) if resp is None: return None return self._parse(BrandActivity, resp.json(), "Brand history") async def enrich_brand(self, domain: str) -> dict[str, Any]: """Enrich brand data using Brandfetch.""" resp = await self._request_ok( "GET", "/api/brands/enrich", params={"domain": domain}, operation="Brand enrich", ) return cast(dict[str, Any], resp.json()) # ======================================================================== # Property Registry Operations # ======================================================================== async def list_properties( self, search: str | None = None, limit: int = 100, offset: int = 0, ) -> list[PropertyRegistryItem]: """List properties in the registry.""" params: dict[str, Any] = {"limit": limit, "offset": offset} if search is not None: params["search"] = search resp = await self._request_ok( "GET", "/api/properties/registry", params=params, operation="Property list", ) data = resp.json() return [ self._parse(PropertyRegistryItem, p, "Property list") for p in data.get("properties", []) ] async def validate_property(self, domain: str) -> ValidationResult: """Validate a domain's adagents.json configuration.""" resp = await self._request_ok( "GET", "/api/properties/validate", params={"domain": domain}, operation="Property validate", ) return self._parse(ValidationResult, resp.json(), "Property validate") async def save_property( self, publisher_domain: str, authorized_agents: list[dict[str, Any]], *, auth_token: str, properties: list[dict[str, Any]] | None = None, contact: dict[str, str] | None = None, ) -> dict[str, Any]: """Save or update a hosted property (auth required).""" body: dict[str, Any] = { "publisher_domain": publisher_domain, "authorized_agents": authorized_agents, } if properties is not None: body["properties"] = properties if contact is not None: body["contact"] = contact resp = await self._request_ok( "POST", "/api/properties/save", json_body=body, auth_token=auth_token, operation="Property save", ) return cast(dict[str, Any], resp.json()) async def property_history( self, domain: str, limit: int = 20, offset: int = 0, ) -> PropertyActivity | None: """Get edit history for a property.""" resp = await self._request( "GET", "/api/properties/history", params={"domain": domain, "limit": limit, "offset": offset}, allow_404=True, operation="Property history", ) if resp is None: return None return self._parse(PropertyActivity, resp.json(), "Property history") async def check_property_list(self, domains: list[str]) -> dict[str, Any]: """Check publisher domains against the registry.""" resp = await self._request_ok( "POST", "/api/properties/check", json_body={"domains": domains}, operation="Property check", ) return cast(dict[str, Any], resp.json()) async def get_property_check_report(self, report_id: str) -> dict[str, Any] | None: """Retrieve a property check report by ID.""" resp = await self._request( "GET", f"/api/properties/check/{url_quote(report_id, safe='')}", allow_404=True, operation="Property check report", ) if resp is None: return None return cast(dict[str, Any], resp.json()) async def verify_hosted_property_origin( self, domain: str, *, auth_token: str | None = None, ) -> dict[str, Any]: """Verify a hosted property's origin adagents.json delegation.""" return await self._request_json( "POST", f"/api/properties/hosted/{url_quote(domain, safe='')}/verify-origin", auth_token=auth_token, operation="Hosted property origin verification", ) # ======================================================================== # Agent Discovery # ======================================================================== async def list_agents( self, *, type: str | None = None, health: bool = False, capabilities: bool = False, properties: bool = False, compliance: bool = False, metric_id: str | list[str] | None = None, accreditation: str | list[str] | None = None, q: str | None = None, verification_mode: str | list[str] | None = None, verified: bool = False, ) -> list[FederatedAgentWithDetails]: """List registered and discovered agents. Measurement filters (``metric_id``, ``accreditation``, ``q``) imply ``type=measurement`` on the registry when ``type`` is omitted. """ params: dict[str, Any] = {} if type is not None: params["type"] = type if health: params["health"] = "true" if capabilities: params["capabilities"] = "true" if properties: params["properties"] = "true" if compliance: params["compliance"] = "true" if metric_id is not None: params["metric_id"] = metric_id if accreditation is not None: params["accreditation"] = accreditation if q is not None: params["q"] = q if verification_mode is not None: params["verification_mode"] = verification_mode if verified: params["verified"] = "true" resp = await self._request_ok( "GET", "/api/registry/agents", params=params, operation="Agent list", ) data = resp.json() return [ self._parse(FederatedAgentWithDetails, a, "Agent list") for a in data.get("agents", []) ] async def list_publishers(self) -> list[FederatedPublisher]: """List publishers in the registry.""" resp = await self._request_ok( "GET", "/api/registry/publishers", operation="Publisher list", ) data = resp.json() return [ self._parse(FederatedPublisher, p, "Publisher list") for p in data.get("publishers", []) ] async def get_registry_stats(self) -> dict[str, Any]: """Get aggregate registry statistics.""" resp = await self._request_ok( "GET", "/api/registry/stats", operation="Registry stats", ) return cast(dict[str, Any], resp.json()) async def search_agents( self, *, auth_token: str, channels: str | None = None, property_types: str | None = None, markets: str | None = None, categories: str | None = None, tags: str | None = None, delivery_types: str | None = None, has_tmp: bool | None = None, min_properties: int | None = None, cursor: str | None = None, limit: int = 50, ) -> dict[str, Any]: """Search agents by inventory profile (auth required).""" params: dict[str, Any] = {"limit": limit} for key, val in [ ("channels", channels), ("property_types", property_types), ("markets", markets), ("categories", categories), ("tags", tags), ("delivery_types", delivery_types), ("cursor", cursor), ]: if val is not None: params[key] = val if has_tmp is not None: params["has_tmp"] = str(has_tmp).lower() if min_properties is not None: params["min_properties"] = min_properties resp = await self._request_ok( "GET", "/api/registry/agents/search", params=params, auth_token=auth_token, operation="Agent search", ) return cast(dict[str, Any], resp.json()) async def request_crawl(self, domain: str, *, auth_token: str) -> dict[str, Any]: """Request a domain re-crawl (auth required).""" resp = await self._request_ok( "POST", "/api/registry/crawl-request", json_body={"domain": domain}, auth_token=auth_token, operation="Crawl request", expected_status={200, 202}, ) return cast(dict[str, Any], resp.json()) async def request_manager_revalidation( self, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Request manager revalidation for registry-managed data.""" return await self._request_json( "POST", "/api/registry/manager-revalidation-request", json_body=dict(body), auth_token=auth_token, operation="Manager revalidation request", expected_status={200, 202}, ) async def request_brand_crawl( self, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Request a brand crawl through the registry.""" return await self._request_json( "POST", "/api/registry/brand-crawl-request", json_body=dict(body), auth_token=auth_token, operation="Brand crawl request", expected_status={200, 202}, ) # ======================================================================== # Lookups & Authorization # ======================================================================== async def lookup_domain(self, domain: str) -> DomainLookupResult: """Find all agents authorized for a publisher domain.""" resp = await self._request_ok( "GET", f"/api/registry/lookup/domain/{url_quote(domain, safe='')}", operation="Domain lookup", ) return self._parse(DomainLookupResult, resp.json(), "Domain lookup") async def lookup_property_identifier(self, type: str, value: str) -> dict[str, Any]: """Find agents holding a specific property identifier.""" resp = await self._request_ok( "GET", "/api/registry/lookup/property", params={"type": type, "value": value}, operation="Property identifier lookup", ) return cast(dict[str, Any], resp.json()) async def get_agent_domains(self, agent_url: str) -> dict[str, Any]: """Get all publisher domains and identifiers for an agent.""" encoded = url_quote(agent_url, safe="") resp = await self._request_ok( "GET", f"/api/registry/lookup/agent/{encoded}/domains", operation="Agent domains lookup", ) return cast(dict[str, Any], resp.json()) async def get_publishers_for_agent( self, agent_url: str, *, since: str | None = None, cursor: str | None = None, status: str | None = None, include: str | None = None, limit: int | None = None, legacy_api_prefix: bool = False, ) -> dict[str, Any]: """List publishers associated with an agent URL.""" encoded = url_quote(agent_url, safe="") params = { k: v for k, v in { "since": since, "cursor": cursor, "status": status, "include": include, "limit": limit, }.items() if v is not None } prefix = "/api/v1" if legacy_api_prefix else "/v1" return await self._request_json( "GET", f"{prefix}/agents/{encoded}/publishers", params=params, operation="Publishers for agent", ) async def lookup_operator( self, domain: str, *, scope: str | None = None, ) -> dict[str, Any]: """Resolve registry operator metadata for a domain.""" params: dict[str, Any] = {"domain": domain} if scope is not None: params["scope"] = scope return await self._request_json( "GET", "/api/registry/operator", params=params, operation="Operator lookup", ) async def lookup_publisher(self, domain: str) -> dict[str, Any]: """Resolve registry publisher metadata for a domain.""" return await self._request_json( "GET", "/api/registry/publisher", params={"domain": domain}, operation="Publisher lookup", ) async def lookup_publisher_agent_authorization( self, domain: str, agent: str, ) -> dict[str, Any]: """Resolve whether a publisher authorizes an agent.""" return await self._request_json( "GET", "/api/registry/publisher/authorization", params={"domain": domain, "agent": agent}, operation="Publisher agent authorization lookup", ) async def get_agent_authorizations( self, agent_url: str, *, auth_token: str | None = None, include: str | None = None, evidence: str | None = None, ) -> dict[str, Any]: """Fetch authorization rows for one agent.""" params = {"agent_url": agent_url} if include is not None: params["include"] = include if evidence is not None: params["evidence"] = evidence return await self._request_json( "GET", "/api/registry/authorizations", params=params, auth_token=auth_token, operation="Agent authorizations", ) async def get_agent_authorizations_snapshot( self, *, auth_token: str | None = None, include: str | None = None, evidence: str | None = None, ) -> dict[str, Any]: """Fetch the full authorization snapshot metadata.""" params = {k: v for k, v in {"include": include, "evidence": evidence}.items() if v} return await self._request_json( "GET", "/api/registry/authorizations/snapshot", params=params, auth_token=auth_token, operation="Agent authorizations snapshot", ) async def validate_product_authorization( self, agent_url: str, publisher_properties: list[dict[str, Any]], ) -> dict[str, Any]: """Check whether an agent is authorized to sell products.""" resp = await self._request_ok( "POST", "/api/registry/validate/product-authorization", json_body={ "agent_url": agent_url, "publisher_properties": publisher_properties, }, operation="Product authorization", ) return cast(dict[str, Any], resp.json()) async def expand_product_identifiers( self, agent_url: str, publisher_properties: list[dict[str, Any]], ) -> dict[str, Any]: """Expand publisher_properties selectors into concrete identifiers.""" resp = await self._request_ok( "POST", "/api/registry/expand/product-identifiers", json_body={ "agent_url": agent_url, "publisher_properties": publisher_properties, }, operation="Expand product identifiers", ) return cast(dict[str, Any], resp.json()) async def validate_property_authorization( self, agent_url: str, identifier_type: str, identifier_value: str, ) -> dict[str, Any]: """Quick check if a property identifier is authorized for an agent.""" resp = await self._request_ok( "GET", "/api/registry/validate/property-authorization", params={ "agent_url": agent_url, "identifier_type": identifier_type, "identifier_value": identifier_value, }, operation="Property authorization", ) return cast(dict[str, Any], resp.json()) # ======================================================================== # Validation Tools # ======================================================================== async def validate_adagents(self, domain: str) -> dict[str, Any]: """Validate a domain's adagents.json via the registry API.""" resp = await self._request_ok( "POST", "/api/adagents/validate", json_body={"domain": domain}, operation="Adagents validate", ) return cast(dict[str, Any], resp.json()) async def create_adagents( self, authorized_agents: list[dict[str, Any]], *, include_schema: bool = False, include_timestamp: bool = False, properties: list[Any] | None = None, ) -> CreateAdagentsResponse: """Generate a valid adagents.json from authorized agents.""" body: dict[str, Any] = {"authorized_agents": authorized_agents} if include_schema: body["include_schema"] = True if include_timestamp: body["include_timestamp"] = True if properties is not None: body["properties"] = properties resp = await self._request_ok( "POST", "/api/adagents/create", json_body=body, operation="Adagents create", ) return self._parse(CreateAdagentsResponse, resp.json(), "Adagents create") # ======================================================================== # Community Mirror Lifecycle # ======================================================================== async def publish_community_mirror_adagents( self, platform: str, config: dict[str, Any], *, auth_token: str, ) -> CommunityMirrorPublishResponse: """Publish or update a catalog-only community mirror adagents.json descriptor. Persists the mirror under ``PUT /api/registry/mirrors/{platform}``. Use ``create_adagents`` (the generator endpoint) when you only need to validate or preview the document without saving it. The publish body is catalog-only; the service forces ``authorized_agents: []``. Args: platform: Stable platform key. Trimmed/lowercased and validated against ``^[a-z0-9_-]{1,64}$``. config: Catalog config (see ``build_community_mirror_adagents``). Any ``properties[].platform`` values must match ``platform``. auth_token: Bearer token required for save operations. Returns: The publish response. Raises: RegistryError: On platform/catalog validation or HTTP errors. """ normalized_platform = _normalize_community_mirror_platform(platform) catalog = build_community_mirror_adagents(config) self._assert_community_mirror_properties_match_platform(normalized_platform, catalog) resp = await self._request_ok( "PUT", f"/api/registry/mirrors/{url_quote(normalized_platform, safe='')}", json_body=catalog, auth_token=auth_token, operation="Community mirror publish", ) return self._parse(CommunityMirrorPublishResponse, resp.json(), "Community mirror publish") async def get_community_mirror_adagents( self, platform: str ) -> CommunityMirrorGetResponse | None: """Retrieve a published catalog-only community mirror adagents.json descriptor. Fetches ``GET /api/registry/mirrors/{platform}``. The response carries the platform metadata (``platform``, ``catalog_etag``, ``superseded_by``, ``created_at``, ``updated_at``) plus the stored catalog-only ``adagents_json`` document. ``superseded_by`` is reported at both the wrapper level and on ``adagents_json`` by the service, so no hydration is needed. The catalog-only invariant (``authorized_agents`` empty) is enforced by the response model. Args: platform: Platform key. Trimmed/lowercased and validated. Returns: The mirror response, or ``None`` if no mirror exists (HTTP 404). Raises: RegistryError: If the registry returns a mismatched platform or an invalid (non-catalog) mirror body, or on other HTTP errors. """ normalized_platform = _normalize_community_mirror_platform(platform) resp = await self._request( "GET", f"/api/registry/mirrors/{url_quote(normalized_platform, safe='')}", operation="Community mirror fetch", allow_404=True, ) if resp is None: return None mirror = self._parse(CommunityMirrorGetResponse, resp.json(), "Community mirror fetch") if mirror.platform != normalized_platform: raise RegistryError("Registry returned mismatched community mirror platform") return mirror async def list_community_mirror_adagents( self, *, limit: int | None = None, offset: int | None = None, ) -> CommunityMirrorListResponse: """List published community mirror catalogs with their current etags. Fetches ``GET /api/registry/mirrors``. The list projection includes presence and freshness metadata but omits the full ``adagents_json`` body; fetch a platform-specific mirror for the full document. Args: limit: Optional page size. The service defaults to 100 and clamps values to the 1-500 range. offset: Optional zero-based result offset. The service defaults to 0 and clamps negative values to 0. Returns: The list response (``mirrors`` summaries plus ``total``). """ params: dict[str, Any] = {} if limit is not None: params["limit"] = limit if offset is not None: params["offset"] = offset resp = await self._request_ok( "GET", "/api/registry/mirrors", params=params or None, operation="Community mirror list", ) return self._parse(CommunityMirrorListResponse, resp.json(), "Community mirror list") async def upsert_community_mirror_adagents( self, config: dict[str, Any], *, platform: str | None = None, auth_token: str, ) -> CommunityMirrorPublishResponse: """Publish or update a community mirror, inferring the platform key. The platform key is resolved from the ``platform`` argument, then ``config["platform"]``, then a single consistent ``properties[].platform`` value. Ambiguous property platforms raise an error. Args: config: Catalog config (see ``build_community_mirror_adagents``). platform: Explicit platform key. Takes precedence over inference. auth_token: Bearer token required for save operations. Returns: The publish response. Raises: RegistryError: If a platform key cannot be resolved, property platforms are ambiguous, or on validation/HTTP errors. """ resolved_platform = ( platform if platform is not None else self._community_mirror_platform_from_config(config) ) return await self.publish_community_mirror_adagents( resolved_platform, config, auth_token=auth_token ) async def delete_community_mirror_adagents( self, platform: str, *, force: bool = False, auth_token: str, ) -> CommunityMirrorDeleteResponse: """Delete a published community mirror and retire its derived rows. Issues ``DELETE /api/registry/mirrors/{platform}``. Without ``force``, the service refuses (HTTP 409) to delete a mirror that has not first published a ``superseded_by`` migration URL; set ``force=True`` to delete anyway. Args: platform: Platform key. Trimmed/lowercased and validated. force: Delete a mirror that has no ``superseded_by`` migration URL. auth_token: Bearer token required for delete operations. Returns: The delete response. Raises: RegistryError: If the mirror has not been superseded and ``force`` is not set (HTTP 409), or on other platform/HTTP errors. """ normalized_platform = _normalize_community_mirror_platform(platform) params = {"force": "true"} if force else None resp = await self._request_ok( "DELETE", f"/api/registry/mirrors/{url_quote(normalized_platform, safe='')}", params=params, auth_token=auth_token, operation="Community mirror delete", ) return self._parse(CommunityMirrorDeleteResponse, resp.json(), "Community mirror delete") def _community_mirror_platform_from_config(self, config: dict[str, Any]) -> str: """Infer the platform key from a community mirror config.""" config_platform = config.get("platform") if isinstance(config_platform, str) and config_platform.strip(): return config_platform properties = config.get("properties") if isinstance(properties, list): platforms: set[str] = set() for prop in properties: if not isinstance(prop, dict): continue prop_platform = prop.get("platform") if isinstance(prop_platform, str) and prop_platform.strip(): platforms.add(_normalize_community_mirror_platform(prop_platform)) if len(platforms) == 1: return next(iter(platforms)) if len(platforms) > 1: raise RegistryError( "platform is ambiguous; pass " "upsert_community_mirror_adagents(config, platform=...)" ) raise RegistryError("platform is required for community mirror publish") def _assert_community_mirror_properties_match_platform( self, normalized_platform: str, catalog: dict[str, Any], ) -> None: """Reject catalogs whose property platforms disagree with the key.""" properties = catalog.get("properties") if not isinstance(properties, list): return for prop in properties: if not isinstance(prop, dict): continue prop_platform = prop.get("platform") if prop_platform is None: continue if ( not isinstance(prop_platform, str) or _normalize_community_mirror_platform(prop_platform) != normalized_platform ): raise RegistryError(f"properties[].platform must match {normalized_platform}") # ======================================================================== # Search # ======================================================================== async def api_discovery(self) -> dict[str, Any]: """Get API discovery info (links to entry points and docs).""" resp = await self._request_ok( "GET", "/api", operation="API discovery", ) return cast(dict[str, Any], resp.json()) async def search(self, q: str) -> dict[str, Any]: """Search across brands, publishers, and properties.""" resp = await self._request_ok( "GET", "/api/search", params={"q": q}, operation="Search", ) return cast(dict[str, Any], resp.json()) async def lookup_manifest_ref(self, domain: str, *, type: str | None = None) -> dict[str, Any]: """Find the best manifest reference for a domain.""" params: dict[str, Any] = {"domain": domain} if type is not None: params["type"] = type resp = await self._request_ok( "GET", "/api/manifest-refs/lookup", params=params, operation="Manifest ref lookup", ) return cast(dict[str, Any], resp.json()) # ======================================================================== # Agent Probing # ======================================================================== async def discover_agent(self, url: str) -> dict[str, Any]: """Probe an agent URL to discover its capabilities.""" resp = await self._request_ok( "GET", "/api/public/discover-agent", params={"url": url}, operation="Agent discovery", ) return cast(dict[str, Any], resp.json()) async def get_agent_formats(self, url: str) -> dict[str, Any]: """Fetch creative formats from an agent.""" resp = await self._request_ok( "GET", "/api/public/agent-formats", params={"url": url}, operation="Agent formats", ) return cast(dict[str, Any], resp.json()) async def get_agent_products(self, url: str) -> dict[str, Any]: """Fetch products from a sales agent.""" resp = await self._request_ok( "GET", "/api/public/agent-products", params={"url": url}, operation="Agent products", ) return cast(dict[str, Any], resp.json()) async def validate_publisher(self, domain: str) -> dict[str, Any]: """Validate a publisher domain's adagents.json and return stats.""" resp = await self._request_ok( "GET", "/api/public/validate-publisher", params={"domain": domain}, operation="Publisher validation", ) return cast(dict[str, Any], resp.json()) # ======================================================================== # Compliance, Verification, and Member Management # ======================================================================== @staticmethod def _encoded_agent_url(agent_url: str) -> str: return url_quote(agent_url, safe="") async def get_agent_compliance(self, agent_url: str) -> dict[str, Any]: """Fetch the latest compliance summary for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/compliance", operation="Agent compliance", ) async def get_jwks(self) -> dict[str, Any]: """Fetch the registry JWKS document.""" return await self._request_json( "GET", "/api/.well-known/jwks.json", operation="Registry JWKS", ) async def get_agent_verification(self, agent_url: str) -> dict[str, Any]: """Fetch the active verification badges for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/verification", operation="Agent verification", ) async def get_agent_badge_svg(self, agent_url: str, role: str) -> str: """Fetch an agent verification badge SVG.""" encoded = self._encoded_agent_url(agent_url) return await self._request_text( "GET", f"/api/registry/agents/{encoded}/badge/{url_quote(role, safe='')}.svg", operation="Agent badge SVG", ) async def get_agent_badge_embed(self, agent_url: str, role: str) -> dict[str, Any]: """Fetch embeddable badge HTML/Markdown for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/badge/{url_quote(role, safe='')}/embed", operation="Agent badge embed", ) async def get_agent_badge_versioned_svg( self, agent_url: str, role: str, version: str, ) -> str: """Fetch a version-pinned agent verification badge SVG.""" encoded = self._encoded_agent_url(agent_url) return await self._request_text( "GET", "/api/registry/agents/" f"{encoded}/badge/{url_quote(role, safe='')}/{url_quote(version, safe='')}.svg", operation="Versioned agent badge SVG", ) async def get_agent_badge_versioned_embed( self, agent_url: str, role: str, version: str, ) -> dict[str, Any]: """Fetch version-pinned embeddable badge HTML/Markdown for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", "/api/registry/agents/" f"{encoded}/badge/{url_quote(role, safe='')}/{url_quote(version, safe='')}/embed", operation="Versioned agent badge embed", ) async def get_agent_storyboard_status(self, agent_url: str) -> dict[str, Any]: """Fetch latest storyboard status for one agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/storyboard-status", operation="Agent storyboard status", ) async def bulk_agent_storyboard_status( self, *, auth_token: str | None = None, **body: Any, ) -> dict[str, Any]: """Fetch storyboard status for multiple agents.""" return await self._request_json( "POST", "/api/registry/agents/storyboard-status", json_body=dict(body), auth_token=auth_token, operation="Bulk agent storyboard status", ) async def get_agent_compliance_history( self, agent_url: str, *, limit: int | None = None, ) -> dict[str, Any]: """Fetch compliance run history for an agent.""" encoded = self._encoded_agent_url(agent_url) params = {"limit": limit} if limit is not None else None return await self._request_json( "GET", f"/api/registry/agents/{encoded}/compliance/history", params=params, operation="Agent compliance history", ) async def update_agent_lifecycle( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Update an agent lifecycle stage.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/lifecycle", json_body=dict(body), auth_token=auth_token, operation="Agent lifecycle update", ) async def update_agent_compliance_opt_out( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Update agent compliance opt-out state.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/compliance/opt-out", json_body=dict(body), auth_token=auth_token, operation="Agent compliance opt-out update", ) async def get_agent_monitoring_settings(self, agent_url: str) -> dict[str, Any]: """Fetch monitoring settings for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/monitoring/settings", operation="Agent monitoring settings", ) async def update_agent_monitoring_pause( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Pause or resume monitoring for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/monitoring/pause", json_body=dict(body), auth_token=auth_token, operation="Agent monitoring pause update", ) async def update_agent_monitoring_interval( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Update monitoring interval for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/monitoring/interval", json_body=dict(body), auth_token=auth_token, operation="Agent monitoring interval update", ) async def requeue_agent_for_heartbeat( self, agent_url: str, *, auth_token: str, ) -> dict[str, Any]: """Requeue an agent for heartbeat monitoring.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", f"/api/registry/agents/{encoded}/monitoring/requeue", auth_token=auth_token, operation="Agent heartbeat requeue", ) async def get_agent_compliance_step_diagnostics( self, agent_url: str, *, run_id: str | None = None, limit: int | None = None, ) -> dict[str, Any]: """Fetch compliance step diagnostics for an agent.""" encoded = self._encoded_agent_url(agent_url) params = {k: v for k, v in {"run_id": run_id, "limit": limit}.items() if v is not None} return await self._request_json( "GET", f"/api/registry/agents/{encoded}/compliance/diagnostics", params=params, operation="Agent compliance step diagnostics", ) async def get_agent_monitoring_requests( self, agent_url: str, *, limit: int | None = None, since: str | None = None, ) -> dict[str, Any]: """Fetch monitoring requests for an agent.""" encoded = self._encoded_agent_url(agent_url) params = {k: v for k, v in {"limit": limit, "since": since}.items() if v is not None} return await self._request_json( "GET", f"/api/registry/agents/{encoded}/monitoring/requests", params=params, operation="Agent monitoring requests", ) async def refresh_agent( self, agent_url: str, *, auth_token: str, ) -> dict[str, Any]: """Refresh an agent's registry health/capability/compliance snapshot.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", f"/api/registry/agents/{encoded}/refresh", auth_token=auth_token, operation="Agent refresh", expected_status={200, 202}, ) async def get_agent_auth_status(self, agent_url: str) -> dict[str, Any]: """Fetch saved authentication status for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/auth-status", operation="Agent auth status", ) async def connect_agent( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Connect registry-managed credentials for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/connect", json_body=dict(body), auth_token=auth_token, operation="Agent connect", ) async def save_agent_oauth_client_credentials( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Save OAuth client credentials for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/oauth-client-credentials", json_body=dict(body), auth_token=auth_token, operation="Agent OAuth client credentials save", ) async def test_agent_oauth_client_credentials( self, agent_url: str, *, auth_token: str, ) -> dict[str, Any]: """Test saved OAuth client credentials for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", f"/api/registry/agents/{encoded}/oauth-client-credentials/test", auth_token=auth_token, operation="Agent OAuth client credentials test", ) async def get_applicable_storyboards(self, agent_url: str) -> dict[str, Any]: """Resolve compliance storyboards applicable to an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/applicable-storyboards", operation="Applicable storyboards", ) async def list_storyboards( self, *, category: str | None = None, compliance_target: str | None = None, ) -> dict[str, Any]: """List available registry compliance storyboards.""" params = { k: v for k, v in {"category": category, "compliance_target": compliance_target}.items() if v is not None } return await self._request_json( "GET", "/api/storyboards", params=params, operation="Storyboard list", ) async def get_storyboard( self, storyboard_id: str, *, compliance_target: str | None = None, ) -> dict[str, Any]: """Fetch one registry compliance storyboard.""" params = {"compliance_target": compliance_target} if compliance_target is not None else None return await self._request_json( "GET", f"/api/storyboards/{url_quote(storyboard_id, safe='')}", params=params, operation="Storyboard get", ) async def find_brand(self, q: str, *, limit: int | None = None) -> dict[str, Any]: """Find brands by name or domain.""" params: dict[str, Any] = {"q": q} if limit is not None: params["limit"] = limit return await self._request_json( "GET", "/api/brands/find", params=params, operation="Brand find", ) async def setup_my_brand(self, *, auth_token: str, **body: Any) -> dict[str, Any]: """Set up a brand record for the authenticated member.""" return await self._request_json( "POST", "/api/brands/setup-my-brand", json_body=dict(body), auth_token=auth_token, operation="Brand setup", ) async def bulk_property_check( self, *, auth_token: str | None = None, **body: Any, ) -> dict[str, Any]: """Start a bulk property check.""" return await self._request_json( "POST", "/api/properties/check/bulk", json_body=dict(body), auth_token=auth_token, operation="Bulk property check", expected_status={200, 202}, ) async def get_bulk_property_check_report(self, report_id: str) -> dict[str, Any]: """Retrieve a bulk property check report by ID.""" return await self._request_json( "GET", f"/api/properties/check/bulk/{url_quote(report_id, safe='')}", operation="Bulk property check report", ) async def run_storyboard_step( self, agent_url: str, storyboard_id: str, step_id: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Run one storyboard step against an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", "/api/registry/agents/" f"{encoded}/storyboard/{url_quote(storyboard_id, safe='')}/step/" f"{url_quote(step_id, safe='')}", json_body=dict(body), auth_token=auth_token, operation="Storyboard step run", ) async def get_storyboard_first_step( self, storyboard_id: str, *, compliance_target: str | None = None, ) -> dict[str, Any]: """Fetch the first runnable step for a storyboard.""" params = {"compliance_target": compliance_target} if compliance_target is not None else None return await self._request_json( "GET", f"/api/storyboards/{url_quote(storyboard_id, safe='')}/first-step", params=params, operation="Storyboard first step", ) async def run_storyboard( self, agent_url: str, storyboard_id: str, *, auth_token: str, ) -> dict[str, Any]: """Run a storyboard against an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", f"/api/registry/agents/{encoded}/storyboard/{url_quote(storyboard_id, safe='')}/run", auth_token=auth_token, operation="Storyboard run", expected_status={200, 202}, ) async def compare_storyboard( self, agent_url: str, storyboard_id: str, *, auth_token: str, ) -> dict[str, Any]: """Compare storyboard runs for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", "/api/registry/agents/" f"{encoded}/storyboard/{url_quote(storyboard_id, safe='')}/compare", auth_token=auth_token, operation="Storyboard compare", ) async def list_member_agents( self, *, auth_token: str, org: str | None = None, ) -> dict[str, Any]: """List the authenticated member's registered agents.""" params = {"org": org} if org is not None else None return await self._request_json( "GET", "/api/me/agents", params=params, auth_token=auth_token, operation="Member agent list", ) async def register_member_agent( self, *, auth_token: str, org: str | None = None, **body: Any, ) -> dict[str, Any]: """Register an agent for the authenticated member.""" params = {"org": org} if org is not None else None return await self._request_json( "POST", "/api/me/agents", params=params, json_body=dict(body), auth_token=auth_token, operation="Member agent register", expected_status={200, 201}, ) async def update_member_agent( self, url: str, *, auth_token: str, org: str | None = None, **body: Any, ) -> dict[str, Any]: """Update one member-owned agent.""" params = {"org": org} if org is not None else None return await self._request_json( "PATCH", f"/api/me/agents/{url_quote(url, safe='')}", params=params, json_body=dict(body), auth_token=auth_token, operation="Member agent update", ) async def remove_member_agent( self, url: str, *, auth_token: str, org: str | None = None, ) -> dict[str, Any]: """Remove one member-owned agent.""" params = {"org": org} if org is not None else None return await self._request_json( "DELETE", f"/api/me/agents/{url_quote(url, safe='')}", params=params, auth_token=auth_token, operation="Member agent remove", ) async def create_organization(self, *, auth_token: str, **body: Any) -> dict[str, Any]: """Create an organization for the authenticated member.""" return await self._request_json( "POST", "/api/organizations", json_body=dict(body), auth_token=auth_token, operation="Organization create", expected_status={200, 201}, ) # ======================================================================== # Change Feed # ======================================================================== async def get_feed( self, *, auth_token: str, cursor: str | None = None, types: str | None = None, limit: int = 100, ) -> FeedPage: """Poll the registry change feed (auth required). Returns a FeedPage with events, cursor, and has_more. Pass cursor from previous response to resume. """ params: dict[str, Any] = {"limit": limit} if cursor is not None: params["cursor"] = cursor if types is not None: params["types"] = types resp = await self._request_ok( "GET", "/api/registry/feed", params=params, auth_token=auth_token, operation="Feed poll", ) return self._parse(FeedPage, resp.json(), "Feed poll")Client for the AdCP registry API.
Provides brand, property, and member lookups against the central AdCP registry.
Args
base_url- Registry API base URL.
timeout- Request timeout in seconds.
client- Optional httpx.AsyncClient for connection pooling. If provided, caller is responsible for client lifecycle.
user_agent- User-Agent header for requests.
Methods
async def api_discovery(self) ‑> dict[str, typing.Any]-
Expand source code
async def api_discovery(self) -> dict[str, Any]: """Get API discovery info (links to entry points and docs).""" resp = await self._request_ok( "GET", "/api", operation="API discovery", ) return cast(dict[str, Any], resp.json())Get API discovery info (links to entry points and docs).
async def brand_history(self, domain: str, limit: int = 20, offset: int = 0) ‑> BrandActivity | None-
Expand source code
async def brand_history( self, domain: str, limit: int = 20, offset: int = 0, ) -> BrandActivity | None: """Get edit history for a brand.""" resp = await self._request( "GET", "/api/brands/history", params={"domain": domain, "limit": limit, "offset": offset}, allow_404=True, operation="Brand history", ) if resp is None: return None return self._parse(BrandActivity, resp.json(), "Brand history")Get edit history for a brand.
async def bulk_agent_storyboard_status(self, *, auth_token: str | None = None, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def bulk_agent_storyboard_status( self, *, auth_token: str | None = None, **body: Any, ) -> dict[str, Any]: """Fetch storyboard status for multiple agents.""" return await self._request_json( "POST", "/api/registry/agents/storyboard-status", json_body=dict(body), auth_token=auth_token, operation="Bulk agent storyboard status", )Fetch storyboard status for multiple agents.
async def bulk_property_check(self, *, auth_token: str | None = None, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def bulk_property_check( self, *, auth_token: str | None = None, **body: Any, ) -> dict[str, Any]: """Start a bulk property check.""" return await self._request_json( "POST", "/api/properties/check/bulk", json_body=dict(body), auth_token=auth_token, operation="Bulk property check", expected_status={200, 202}, )Start a bulk property check.
async def check_property_list(self, domains: list[str]) ‑> dict[str, typing.Any]-
Expand source code
async def check_property_list(self, domains: list[str]) -> dict[str, Any]: """Check publisher domains against the registry.""" resp = await self._request_ok( "POST", "/api/properties/check", json_body={"domains": domains}, operation="Property check", ) return cast(dict[str, Any], resp.json())Check publisher domains against the registry.
async def close(self) ‑> None-
Expand source code
async def close(self) -> None: """Close owned HTTP client. No-op if using external client.""" if self._owned_client is not None: await self._owned_client.aclose() self._owned_client = NoneClose owned HTTP client. No-op if using external client.
async def compare_storyboard(self, agent_url: str, storyboard_id: str, *, auth_token: str) ‑> dict[str, typing.Any]-
Expand source code
async def compare_storyboard( self, agent_url: str, storyboard_id: str, *, auth_token: str, ) -> dict[str, Any]: """Compare storyboard runs for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", "/api/registry/agents/" f"{encoded}/storyboard/{url_quote(storyboard_id, safe='')}/compare", auth_token=auth_token, operation="Storyboard compare", )Compare storyboard runs for an agent.
async def connect_agent(self, agent_url: str, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def connect_agent( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Connect registry-managed credentials for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/connect", json_body=dict(body), auth_token=auth_token, operation="Agent connect", )Connect registry-managed credentials for an agent.
async def create_adagents(self,
authorized_agents: list[dict[str, Any]],
*,
include_schema: bool = False,
include_timestamp: bool = False,
properties: list[Any] | None = None) ‑> CreateAdagentsResponse-
Expand source code
async def create_adagents( self, authorized_agents: list[dict[str, Any]], *, include_schema: bool = False, include_timestamp: bool = False, properties: list[Any] | None = None, ) -> CreateAdagentsResponse: """Generate a valid adagents.json from authorized agents.""" body: dict[str, Any] = {"authorized_agents": authorized_agents} if include_schema: body["include_schema"] = True if include_timestamp: body["include_timestamp"] = True if properties is not None: body["properties"] = properties resp = await self._request_ok( "POST", "/api/adagents/create", json_body=body, operation="Adagents create", ) return self._parse(CreateAdagentsResponse, resp.json(), "Adagents create")Generate a valid adagents.json from authorized agents.
async def create_organization(self, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def create_organization(self, *, auth_token: str, **body: Any) -> dict[str, Any]: """Create an organization for the authenticated member.""" return await self._request_json( "POST", "/api/organizations", json_body=dict(body), auth_token=auth_token, operation="Organization create", expected_status={200, 201}, )Create an organization for the authenticated member.
async def delete_community_mirror_adagents(self, platform: str, *, force: bool = False, auth_token: str) ‑> CommunityMirrorDeleteResponse-
Expand source code
async def delete_community_mirror_adagents( self, platform: str, *, force: bool = False, auth_token: str, ) -> CommunityMirrorDeleteResponse: """Delete a published community mirror and retire its derived rows. Issues ``DELETE /api/registry/mirrors/{platform}``. Without ``force``, the service refuses (HTTP 409) to delete a mirror that has not first published a ``superseded_by`` migration URL; set ``force=True`` to delete anyway. Args: platform: Platform key. Trimmed/lowercased and validated. force: Delete a mirror that has no ``superseded_by`` migration URL. auth_token: Bearer token required for delete operations. Returns: The delete response. Raises: RegistryError: If the mirror has not been superseded and ``force`` is not set (HTTP 409), or on other platform/HTTP errors. """ normalized_platform = _normalize_community_mirror_platform(platform) params = {"force": "true"} if force else None resp = await self._request_ok( "DELETE", f"/api/registry/mirrors/{url_quote(normalized_platform, safe='')}", params=params, auth_token=auth_token, operation="Community mirror delete", ) return self._parse(CommunityMirrorDeleteResponse, resp.json(), "Community mirror delete")Delete a published community mirror and retire its derived rows.
Issues
DELETE /api/registry/mirrors/{platform}. Withoutforce, the service refuses (HTTP 409) to delete a mirror that has not first published asuperseded_bymigration URL; setforce=Trueto delete anyway.Args
platform- Platform key. Trimmed/lowercased and validated.
force- Delete a mirror that has no
superseded_bymigration URL. auth_token- Bearer token required for delete operations.
Returns
The delete response.
Raises
RegistryError- If the mirror has not been superseded and
forceis not set (HTTP 409), or on other platform/HTTP errors.
async def discover_agent(self, url: str) ‑> dict[str, typing.Any]-
Expand source code
async def discover_agent(self, url: str) -> dict[str, Any]: """Probe an agent URL to discover its capabilities.""" resp = await self._request_ok( "GET", "/api/public/discover-agent", params={"url": url}, operation="Agent discovery", ) return cast(dict[str, Any], resp.json())Probe an agent URL to discover its capabilities.
async def enrich_brand(self, domain: str) ‑> dict[str, typing.Any]-
Expand source code
async def enrich_brand(self, domain: str) -> dict[str, Any]: """Enrich brand data using Brandfetch.""" resp = await self._request_ok( "GET", "/api/brands/enrich", params={"domain": domain}, operation="Brand enrich", ) return cast(dict[str, Any], resp.json())Enrich brand data using Brandfetch.
async def expand_product_identifiers(self, agent_url: str, publisher_properties: list[dict[str, Any]]) ‑> dict[str, typing.Any]-
Expand source code
async def expand_product_identifiers( self, agent_url: str, publisher_properties: list[dict[str, Any]], ) -> dict[str, Any]: """Expand publisher_properties selectors into concrete identifiers.""" resp = await self._request_ok( "POST", "/api/registry/expand/product-identifiers", json_body={ "agent_url": agent_url, "publisher_properties": publisher_properties, }, operation="Expand product identifiers", ) return cast(dict[str, Any], resp.json())Expand publisher_properties selectors into concrete identifiers.
async def find_brand(self, q: str, *, limit: int | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def find_brand(self, q: str, *, limit: int | None = None) -> dict[str, Any]: """Find brands by name or domain.""" params: dict[str, Any] = {"q": q} if limit is not None: params["limit"] = limit return await self._request_json( "GET", "/api/brands/find", params=params, operation="Brand find", )Find brands by name or domain.
async def get_agent_auth_status(self, agent_url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_auth_status(self, agent_url: str) -> dict[str, Any]: """Fetch saved authentication status for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/auth-status", operation="Agent auth status", )Fetch saved authentication status for an agent.
-
Expand source code
async def get_agent_authorizations( self, agent_url: str, *, auth_token: str | None = None, include: str | None = None, evidence: str | None = None, ) -> dict[str, Any]: """Fetch authorization rows for one agent.""" params = {"agent_url": agent_url} if include is not None: params["include"] = include if evidence is not None: params["evidence"] = evidence return await self._request_json( "GET", "/api/registry/authorizations", params=params, auth_token=auth_token, operation="Agent authorizations", )Fetch authorization rows for one agent.
-
Expand source code
async def get_agent_authorizations_snapshot( self, *, auth_token: str | None = None, include: str | None = None, evidence: str | None = None, ) -> dict[str, Any]: """Fetch the full authorization snapshot metadata.""" params = {k: v for k, v in {"include": include, "evidence": evidence}.items() if v} return await self._request_json( "GET", "/api/registry/authorizations/snapshot", params=params, auth_token=auth_token, operation="Agent authorizations snapshot", )Fetch the full authorization snapshot metadata.
async def get_agent_badge_embed(self, agent_url: str, role: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_badge_embed(self, agent_url: str, role: str) -> dict[str, Any]: """Fetch embeddable badge HTML/Markdown for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/badge/{url_quote(role, safe='')}/embed", operation="Agent badge embed", )Fetch embeddable badge HTML/Markdown for an agent.
async def get_agent_badge_svg(self, agent_url: str, role: str) ‑> str-
Expand source code
async def get_agent_badge_svg(self, agent_url: str, role: str) -> str: """Fetch an agent verification badge SVG.""" encoded = self._encoded_agent_url(agent_url) return await self._request_text( "GET", f"/api/registry/agents/{encoded}/badge/{url_quote(role, safe='')}.svg", operation="Agent badge SVG", )Fetch an agent verification badge SVG.
async def get_agent_badge_versioned_embed(self, agent_url: str, role: str, version: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_badge_versioned_embed( self, agent_url: str, role: str, version: str, ) -> dict[str, Any]: """Fetch version-pinned embeddable badge HTML/Markdown for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", "/api/registry/agents/" f"{encoded}/badge/{url_quote(role, safe='')}/{url_quote(version, safe='')}/embed", operation="Versioned agent badge embed", )Fetch version-pinned embeddable badge HTML/Markdown for an agent.
async def get_agent_badge_versioned_svg(self, agent_url: str, role: str, version: str) ‑> str-
Expand source code
async def get_agent_badge_versioned_svg( self, agent_url: str, role: str, version: str, ) -> str: """Fetch a version-pinned agent verification badge SVG.""" encoded = self._encoded_agent_url(agent_url) return await self._request_text( "GET", "/api/registry/agents/" f"{encoded}/badge/{url_quote(role, safe='')}/{url_quote(version, safe='')}.svg", operation="Versioned agent badge SVG", )Fetch a version-pinned agent verification badge SVG.
async def get_agent_compliance(self, agent_url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_compliance(self, agent_url: str) -> dict[str, Any]: """Fetch the latest compliance summary for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/compliance", operation="Agent compliance", )Fetch the latest compliance summary for an agent.
async def get_agent_compliance_history(self, agent_url: str, *, limit: int | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_compliance_history( self, agent_url: str, *, limit: int | None = None, ) -> dict[str, Any]: """Fetch compliance run history for an agent.""" encoded = self._encoded_agent_url(agent_url) params = {"limit": limit} if limit is not None else None return await self._request_json( "GET", f"/api/registry/agents/{encoded}/compliance/history", params=params, operation="Agent compliance history", )Fetch compliance run history for an agent.
async def get_agent_compliance_step_diagnostics(self, agent_url: str, *, run_id: str | None = None, limit: int | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_compliance_step_diagnostics( self, agent_url: str, *, run_id: str | None = None, limit: int | None = None, ) -> dict[str, Any]: """Fetch compliance step diagnostics for an agent.""" encoded = self._encoded_agent_url(agent_url) params = {k: v for k, v in {"run_id": run_id, "limit": limit}.items() if v is not None} return await self._request_json( "GET", f"/api/registry/agents/{encoded}/compliance/diagnostics", params=params, operation="Agent compliance step diagnostics", )Fetch compliance step diagnostics for an agent.
async def get_agent_domains(self, agent_url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_domains(self, agent_url: str) -> dict[str, Any]: """Get all publisher domains and identifiers for an agent.""" encoded = url_quote(agent_url, safe="") resp = await self._request_ok( "GET", f"/api/registry/lookup/agent/{encoded}/domains", operation="Agent domains lookup", ) return cast(dict[str, Any], resp.json())Get all publisher domains and identifiers for an agent.
async def get_agent_formats(self, url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_formats(self, url: str) -> dict[str, Any]: """Fetch creative formats from an agent.""" resp = await self._request_ok( "GET", "/api/public/agent-formats", params={"url": url}, operation="Agent formats", ) return cast(dict[str, Any], resp.json())Fetch creative formats from an agent.
async def get_agent_monitoring_requests(self, agent_url: str, *, limit: int | None = None, since: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_monitoring_requests( self, agent_url: str, *, limit: int | None = None, since: str | None = None, ) -> dict[str, Any]: """Fetch monitoring requests for an agent.""" encoded = self._encoded_agent_url(agent_url) params = {k: v for k, v in {"limit": limit, "since": since}.items() if v is not None} return await self._request_json( "GET", f"/api/registry/agents/{encoded}/monitoring/requests", params=params, operation="Agent monitoring requests", )Fetch monitoring requests for an agent.
async def get_agent_monitoring_settings(self, agent_url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_monitoring_settings(self, agent_url: str) -> dict[str, Any]: """Fetch monitoring settings for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/monitoring/settings", operation="Agent monitoring settings", )Fetch monitoring settings for an agent.
async def get_agent_products(self, url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_products(self, url: str) -> dict[str, Any]: """Fetch products from a sales agent.""" resp = await self._request_ok( "GET", "/api/public/agent-products", params={"url": url}, operation="Agent products", ) return cast(dict[str, Any], resp.json())Fetch products from a sales agent.
async def get_agent_storyboard_status(self, agent_url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_storyboard_status(self, agent_url: str) -> dict[str, Any]: """Fetch latest storyboard status for one agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/storyboard-status", operation="Agent storyboard status", )Fetch latest storyboard status for one agent.
async def get_agent_verification(self, agent_url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_agent_verification(self, agent_url: str) -> dict[str, Any]: """Fetch the active verification badges for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/verification", operation="Agent verification", )Fetch the active verification badges for an agent.
async def get_applicable_storyboards(self, agent_url: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_applicable_storyboards(self, agent_url: str) -> dict[str, Any]: """Resolve compliance storyboards applicable to an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "GET", f"/api/registry/agents/{encoded}/applicable-storyboards", operation="Applicable storyboards", )Resolve compliance storyboards applicable to an agent.
async def get_brand_json(self, domain: str, *, fresh: bool = False) ‑> dict[str, typing.Any] | None-
Expand source code
async def get_brand_json(self, domain: str, *, fresh: bool = False) -> dict[str, Any] | None: """Fetch raw brand.json for a domain.""" params: dict[str, Any] = {"domain": domain} if fresh: params["fresh"] = "true" resp = await self._request( "GET", "/api/brands/brand-json", params=params, allow_404=True, operation="Brand JSON fetch", ) if resp is None: return None return cast(dict[str, Any], resp.json())Fetch raw brand.json for a domain.
async def get_bulk_property_check_report(self, report_id: str) ‑> dict[str, typing.Any]-
Expand source code
async def get_bulk_property_check_report(self, report_id: str) -> dict[str, Any]: """Retrieve a bulk property check report by ID.""" return await self._request_json( "GET", f"/api/properties/check/bulk/{url_quote(report_id, safe='')}", operation="Bulk property check report", )Retrieve a bulk property check report by ID.
async def get_community_mirror_adagents(self, platform: str) ‑> CommunityMirrorGetResponse | None-
Expand source code
async def get_community_mirror_adagents( self, platform: str ) -> CommunityMirrorGetResponse | None: """Retrieve a published catalog-only community mirror adagents.json descriptor. Fetches ``GET /api/registry/mirrors/{platform}``. The response carries the platform metadata (``platform``, ``catalog_etag``, ``superseded_by``, ``created_at``, ``updated_at``) plus the stored catalog-only ``adagents_json`` document. ``superseded_by`` is reported at both the wrapper level and on ``adagents_json`` by the service, so no hydration is needed. The catalog-only invariant (``authorized_agents`` empty) is enforced by the response model. Args: platform: Platform key. Trimmed/lowercased and validated. Returns: The mirror response, or ``None`` if no mirror exists (HTTP 404). Raises: RegistryError: If the registry returns a mismatched platform or an invalid (non-catalog) mirror body, or on other HTTP errors. """ normalized_platform = _normalize_community_mirror_platform(platform) resp = await self._request( "GET", f"/api/registry/mirrors/{url_quote(normalized_platform, safe='')}", operation="Community mirror fetch", allow_404=True, ) if resp is None: return None mirror = self._parse(CommunityMirrorGetResponse, resp.json(), "Community mirror fetch") if mirror.platform != normalized_platform: raise RegistryError("Registry returned mismatched community mirror platform") return mirrorRetrieve a published catalog-only community mirror adagents.json descriptor.
Fetches
GET /api/registry/mirrors/{platform}. The response carries the platform metadata (platform,catalog_etag,superseded_by,created_at,updated_at) plus the stored catalog-onlyadagents_jsondocument.superseded_byis reported at both the wrapper level and onadagents_jsonby the service, so no hydration is needed. The catalog-only invariant (authorized_agentsempty) is enforced by the response model.Args
platform- Platform key. Trimmed/lowercased and validated.
Returns
The mirror response, or
Noneif no mirror exists (HTTP 404).Raises
RegistryError- If the registry returns a mismatched platform or an invalid (non-catalog) mirror body, or on other HTTP errors.
async def get_feed(self,
*,
auth_token: str,
cursor: str | None = None,
types: str | None = None,
limit: int = 100) ‑> FeedPage-
Expand source code
async def get_feed( self, *, auth_token: str, cursor: str | None = None, types: str | None = None, limit: int = 100, ) -> FeedPage: """Poll the registry change feed (auth required). Returns a FeedPage with events, cursor, and has_more. Pass cursor from previous response to resume. """ params: dict[str, Any] = {"limit": limit} if cursor is not None: params["cursor"] = cursor if types is not None: params["types"] = types resp = await self._request_ok( "GET", "/api/registry/feed", params=params, auth_token=auth_token, operation="Feed poll", ) return self._parse(FeedPage, resp.json(), "Feed poll")Poll the registry change feed (auth required).
Returns a FeedPage with events, cursor, and has_more. Pass cursor from previous response to resume.
async def get_jwks(self) ‑> dict[str, typing.Any]-
Expand source code
async def get_jwks(self) -> dict[str, Any]: """Fetch the registry JWKS document.""" return await self._request_json( "GET", "/api/.well-known/jwks.json", operation="Registry JWKS", )Fetch the registry JWKS document.
async def get_member(self, slug: str) ‑> Member | None-
Expand source code
async def get_member(self, slug: str) -> Member | None: """Get a single AAO member by their slug. Args: slug: Member slug (e.g., "adgentek"). Returns: Member if found, None if not in the registry. Raises: RegistryError: On HTTP or parsing errors. ValueError: If slug contains path-traversal characters. """ if not slug or not re.fullmatch(r"[a-zA-Z0-9_-]+", slug): raise ValueError(f"Invalid member slug: {slug!r}") client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/members/{slug}", headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Member lookup failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return Member.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Member lookup timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Member lookup failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Member lookup failed: invalid response: {e}") from eGet a single AAO member by their slug.
Args
slug- Member slug (e.g., "adgentek").
Returns
Member if found, None if not in the registry.
Raises
RegistryError- On HTTP or parsing errors.
ValueError- If slug contains path-traversal characters.
async def get_property_check_report(self, report_id: str) ‑> dict[str, typing.Any] | None-
Expand source code
async def get_property_check_report(self, report_id: str) -> dict[str, Any] | None: """Retrieve a property check report by ID.""" resp = await self._request( "GET", f"/api/properties/check/{url_quote(report_id, safe='')}", allow_404=True, operation="Property check report", ) if resp is None: return None return cast(dict[str, Any], resp.json())Retrieve a property check report by ID.
async def get_publishers_for_agent(self,
agent_url: str,
*,
since: str | None = None,
cursor: str | None = None,
status: str | None = None,
include: str | None = None,
limit: int | None = None,
legacy_api_prefix: bool = False) ‑> dict[str, typing.Any]-
Expand source code
async def get_publishers_for_agent( self, agent_url: str, *, since: str | None = None, cursor: str | None = None, status: str | None = None, include: str | None = None, limit: int | None = None, legacy_api_prefix: bool = False, ) -> dict[str, Any]: """List publishers associated with an agent URL.""" encoded = url_quote(agent_url, safe="") params = { k: v for k, v in { "since": since, "cursor": cursor, "status": status, "include": include, "limit": limit, }.items() if v is not None } prefix = "/api/v1" if legacy_api_prefix else "/v1" return await self._request_json( "GET", f"{prefix}/agents/{encoded}/publishers", params=params, operation="Publishers for agent", )List publishers associated with an agent URL.
async def get_registry_stats(self) ‑> dict[str, typing.Any]-
Expand source code
async def get_registry_stats(self) -> dict[str, Any]: """Get aggregate registry statistics.""" resp = await self._request_ok( "GET", "/api/registry/stats", operation="Registry stats", ) return cast(dict[str, Any], resp.json())Get aggregate registry statistics.
async def get_storyboard(self, storyboard_id: str, *, compliance_target: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def get_storyboard( self, storyboard_id: str, *, compliance_target: str | None = None, ) -> dict[str, Any]: """Fetch one registry compliance storyboard.""" params = {"compliance_target": compliance_target} if compliance_target is not None else None return await self._request_json( "GET", f"/api/storyboards/{url_quote(storyboard_id, safe='')}", params=params, operation="Storyboard get", )Fetch one registry compliance storyboard.
async def get_storyboard_first_step(self, storyboard_id: str, *, compliance_target: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def get_storyboard_first_step( self, storyboard_id: str, *, compliance_target: str | None = None, ) -> dict[str, Any]: """Fetch the first runnable step for a storyboard.""" params = {"compliance_target": compliance_target} if compliance_target is not None else None return await self._request_json( "GET", f"/api/storyboards/{url_quote(storyboard_id, safe='')}/first-step", params=params, operation="Storyboard first step", )Fetch the first runnable step for a storyboard.
async def list_agents(self,
*,
type: str | None = None,
health: bool = False,
capabilities: bool = False,
properties: bool = False,
compliance: bool = False,
metric_id: str | list[str] | None = None,
accreditation: str | list[str] | None = None,
q: str | None = None,
verification_mode: str | list[str] | None = None,
verified: bool = False) ‑> list[FederatedAgentWithDetails]-
Expand source code
async def list_agents( self, *, type: str | None = None, health: bool = False, capabilities: bool = False, properties: bool = False, compliance: bool = False, metric_id: str | list[str] | None = None, accreditation: str | list[str] | None = None, q: str | None = None, verification_mode: str | list[str] | None = None, verified: bool = False, ) -> list[FederatedAgentWithDetails]: """List registered and discovered agents. Measurement filters (``metric_id``, ``accreditation``, ``q``) imply ``type=measurement`` on the registry when ``type`` is omitted. """ params: dict[str, Any] = {} if type is not None: params["type"] = type if health: params["health"] = "true" if capabilities: params["capabilities"] = "true" if properties: params["properties"] = "true" if compliance: params["compliance"] = "true" if metric_id is not None: params["metric_id"] = metric_id if accreditation is not None: params["accreditation"] = accreditation if q is not None: params["q"] = q if verification_mode is not None: params["verification_mode"] = verification_mode if verified: params["verified"] = "true" resp = await self._request_ok( "GET", "/api/registry/agents", params=params, operation="Agent list", ) data = resp.json() return [ self._parse(FederatedAgentWithDetails, a, "Agent list") for a in data.get("agents", []) ]List registered and discovered agents.
Measurement filters (
metric_id,accreditation,q) implytype=measurementon the registry whentypeis omitted. async def list_brands(self, search: str | None = None, limit: int = 100, offset: int = 0) ‑> list[BrandRegistryItem]-
Expand source code
async def list_brands( self, search: str | None = None, limit: int = 100, offset: int = 0, ) -> list[BrandRegistryItem]: """List brands in the registry.""" params: dict[str, Any] = {"limit": limit, "offset": offset} if search is not None: params["search"] = search resp = await self._request_ok( "GET", "/api/brands/registry", params=params, operation="Brand list", ) data = resp.json() return [self._parse(BrandRegistryItem, b, "Brand list") for b in data.get("brands", [])]List brands in the registry.
async def list_community_mirror_adagents(self, *, limit: int | None = None, offset: int | None = None) ‑> CommunityMirrorListResponse-
Expand source code
async def list_community_mirror_adagents( self, *, limit: int | None = None, offset: int | None = None, ) -> CommunityMirrorListResponse: """List published community mirror catalogs with their current etags. Fetches ``GET /api/registry/mirrors``. The list projection includes presence and freshness metadata but omits the full ``adagents_json`` body; fetch a platform-specific mirror for the full document. Args: limit: Optional page size. The service defaults to 100 and clamps values to the 1-500 range. offset: Optional zero-based result offset. The service defaults to 0 and clamps negative values to 0. Returns: The list response (``mirrors`` summaries plus ``total``). """ params: dict[str, Any] = {} if limit is not None: params["limit"] = limit if offset is not None: params["offset"] = offset resp = await self._request_ok( "GET", "/api/registry/mirrors", params=params or None, operation="Community mirror list", ) return self._parse(CommunityMirrorListResponse, resp.json(), "Community mirror list")List published community mirror catalogs with their current etags.
Fetches
GET /api/registry/mirrors. The list projection includes presence and freshness metadata but omits the fulladagents_jsonbody; fetch a platform-specific mirror for the full document.Args
limit- Optional page size. The service defaults to 100 and clamps values to the 1-500 range.
offset- Optional zero-based result offset. The service defaults to 0 and clamps negative values to 0.
Returns
The list response (
mirrorssummaries plustotal). async def list_member_agents(self, *, auth_token: str, org: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def list_member_agents( self, *, auth_token: str, org: str | None = None, ) -> dict[str, Any]: """List the authenticated member's registered agents.""" params = {"org": org} if org is not None else None return await self._request_json( "GET", "/api/me/agents", params=params, auth_token=auth_token, operation="Member agent list", )List the authenticated member's registered agents.
async def list_members(self, limit: int = 100) ‑> list[Member]-
Expand source code
async def list_members(self, limit: int = 100) -> list[Member]: """List organizations registered in the AAO member directory. Args: limit: Maximum number of members to return. Returns: List of Member objects. Raises: RegistryError: On HTTP or parsing errors. """ if limit < 1: raise ValueError(f"limit must be at least 1, got {limit}") client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/members", params={"limit": limit}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Member list failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() return [Member.model_validate(m) for m in data.get("members", [])] except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Member list timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Member list failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Member list failed: invalid response: {e}") from eList organizations registered in the AAO member directory.
Args
limit- Maximum number of members to return.
Returns
List of Member objects.
Raises
RegistryError- On HTTP or parsing errors.
async def list_policies(self,
search: str | None = None,
category: str | None = None,
enforcement: str | None = None,
jurisdiction: str | None = None,
vertical: str | None = None,
domain: str | None = None,
limit: int = 20,
offset: int = 0) ‑> list[PolicySummary]-
Expand source code
async def list_policies( self, search: str | None = None, category: str | None = None, enforcement: str | None = None, jurisdiction: str | None = None, vertical: str | None = None, domain: str | None = None, limit: int = 20, offset: int = 0, ) -> list[PolicySummary]: """List governance policies with optional filtering. Args: search: Full-text search on policy name and description. category: Filter by category ("regulation" or "standard"). enforcement: Filter by enforcement level ("must", "should", "may"). jurisdiction: Filter by jurisdiction with region alias matching. vertical: Filter by industry vertical. domain: Filter by governance domain ("campaign", "creative", etc.). limit: Results per page (default 20, max 1000). offset: Pagination offset. Returns: List of PolicySummary objects. Raises: RegistryError: On HTTP or parsing errors. """ client = await self._get_client() params: dict[str, str | int] = {"limit": limit, "offset": offset} if search is not None: params["search"] = search if category is not None: params["category"] = category if enforcement is not None: params["enforcement"] = enforcement if jurisdiction is not None: params["jurisdiction"] = jurisdiction if vertical is not None: params["vertical"] = vertical if domain is not None: params["domain"] = domain try: response = await client.get( f"{self._base_url}/api/policies/registry", params=params, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Policy list failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() return [PolicySummary.model_validate(p) for p in data.get("policies", [])] except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Policy list timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Policy list failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Policy list failed: invalid response: {e}") from eList governance policies with optional filtering.
Args
search- Full-text search on policy name and description.
category- Filter by category ("regulation" or "standard").
enforcement- Filter by enforcement level ("must", "should", "may").
jurisdiction- Filter by jurisdiction with region alias matching.
vertical- Filter by industry vertical.
domain- Filter by governance domain ("campaign", "creative", etc.).
limit- Results per page (default 20, max 1000).
offset- Pagination offset.
Returns
List of PolicySummary objects.
Raises
RegistryError- On HTTP or parsing errors.
async def list_properties(self, search: str | None = None, limit: int = 100, offset: int = 0) ‑> list[PropertyRegistryItem]-
Expand source code
async def list_properties( self, search: str | None = None, limit: int = 100, offset: int = 0, ) -> list[PropertyRegistryItem]: """List properties in the registry.""" params: dict[str, Any] = {"limit": limit, "offset": offset} if search is not None: params["search"] = search resp = await self._request_ok( "GET", "/api/properties/registry", params=params, operation="Property list", ) data = resp.json() return [ self._parse(PropertyRegistryItem, p, "Property list") for p in data.get("properties", []) ]List properties in the registry.
async def list_publishers(self) ‑> list[FederatedPublisher]-
Expand source code
async def list_publishers(self) -> list[FederatedPublisher]: """List publishers in the registry.""" resp = await self._request_ok( "GET", "/api/registry/publishers", operation="Publisher list", ) data = resp.json() return [ self._parse(FederatedPublisher, p, "Publisher list") for p in data.get("publishers", []) ]List publishers in the registry.
async def list_storyboards(self, *, category: str | None = None, compliance_target: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def list_storyboards( self, *, category: str | None = None, compliance_target: str | None = None, ) -> dict[str, Any]: """List available registry compliance storyboards.""" params = { k: v for k, v in {"category": category, "compliance_target": compliance_target}.items() if v is not None } return await self._request_json( "GET", "/api/storyboards", params=params, operation="Storyboard list", )List available registry compliance storyboards.
async def lookup_brand(self, domain: str) ‑> ResolvedBrand | None-
Expand source code
async def lookup_brand(self, domain: str) -> ResolvedBrand | None: """Resolve a domain to its brand identity. Works for any domain — brand houses, sub-brands, and operators (agencies, DSPs) are all brands in the registry. Args: domain: Domain to resolve (e.g., "nike.com", "wpp.com"). Returns: ResolvedBrand if found, None if not in the registry. Raises: RegistryError: On HTTP or parsing errors. Example: brand = await registry.lookup_brand(request.brand.domain) """ client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/brands/resolve", params={"domain": domain}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Brand lookup failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return ResolvedBrand.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Brand lookup timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Brand lookup failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Brand lookup failed: invalid response: {e}") from eResolve a domain to its brand identity.
Works for any domain — brand houses, sub-brands, and operators (agencies, DSPs) are all brands in the registry.
Args
domain- Domain to resolve (e.g., "nike.com", "wpp.com").
Returns
ResolvedBrand if found, None if not in the registry.
Raises
RegistryError- On HTTP or parsing errors.
Example
brand = await registry.lookup_brand(request.brand.domain)
async def lookup_brands(self, domains: list[str]) ‑> dict[str, ResolvedBrand | None]-
Expand source code
async def lookup_brands(self, domains: list[str]) -> dict[str, ResolvedBrand | None]: """Bulk resolve domains to brand identities. Automatically chunks requests exceeding 100 domains. Args: domains: List of domains to resolve. Returns: Dict mapping each domain to its ResolvedBrand, or None if not found. Raises: RegistryError: On HTTP or parsing errors. """ if not domains: return {} chunks = [ domains[i : i + MAX_BULK_DOMAINS] for i in range(0, len(domains), MAX_BULK_DOMAINS) ] chunk_results = await asyncio.gather( *[self._lookup_brands_chunk(chunk) for chunk in chunks] ) merged: dict[str, ResolvedBrand | None] = {} for result in chunk_results: merged.update(result) return mergedBulk resolve domains to brand identities.
Automatically chunks requests exceeding 100 domains.
Args
domains- List of domains to resolve.
Returns
Dict mapping each domain to its ResolvedBrand, or None if not found.
Raises
RegistryError- On HTTP or parsing errors.
async def lookup_domain(self, domain: str) ‑> DomainLookupResult-
Expand source code
async def lookup_domain(self, domain: str) -> DomainLookupResult: """Find all agents authorized for a publisher domain.""" resp = await self._request_ok( "GET", f"/api/registry/lookup/domain/{url_quote(domain, safe='')}", operation="Domain lookup", ) return self._parse(DomainLookupResult, resp.json(), "Domain lookup")Find all agents authorized for a publisher domain.
async def lookup_manifest_ref(self, domain: str, *, type: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def lookup_manifest_ref(self, domain: str, *, type: str | None = None) -> dict[str, Any]: """Find the best manifest reference for a domain.""" params: dict[str, Any] = {"domain": domain} if type is not None: params["type"] = type resp = await self._request_ok( "GET", "/api/manifest-refs/lookup", params=params, operation="Manifest ref lookup", ) return cast(dict[str, Any], resp.json())Find the best manifest reference for a domain.
async def lookup_operator(self, domain: str, *, scope: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def lookup_operator( self, domain: str, *, scope: str | None = None, ) -> dict[str, Any]: """Resolve registry operator metadata for a domain.""" params: dict[str, Any] = {"domain": domain} if scope is not None: params["scope"] = scope return await self._request_json( "GET", "/api/registry/operator", params=params, operation="Operator lookup", )Resolve registry operator metadata for a domain.
async def lookup_properties(self, domains: list[str]) ‑> dict[str, ResolvedProperty | None]-
Expand source code
async def lookup_properties(self, domains: list[str]) -> dict[str, ResolvedProperty | None]: """Bulk resolve publisher domains to property info. Automatically chunks requests exceeding 100 domains. Args: domains: List of publisher domains to resolve. Returns: Dict mapping each domain to its ResolvedProperty, or None if not found. Raises: RegistryError: On HTTP or parsing errors. """ if not domains: return {} chunks = [ domains[i : i + MAX_BULK_DOMAINS] for i in range(0, len(domains), MAX_BULK_DOMAINS) ] chunk_results = await asyncio.gather( *[self._lookup_properties_chunk(chunk) for chunk in chunks] ) merged: dict[str, ResolvedProperty | None] = {} for result in chunk_results: merged.update(result) return mergedBulk resolve publisher domains to property info.
Automatically chunks requests exceeding 100 domains.
Args
domains- List of publisher domains to resolve.
Returns
Dict mapping each domain to its ResolvedProperty, or None if not found.
Raises
RegistryError- On HTTP or parsing errors.
async def lookup_property(self, domain: str) ‑> ResolvedProperty | None-
Expand source code
async def lookup_property(self, domain: str) -> ResolvedProperty | None: """Resolve a publisher domain to its property info. Args: domain: Publisher domain to resolve (e.g., "nytimes.com"). Returns: ResolvedProperty if found, None if the domain is not in the registry. Raises: RegistryError: On HTTP or parsing errors. """ client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/properties/resolve", params={"domain": domain}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Property lookup failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return ResolvedProperty.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Property lookup timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Property lookup failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Property lookup failed: invalid response: {e}") from eResolve a publisher domain to its property info.
Args
domain- Publisher domain to resolve (e.g., "nytimes.com").
Returns
ResolvedProperty if found, None if the domain is not in the registry.
Raises
RegistryError- On HTTP or parsing errors.
async def lookup_property_identifier(self, type: str, value: str) ‑> dict[str, typing.Any]-
Expand source code
async def lookup_property_identifier(self, type: str, value: str) -> dict[str, Any]: """Find agents holding a specific property identifier.""" resp = await self._request_ok( "GET", "/api/registry/lookup/property", params={"type": type, "value": value}, operation="Property identifier lookup", ) return cast(dict[str, Any], resp.json())Find agents holding a specific property identifier.
async def lookup_publisher(self, domain: str) ‑> dict[str, typing.Any]-
Expand source code
async def lookup_publisher(self, domain: str) -> dict[str, Any]: """Resolve registry publisher metadata for a domain.""" return await self._request_json( "GET", "/api/registry/publisher", params={"domain": domain}, operation="Publisher lookup", )Resolve registry publisher metadata for a domain.
-
Expand source code
async def lookup_publisher_agent_authorization( self, domain: str, agent: str, ) -> dict[str, Any]: """Resolve whether a publisher authorizes an agent.""" return await self._request_json( "GET", "/api/registry/publisher/authorization", params={"domain": domain, "agent": agent}, operation="Publisher agent authorization lookup", )Resolve whether a publisher authorizes an agent.
async def policy_history(self, policy_id: str, limit: int = 20, offset: int = 0) ‑> PolicyHistory | None-
Expand source code
async def policy_history( self, policy_id: str, limit: int = 20, offset: int = 0, ) -> PolicyHistory | None: """Retrieve edit history for a policy. Args: policy_id: Policy identifier. limit: Maximum revisions to return (default 20, max 100). offset: Pagination offset. Returns: PolicyHistory if found, None if the policy doesn't exist. Raises: RegistryError: On HTTP or parsing errors. """ client = await self._get_client() try: response = await client.get( f"{self._base_url}/api/policies/history", params={"policy_id": policy_id, "limit": limit, "offset": offset}, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Policy history failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return PolicyHistory.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Policy history timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Policy history failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Policy history failed: invalid response: {e}") from eRetrieve edit history for a policy.
Args
policy_id- Policy identifier.
limit- Maximum revisions to return (default 20, max 100).
offset- Pagination offset.
Returns
PolicyHistory if found, None if the policy doesn't exist.
Raises
RegistryError- On HTTP or parsing errors.
async def property_history(self, domain: str, limit: int = 20, offset: int = 0) ‑> PropertyActivity | None-
Expand source code
async def property_history( self, domain: str, limit: int = 20, offset: int = 0, ) -> PropertyActivity | None: """Get edit history for a property.""" resp = await self._request( "GET", "/api/properties/history", params={"domain": domain, "limit": limit, "offset": offset}, allow_404=True, operation="Property history", ) if resp is None: return None return self._parse(PropertyActivity, resp.json(), "Property history")Get edit history for a property.
async def publish_community_mirror_adagents(self, platform: str, config: dict[str, Any], *, auth_token: str) ‑> CommunityMirrorPublishResponse-
Expand source code
async def publish_community_mirror_adagents( self, platform: str, config: dict[str, Any], *, auth_token: str, ) -> CommunityMirrorPublishResponse: """Publish or update a catalog-only community mirror adagents.json descriptor. Persists the mirror under ``PUT /api/registry/mirrors/{platform}``. Use ``create_adagents`` (the generator endpoint) when you only need to validate or preview the document without saving it. The publish body is catalog-only; the service forces ``authorized_agents: []``. Args: platform: Stable platform key. Trimmed/lowercased and validated against ``^[a-z0-9_-]{1,64}$``. config: Catalog config (see ``build_community_mirror_adagents``). Any ``properties[].platform`` values must match ``platform``. auth_token: Bearer token required for save operations. Returns: The publish response. Raises: RegistryError: On platform/catalog validation or HTTP errors. """ normalized_platform = _normalize_community_mirror_platform(platform) catalog = build_community_mirror_adagents(config) self._assert_community_mirror_properties_match_platform(normalized_platform, catalog) resp = await self._request_ok( "PUT", f"/api/registry/mirrors/{url_quote(normalized_platform, safe='')}", json_body=catalog, auth_token=auth_token, operation="Community mirror publish", ) return self._parse(CommunityMirrorPublishResponse, resp.json(), "Community mirror publish")Publish or update a catalog-only community mirror adagents.json descriptor.
Persists the mirror under
PUT /api/registry/mirrors/{platform}. Usecreate_adagents(the generator endpoint) when you only need to validate or preview the document without saving it. The publish body is catalog-only; the service forcesauthorized_agents: [].Args
platform- Stable platform key. Trimmed/lowercased and validated
against
^[a-z0-9_-]{1,64}$. config- Catalog config (see
build_community_mirror_adagents). Anyproperties[].platformvalues must matchplatform. auth_token- Bearer token required for save operations.
Returns
The publish response.
Raises
RegistryError- On platform/catalog validation or HTTP errors.
async def refresh_agent(self, agent_url: str, *, auth_token: str) ‑> dict[str, typing.Any]-
Expand source code
async def refresh_agent( self, agent_url: str, *, auth_token: str, ) -> dict[str, Any]: """Refresh an agent's registry health/capability/compliance snapshot.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", f"/api/registry/agents/{encoded}/refresh", auth_token=auth_token, operation="Agent refresh", expected_status={200, 202}, )Refresh an agent's registry health/capability/compliance snapshot.
async def register_member_agent(self, *, auth_token: str, org: str | None = None, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def register_member_agent( self, *, auth_token: str, org: str | None = None, **body: Any, ) -> dict[str, Any]: """Register an agent for the authenticated member.""" params = {"org": org} if org is not None else None return await self._request_json( "POST", "/api/me/agents", params=params, json_body=dict(body), auth_token=auth_token, operation="Member agent register", expected_status={200, 201}, )Register an agent for the authenticated member.
async def remove_member_agent(self, url: str, *, auth_token: str, org: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def remove_member_agent( self, url: str, *, auth_token: str, org: str | None = None, ) -> dict[str, Any]: """Remove one member-owned agent.""" params = {"org": org} if org is not None else None return await self._request_json( "DELETE", f"/api/me/agents/{url_quote(url, safe='')}", params=params, auth_token=auth_token, operation="Member agent remove", )Remove one member-owned agent.
async def request_brand_crawl(self, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def request_brand_crawl( self, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Request a brand crawl through the registry.""" return await self._request_json( "POST", "/api/registry/brand-crawl-request", json_body=dict(body), auth_token=auth_token, operation="Brand crawl request", expected_status={200, 202}, )Request a brand crawl through the registry.
async def request_crawl(self, domain: str, *, auth_token: str) ‑> dict[str, typing.Any]-
Expand source code
async def request_crawl(self, domain: str, *, auth_token: str) -> dict[str, Any]: """Request a domain re-crawl (auth required).""" resp = await self._request_ok( "POST", "/api/registry/crawl-request", json_body={"domain": domain}, auth_token=auth_token, operation="Crawl request", expected_status={200, 202}, ) return cast(dict[str, Any], resp.json())Request a domain re-crawl (auth required).
async def request_manager_revalidation(self, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def request_manager_revalidation( self, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Request manager revalidation for registry-managed data.""" return await self._request_json( "POST", "/api/registry/manager-revalidation-request", json_body=dict(body), auth_token=auth_token, operation="Manager revalidation request", expected_status={200, 202}, )Request manager revalidation for registry-managed data.
async def requeue_agent_for_heartbeat(self, agent_url: str, *, auth_token: str) ‑> dict[str, typing.Any]-
Expand source code
async def requeue_agent_for_heartbeat( self, agent_url: str, *, auth_token: str, ) -> dict[str, Any]: """Requeue an agent for heartbeat monitoring.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", f"/api/registry/agents/{encoded}/monitoring/requeue", auth_token=auth_token, operation="Agent heartbeat requeue", )Requeue an agent for heartbeat monitoring.
async def resolve_policies(self, policy_ids: list[str]) ‑> dict[str, Policy | None]-
Expand source code
async def resolve_policies( self, policy_ids: list[str], ) -> dict[str, Policy | None]: """Bulk resolve policies by ID. Automatically chunks requests exceeding 100 policy IDs. Args: policy_ids: List of policy identifiers to resolve. Returns: Dict mapping each policy_id to its Policy, or None if not found. Raises: RegistryError: On HTTP or parsing errors. """ if not policy_ids: return {} chunks = [ policy_ids[i : i + MAX_BULK_POLICIES] for i in range(0, len(policy_ids), MAX_BULK_POLICIES) ] chunk_results = await asyncio.gather( *[self._resolve_policies_chunk(chunk) for chunk in chunks] ) merged: dict[str, Policy | None] = {} for result in chunk_results: merged.update(result) return mergedBulk resolve policies by ID.
Automatically chunks requests exceeding 100 policy IDs.
Args
policy_ids- List of policy identifiers to resolve.
Returns
Dict mapping each policy_id to its Policy, or None if not found.
Raises
RegistryError- On HTTP or parsing errors.
async def resolve_policy(self, policy_id: str, version: str | None = None) ‑> Policy | None-
Expand source code
async def resolve_policy( self, policy_id: str, version: str | None = None, ) -> Policy | None: """Resolve a single policy by ID. Args: policy_id: Policy identifier (e.g., "gdpr_consent"). version: Optional version pin; returns None if current version differs. Returns: Policy if found, None if not in the registry. Raises: RegistryError: On HTTP or parsing errors. """ client = await self._get_client() params: dict[str, str] = {"policy_id": policy_id} if version is not None: params["version"] = version try: response = await client.get( f"{self._base_url}/api/policies/resolve", params=params, headers={"User-Agent": self._user_agent}, timeout=self._timeout, ) if response.status_code == 404: return None if response.status_code != 200: raise RegistryError( f"Policy resolve failed: HTTP {response.status_code}", status_code=response.status_code, ) data = response.json() if data is None: return None return Policy.model_validate(data) except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Policy resolve timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Policy resolve failed: {e}") from e except (ValidationError, ValueError) as e: raise RegistryError(f"Policy resolve failed: invalid response: {e}") from eResolve a single policy by ID.
Args
policy_id- Policy identifier (e.g., "gdpr_consent").
version- Optional version pin; returns None if current version differs.
Returns
Policy if found, None if not in the registry.
Raises
RegistryError- On HTTP or parsing errors.
async def run_storyboard(self, agent_url: str, storyboard_id: str, *, auth_token: str) ‑> dict[str, typing.Any]-
Expand source code
async def run_storyboard( self, agent_url: str, storyboard_id: str, *, auth_token: str, ) -> dict[str, Any]: """Run a storyboard against an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", f"/api/registry/agents/{encoded}/storyboard/{url_quote(storyboard_id, safe='')}/run", auth_token=auth_token, operation="Storyboard run", expected_status={200, 202}, )Run a storyboard against an agent.
async def run_storyboard_step(self, agent_url: str, storyboard_id: str, step_id: str, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def run_storyboard_step( self, agent_url: str, storyboard_id: str, step_id: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Run one storyboard step against an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", "/api/registry/agents/" f"{encoded}/storyboard/{url_quote(storyboard_id, safe='')}/step/" f"{url_quote(step_id, safe='')}", json_body=dict(body), auth_token=auth_token, operation="Storyboard step run", )Run one storyboard step against an agent.
async def save_agent_oauth_client_credentials(self, agent_url: str, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def save_agent_oauth_client_credentials( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Save OAuth client credentials for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/oauth-client-credentials", json_body=dict(body), auth_token=auth_token, operation="Agent OAuth client credentials save", )Save OAuth client credentials for an agent.
async def save_brand(self,
domain: str,
brand_name: str,
*,
auth_token: str,
brand_manifest: dict[str, Any] | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def save_brand( self, domain: str, brand_name: str, *, auth_token: str, brand_manifest: dict[str, Any] | None = None, ) -> dict[str, Any]: """Save or update a brand in the registry (auth required).""" body: dict[str, Any] = {"domain": domain, "brand_name": brand_name} if brand_manifest is not None: body["brand_manifest"] = brand_manifest resp = await self._request_ok( "POST", "/api/brands/save", json_body=body, auth_token=auth_token, operation="Brand save", ) return cast(dict[str, Any], resp.json())Save or update a brand in the registry (auth required).
async def save_policy(self,
policy_id: str,
version: str,
name: str,
category: str,
enforcement: str,
policy: str,
*,
auth_token: str,
description: str | None = None,
jurisdictions: list[str] | None = None,
region_aliases: dict[str, list[str]] | None = None,
verticals: list[str] | None = None,
channels: list[str] | None = None,
effective_date: str | None = None,
sunset_date: str | None = None,
governance_domains: list[str] | None = None,
source_url: str | None = None,
source_name: str | None = None,
guidance: str | None = None,
exemplars: dict[str, Any] | None = None,
ext: dict[str, Any] | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def save_policy( self, policy_id: str, version: str, name: str, category: str, enforcement: str, policy: str, *, auth_token: str, description: str | None = None, jurisdictions: list[str] | None = None, region_aliases: dict[str, list[str]] | None = None, verticals: list[str] | None = None, channels: list[str] | None = None, effective_date: str | None = None, sunset_date: str | None = None, governance_domains: list[str] | None = None, source_url: str | None = None, source_name: str | None = None, guidance: str | None = None, exemplars: dict[str, Any] | None = None, ext: dict[str, Any] | None = None, ) -> dict[str, Any]: """Create or update a community-contributed policy. Requires authentication. Cannot edit registry-sourced or pending policies. Args: policy_id: Policy identifier (lowercase alphanumeric with underscores). version: Semantic version string. name: Human-readable policy name. category: "regulation" or "standard". enforcement: "must", "should", or "may". policy: Natural language policy text. auth_token: API key for authentication. description: Policy description. jurisdictions: ISO jurisdiction codes. region_aliases: Region alias mappings (e.g., {"EU": ["DE", "FR"]}). verticals: Industry verticals. channels: Media channels. effective_date: ISO 8601 date when enforcement begins. sunset_date: ISO 8601 date when enforcement ends. governance_domains: Applicable domains ("campaign", "creative", etc.). source_url: URL of the source regulation/standard. source_name: Name of the source. guidance: Implementation guidance text. exemplars: Pass/fail calibration scenarios. ext: Extension data. Returns: Dict with success, message, policy_id, and revision_number. Raises: RegistryError: On HTTP or parsing errors (400, 401, 409, 429). """ client = await self._get_client() body: dict[str, Any] = { "policy_id": policy_id, "version": version, "name": name, "category": category, "enforcement": enforcement, "policy": policy, } for key, value in [ ("description", description), ("jurisdictions", jurisdictions), ("region_aliases", region_aliases), ("verticals", verticals), ("channels", channels), ("effective_date", effective_date), ("sunset_date", sunset_date), ("governance_domains", governance_domains), ("source_url", source_url), ("source_name", source_name), ("guidance", guidance), ("exemplars", exemplars), ("ext", ext), ]: if value is not None: body[key] = value try: response = await client.post( f"{self._base_url}/api/policies/save", json=body, headers={ "User-Agent": self._user_agent, "Authorization": f"Bearer {auth_token}", }, timeout=self._timeout, ) if response.status_code != 200: raise RegistryError( f"Policy save failed: HTTP {response.status_code}", status_code=response.status_code, ) result: dict[str, Any] = response.json() return result except RegistryError: raise except httpx.TimeoutException as e: raise RegistryError(f"Policy save timed out after {self._timeout}s") from e except httpx.HTTPError as e: raise RegistryError(f"Policy save failed: {e}") from eCreate or update a community-contributed policy.
Requires authentication. Cannot edit registry-sourced or pending policies.
Args
policy_id- Policy identifier (lowercase alphanumeric with underscores).
version- Semantic version string.
name- Human-readable policy name.
category- "regulation" or "standard".
enforcement- "must", "should", or "may".
policy- Natural language policy text.
auth_token- API key for authentication.
description- Policy description.
jurisdictions- ISO jurisdiction codes.
region_aliases- Region alias mappings (e.g., {"EU": ["DE", "FR"]}).
verticals- Industry verticals.
channels- Media channels.
effective_date- ISO 8601 date when enforcement begins.
sunset_date- ISO 8601 date when enforcement ends.
governance_domains- Applicable domains ("campaign", "creative", etc.).
source_url- URL of the source regulation/standard.
source_name- Name of the source.
guidance- Implementation guidance text.
exemplars- Pass/fail calibration scenarios.
ext- Extension data.
Returns
Dict with success, message, policy_id, and revision_number.
Raises
RegistryError- On HTTP or parsing errors (400, 401, 409, 429).
async def save_property(self,
publisher_domain: str,
authorized_agents: list[dict[str, Any]],
*,
auth_token: str,
properties: list[dict[str, Any]] | None = None,
contact: dict[str, str] | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def save_property( self, publisher_domain: str, authorized_agents: list[dict[str, Any]], *, auth_token: str, properties: list[dict[str, Any]] | None = None, contact: dict[str, str] | None = None, ) -> dict[str, Any]: """Save or update a hosted property (auth required).""" body: dict[str, Any] = { "publisher_domain": publisher_domain, "authorized_agents": authorized_agents, } if properties is not None: body["properties"] = properties if contact is not None: body["contact"] = contact resp = await self._request_ok( "POST", "/api/properties/save", json_body=body, auth_token=auth_token, operation="Property save", ) return cast(dict[str, Any], resp.json())Save or update a hosted property (auth required).
async def search(self, q: str) ‑> dict[str, typing.Any]-
Expand source code
async def search(self, q: str) -> dict[str, Any]: """Search across brands, publishers, and properties.""" resp = await self._request_ok( "GET", "/api/search", params={"q": q}, operation="Search", ) return cast(dict[str, Any], resp.json())Search across brands, publishers, and properties.
async def search_agents(self,
*,
auth_token: str,
channels: str | None = None,
property_types: str | None = None,
markets: str | None = None,
categories: str | None = None,
tags: str | None = None,
delivery_types: str | None = None,
has_tmp: bool | None = None,
min_properties: int | None = None,
cursor: str | None = None,
limit: int = 50) ‑> dict[str, typing.Any]-
Expand source code
async def search_agents( self, *, auth_token: str, channels: str | None = None, property_types: str | None = None, markets: str | None = None, categories: str | None = None, tags: str | None = None, delivery_types: str | None = None, has_tmp: bool | None = None, min_properties: int | None = None, cursor: str | None = None, limit: int = 50, ) -> dict[str, Any]: """Search agents by inventory profile (auth required).""" params: dict[str, Any] = {"limit": limit} for key, val in [ ("channels", channels), ("property_types", property_types), ("markets", markets), ("categories", categories), ("tags", tags), ("delivery_types", delivery_types), ("cursor", cursor), ]: if val is not None: params[key] = val if has_tmp is not None: params["has_tmp"] = str(has_tmp).lower() if min_properties is not None: params["min_properties"] = min_properties resp = await self._request_ok( "GET", "/api/registry/agents/search", params=params, auth_token=auth_token, operation="Agent search", ) return cast(dict[str, Any], resp.json())Search agents by inventory profile (auth required).
async def setup_my_brand(self, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def setup_my_brand(self, *, auth_token: str, **body: Any) -> dict[str, Any]: """Set up a brand record for the authenticated member.""" return await self._request_json( "POST", "/api/brands/setup-my-brand", json_body=dict(body), auth_token=auth_token, operation="Brand setup", )Set up a brand record for the authenticated member.
async def test_agent_oauth_client_credentials(self, agent_url: str, *, auth_token: str) ‑> dict[str, typing.Any]-
Expand source code
async def test_agent_oauth_client_credentials( self, agent_url: str, *, auth_token: str, ) -> dict[str, Any]: """Test saved OAuth client credentials for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "POST", f"/api/registry/agents/{encoded}/oauth-client-credentials/test", auth_token=auth_token, operation="Agent OAuth client credentials test", )Test saved OAuth client credentials for an agent.
async def update_agent_compliance_opt_out(self, agent_url: str, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def update_agent_compliance_opt_out( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Update agent compliance opt-out state.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/compliance/opt-out", json_body=dict(body), auth_token=auth_token, operation="Agent compliance opt-out update", )Update agent compliance opt-out state.
async def update_agent_lifecycle(self, agent_url: str, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def update_agent_lifecycle( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Update an agent lifecycle stage.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/lifecycle", json_body=dict(body), auth_token=auth_token, operation="Agent lifecycle update", )Update an agent lifecycle stage.
async def update_agent_monitoring_interval(self, agent_url: str, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def update_agent_monitoring_interval( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Update monitoring interval for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/monitoring/interval", json_body=dict(body), auth_token=auth_token, operation="Agent monitoring interval update", )Update monitoring interval for an agent.
async def update_agent_monitoring_pause(self, agent_url: str, *, auth_token: str, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def update_agent_monitoring_pause( self, agent_url: str, *, auth_token: str, **body: Any, ) -> dict[str, Any]: """Pause or resume monitoring for an agent.""" encoded = self._encoded_agent_url(agent_url) return await self._request_json( "PUT", f"/api/registry/agents/{encoded}/monitoring/pause", json_body=dict(body), auth_token=auth_token, operation="Agent monitoring pause update", )Pause or resume monitoring for an agent.
async def update_member_agent(self, url: str, *, auth_token: str, org: str | None = None, **body: Any) ‑> dict[str, typing.Any]-
Expand source code
async def update_member_agent( self, url: str, *, auth_token: str, org: str | None = None, **body: Any, ) -> dict[str, Any]: """Update one member-owned agent.""" params = {"org": org} if org is not None else None return await self._request_json( "PATCH", f"/api/me/agents/{url_quote(url, safe='')}", params=params, json_body=dict(body), auth_token=auth_token, operation="Member agent update", )Update one member-owned agent.
async def upsert_community_mirror_adagents(self, config: dict[str, Any], *, platform: str | None = None, auth_token: str) ‑> CommunityMirrorPublishResponse-
Expand source code
async def upsert_community_mirror_adagents( self, config: dict[str, Any], *, platform: str | None = None, auth_token: str, ) -> CommunityMirrorPublishResponse: """Publish or update a community mirror, inferring the platform key. The platform key is resolved from the ``platform`` argument, then ``config["platform"]``, then a single consistent ``properties[].platform`` value. Ambiguous property platforms raise an error. Args: config: Catalog config (see ``build_community_mirror_adagents``). platform: Explicit platform key. Takes precedence over inference. auth_token: Bearer token required for save operations. Returns: The publish response. Raises: RegistryError: If a platform key cannot be resolved, property platforms are ambiguous, or on validation/HTTP errors. """ resolved_platform = ( platform if platform is not None else self._community_mirror_platform_from_config(config) ) return await self.publish_community_mirror_adagents( resolved_platform, config, auth_token=auth_token )Publish or update a community mirror, inferring the platform key.
The platform key is resolved from the
platformargument, thenconfig["platform"], then a single consistentproperties[].platformvalue. Ambiguous property platforms raise an error.Args
config- Catalog config (see
build_community_mirror_adagents). platform- Explicit platform key. Takes precedence over inference.
auth_token- Bearer token required for save operations.
Returns
The publish response.
Raises
RegistryError- If a platform key cannot be resolved, property platforms are ambiguous, or on validation/HTTP errors.
async def validate_adagents(self, domain: str) ‑> dict[str, typing.Any]-
Expand source code
async def validate_adagents(self, domain: str) -> dict[str, Any]: """Validate a domain's adagents.json via the registry API.""" resp = await self._request_ok( "POST", "/api/adagents/validate", json_body={"domain": domain}, operation="Adagents validate", ) return cast(dict[str, Any], resp.json())Validate a domain's adagents.json via the registry API.
-
Expand source code
async def validate_product_authorization( self, agent_url: str, publisher_properties: list[dict[str, Any]], ) -> dict[str, Any]: """Check whether an agent is authorized to sell products.""" resp = await self._request_ok( "POST", "/api/registry/validate/product-authorization", json_body={ "agent_url": agent_url, "publisher_properties": publisher_properties, }, operation="Product authorization", ) return cast(dict[str, Any], resp.json())Check whether an agent is authorized to sell products.
async def validate_property(self, domain: str) ‑> ValidationResult-
Expand source code
async def validate_property(self, domain: str) -> ValidationResult: """Validate a domain's adagents.json configuration.""" resp = await self._request_ok( "GET", "/api/properties/validate", params={"domain": domain}, operation="Property validate", ) return self._parse(ValidationResult, resp.json(), "Property validate")Validate a domain's adagents.json configuration.
-
Expand source code
async def validate_property_authorization( self, agent_url: str, identifier_type: str, identifier_value: str, ) -> dict[str, Any]: """Quick check if a property identifier is authorized for an agent.""" resp = await self._request_ok( "GET", "/api/registry/validate/property-authorization", params={ "agent_url": agent_url, "identifier_type": identifier_type, "identifier_value": identifier_value, }, operation="Property authorization", ) return cast(dict[str, Any], resp.json())Quick check if a property identifier is authorized for an agent.
async def validate_publisher(self, domain: str) ‑> dict[str, typing.Any]-
Expand source code
async def validate_publisher(self, domain: str) -> dict[str, Any]: """Validate a publisher domain's adagents.json and return stats.""" resp = await self._request_ok( "GET", "/api/public/validate-publisher", params={"domain": domain}, operation="Publisher validation", ) return cast(dict[str, Any], resp.json())Validate a publisher domain's adagents.json and return stats.
async def verify_hosted_property_origin(self, domain: str, *, auth_token: str | None = None) ‑> dict[str, typing.Any]-
Expand source code
async def verify_hosted_property_origin( self, domain: str, *, auth_token: str | None = None, ) -> dict[str, Any]: """Verify a hosted property's origin adagents.json delegation.""" return await self._request_json( "POST", f"/api/properties/hosted/{url_quote(domain, safe='')}/verify-origin", auth_token=auth_token, operation="Hosted property origin verification", )Verify a hosted property's origin adagents.json delegation.
class RegistryError (message: str, status_code: int | None = None)-
Expand source code
class RegistryError(ADCPError): """Error from AdCP registry API operations (brand/property lookups).""" def __init__(self, message: str, status_code: int | None = None): """Initialize registry error.""" self.status_code = status_code suggestion = "Check that the registry API is accessible and the domain is valid." super().__init__(message, suggestion=suggestion)Error from AdCP registry API operations (brand/property lookups).
Initialize registry error.
Ancestors
- ADCPError
- builtins.Exception
- builtins.BaseException
Inherited members
class RegistrySync (client: RegistryClient,
*,
auth_token: str,
poll_interval: float = 60.0,
cursor_store: CursorStore | None = None,
types: str | None = None,
batch_size: int = 100)-
Expand source code
class RegistrySync: """Polls the registry change feed and dispatches events to handlers. Args: client: RegistryClient instance for HTTP calls. auth_token: Bearer token for feed access. poll_interval: Seconds between polls (default 60). cursor_store: Optional CursorStore for persistence. Defaults to FileCursorStore. types: Optional event type filter (e.g., "property.*,agent.*"). batch_size: Max events per poll (default 100, max 10000). """ def __init__( self, client: RegistryClient, *, auth_token: str, poll_interval: float = 60.0, cursor_store: CursorStore | None = None, types: str | None = None, batch_size: int = 100, ) -> None: self._client = client self._auth_token = auth_token self._poll_interval = poll_interval self._cursor_store: CursorStore = cursor_store or FileCursorStore() self._types = types self._batch_size = min(batch_size, 10000) self._handlers: dict[str, list[ChangeHandler]] = defaultdict(list) self._all_handlers: list[ChangeHandler] = [] self._cursor: str | None = None self._cursor_loaded = False self._stop_event: asyncio.Event | None = None self._running = False def on(self, event_type: str, handler: ChangeHandler) -> None: """Register a handler for a specific event type. Supports glob patterns: "property.*" matches "property.created", "property.updated", etc. """ self._handlers[event_type].append(handler) def on_all(self, handler: ChangeHandler) -> None: """Register a handler for all events.""" self._all_handlers.append(handler) @property def cursor(self) -> str | None: """Current cursor position.""" return self._cursor async def _load_cursor(self) -> None: """Load cursor from store on first use.""" if not self._cursor_loaded: self._cursor = await self._cursor_store.load() self._cursor_loaded = True async def _dispatch(self, event: FeedEvent) -> None: """Dispatch a single event to matching handlers.""" # Dispatch to type-specific handlers for pattern, handlers in self._handlers.items(): if fnmatch(event.event_type, pattern): for handler in handlers: try: await handler(event) except Exception: logger.exception( "Handler error for event %s (%s)", event.event_id, event.event_type, ) # Dispatch to catch-all handlers for handler in self._all_handlers: try: await handler(event) except Exception: logger.exception( "Handler error for event %s (%s)", event.event_id, event.event_type, ) async def poll_once(self) -> list[FeedEvent]: """Poll the feed once and dispatch events. Returns the list of events processed. """ await self._load_cursor() try: page = await self._client.get_feed( auth_token=self._auth_token, cursor=self._cursor, types=self._types, limit=self._batch_size, ) except RegistryError as e: if e.status_code == 410: logger.warning("Feed cursor expired, resetting to start") self._cursor = None await self._cursor_store.save("") return [] raise for event in page.events: await self._dispatch(event) if page.cursor: self._cursor = page.cursor await self._cursor_store.save(page.cursor) return list(page.events) async def start(self) -> None: """Start the polling loop. Runs until stop() is called.""" if self._running: return self._running = True self._stop_event = asyncio.Event() logger.info("RegistrySync started (interval=%.1fs)", self._poll_interval) try: while not self._stop_event.is_set(): try: events = await self.poll_once() if events: logger.debug("Processed %d events", len(events)) except RegistryError as e: logger.error("Feed poll failed: %s", e) except Exception: logger.exception("Unexpected error in feed poll") # Wait for interval or stop signal try: await asyncio.wait_for( self._stop_event.wait(), timeout=self._poll_interval, ) except asyncio.TimeoutError: pass # Normal - poll interval elapsed finally: self._running = False logger.info("RegistrySync stopped") async def stop(self) -> None: """Stop the polling loop gracefully.""" if self._stop_event is not None: self._stop_event.set()Polls the registry change feed and dispatches events to handlers.
Args
client- RegistryClient instance for HTTP calls.
auth_token- Bearer token for feed access.
poll_interval- Seconds between polls (default 60).
cursor_store- Optional CursorStore for persistence. Defaults to FileCursorStore.
types- Optional event type filter (e.g., "property.,agent.").
batch_size- Max events per poll (default 100, max 10000).
Instance variables
prop cursor : str | None-
Expand source code
@property def cursor(self) -> str | None: """Current cursor position.""" return self._cursorCurrent cursor position.
Methods
def on(self, event_type: str, handler: ChangeHandler) ‑> None-
Expand source code
def on(self, event_type: str, handler: ChangeHandler) -> None: """Register a handler for a specific event type. Supports glob patterns: "property.*" matches "property.created", "property.updated", etc. """ self._handlers[event_type].append(handler)Register a handler for a specific event type.
Supports glob patterns: "property.*" matches "property.created", "property.updated", etc.
def on_all(self, handler: ChangeHandler) ‑> None-
Expand source code
def on_all(self, handler: ChangeHandler) -> None: """Register a handler for all events.""" self._all_handlers.append(handler)Register a handler for all events.
async def poll_once(self) ‑> list[FeedEvent]-
Expand source code
async def poll_once(self) -> list[FeedEvent]: """Poll the feed once and dispatch events. Returns the list of events processed. """ await self._load_cursor() try: page = await self._client.get_feed( auth_token=self._auth_token, cursor=self._cursor, types=self._types, limit=self._batch_size, ) except RegistryError as e: if e.status_code == 410: logger.warning("Feed cursor expired, resetting to start") self._cursor = None await self._cursor_store.save("") return [] raise for event in page.events: await self._dispatch(event) if page.cursor: self._cursor = page.cursor await self._cursor_store.save(page.cursor) return list(page.events)Poll the feed once and dispatch events.
Returns the list of events processed.
async def start(self) ‑> None-
Expand source code
async def start(self) -> None: """Start the polling loop. Runs until stop() is called.""" if self._running: return self._running = True self._stop_event = asyncio.Event() logger.info("RegistrySync started (interval=%.1fs)", self._poll_interval) try: while not self._stop_event.is_set(): try: events = await self.poll_once() if events: logger.debug("Processed %d events", len(events)) except RegistryError as e: logger.error("Feed poll failed: %s", e) except Exception: logger.exception("Unexpected error in feed poll") # Wait for interval or stop signal try: await asyncio.wait_for( self._stop_event.wait(), timeout=self._poll_interval, ) except asyncio.TimeoutError: pass # Normal - poll interval elapsed finally: self._running = False logger.info("RegistrySync stopped")Start the polling loop. Runs until stop() is called.
async def stop(self) ‑> None-
Expand source code
async def stop(self) -> None: """Stop the polling loop gracefully.""" if self._stop_event is not None: self._stop_event.set()Stop the polling loop gracefully.
class ReportPlanOutcomeRequest (**data: Any)-
Expand source code
class ReportPlanOutcomeRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) plan_id: Annotated[ str, Field( description='The plan this outcome is for. The plan uniquely scopes the account and operator; do not include a separate `account` field — the governance agent resolves account from the plan. Including `account` is rejected by `additionalProperties: false`.' ), ] check_id: Annotated[ str | None, Field( description="The check_id from check_governance. Links the outcome to the governance check that authorized it. Required for 'completed' and 'failed' outcomes." ), ] = None idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. Prevents duplicate outcome reports on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] purchase_type: Annotated[ purchase_type_1.PurchaseType | None, Field( description="The type of financial commitment this outcome is for. Determines which budget allocation (if any) to charge against. Defaults to 'media_buy' when omitted." ), ] = purchase_type_1.PurchaseType.media_buy outcome: Annotated[outcome_type.OutcomeType, Field(description='Outcome type.')] seller_response: Annotated[ SellerResponse | None, Field(description="The seller's full response. Required when outcome is 'completed'."), ] = None delivery: Annotated[ Delivery | None, Field(description="Delivery metrics. Required when outcome is 'delivery'.") ] = None error: Annotated[ Error | None, Field(description="Error details. Required when outcome is 'failed'.") ] = None governance_context: Annotated[ str, Field( description='Opaque governance context from the check_governance response that authorized this action. Enables the governance agent to correlate the outcome to the original check.', max_length=4096, min_length=1, pattern='^[\\x20-\\x7E]+$', ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var check_id : str | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar delivery : adcp.types.generated_poc.governance.report_plan_outcome_request.Delivery | Nonevar error : adcp.types.generated_poc.governance.report_plan_outcome_request.Error | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar governance_context : strvar idempotency_key : strvar model_configvar outcome : adcp.types.generated_poc.enums.outcome_type.OutcomeTypevar plan_id : strvar purchase_type : adcp.types.generated_poc.enums.purchase_type.PurchaseType | Nonevar seller_response : adcp.types.generated_poc.governance.report_plan_outcome_request.SellerResponse | None
Inherited members
class ReportPlanOutcomeResponse (**data: Any)-
Expand source code
class ReportPlanOutcomeResponse(AdcpVersionEnvelope): @model_validator(mode='before') @classmethod def _status_to_outcome_state(cls, data: Any) -> Any: if isinstance(data, dict) and 'outcome_state' not in data and 'status' in data: data = dict(data) data['outcome_state'] = data['status'] return data model_config = ConfigDict( extra='allow', ) outcome_id: Annotated[str, Field(description='Unique identifier for this outcome record.')] outcome_state: Annotated[ OutcomeState, Field( description="Outcome state. 'accepted' means state updated with no issues. 'findings' means issues were detected. Renamed from `status` in 3.1 to free the top-level `status` key for the envelope task-status (TaskStatus) under MCP flat-on-the-wire serialization." ), ] committed_budget: Annotated[ float | None, Field( description="Budget committed from this outcome. Present for 'completed' and 'failed' outcomes." ), ] = None findings: Annotated[ list[Finding] | None, Field(description="Issues detected. Present only when outcome_state is 'findings'."), ] = None plan_summary: Annotated[ PlanSummary | None, Field( description="Updated plan budget state. Present for 'completed' and 'failed' outcomes." ), ] = None replayed: Annotated[ bool | None, Field( description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." ), ] = False context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var committed_budget : float | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar findings : list[adcp.types.generated_poc.governance.report_plan_outcome_response.Finding] | Nonevar model_configvar outcome_id : strvar outcome_state : adcp.types.generated_poc.governance.report_plan_outcome_response.OutcomeStatevar plan_summary : adcp.types.generated_poc.governance.report_plan_outcome_response.PlanSummary | Nonevar replayed : bool | None
Inherited members
class ReportUsageRequest (**data: Any)-
Expand source code
class ReportUsageRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for this request. If a request with the same key has already been accepted, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request. Prevents duplicate billing on retries.' ), ] reporting_period: Annotated[ datetime_range.DatetimeRange, Field( description='The time range covered by this usage report. Applies to all records in the request.' ), ] usage: Annotated[ list[UsageItem], Field( description='One or more usage records. Each record is self-contained: it carries its own account, allowing a single request to span multiple accounts.', min_length=1, ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_configvar reporting_period : adcp.types.generated_poc.core.datetime_range.DatetimeRangevar usage : list[adcp.types.generated_poc.account.report_usage_request.UsageItem]
Inherited members
class ReportUsageResponse (**data: Any)-
Expand source code
class ReportUsageResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) accepted: Annotated[ int, Field(description='Number of usage records successfully stored.', ge=0) ] errors: Annotated[ list[error.Error] | None, Field( description="Validation errors for individual records. The field property identifies which record failed (e.g., 'usage[1].pricing_option_id')." ), ] = None sandbox: Annotated[ bool | None, Field(description='When true, the account is a sandbox account and no billing occurred.'), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var accepted : intvar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
Inherited members
class ResolvedBrand (**data: Any)-
Expand source code
class ResolvedBrand(BaseModel): """Brand identity resolved from the AdCP registry.""" model_config = ConfigDict(extra="allow") canonical_id: str canonical_domain: str brand_name: str names: list[dict[str, str]] | None = None keller_type: str | None = None parent_brand: str | None = None house_domain: str | None = None house_name: str | None = None brand_agent_url: str | None = None brand: dict[str, Any] | None = None source: strBrand identity resolved from the AdCP registry.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var brand : dict[str, typing.Any] | Nonevar brand_agent_url : str | Nonevar brand_name : strvar canonical_domain : strvar canonical_id : strvar house_domain : str | Nonevar house_name : str | Nonevar keller_type : str | Nonevar model_configvar names : list[dict[str, str]] | Nonevar parent_brand : str | Nonevar source : str
class ResolvedProperty (**data: Any)-
Expand source code
class ResolvedProperty(BaseModel): """Property information resolved from the AdCP registry.""" model_config = ConfigDict(extra="allow") publisher_domain: str source: str authorized_agents: list[dict[str, Any]] properties: list[dict[str, Any]] verified: boolProperty information resolved from the AdCP registry.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var model_configvar properties : list[dict[str, typing.Any]]var publisher_domain : strvar source : strvar verified : bool
class ResponsePayloadJwsEnvelope (**data: Any)-
Expand source code
class ResponsePayloadJwsEnvelope(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) protected: Annotated[ str, Field( description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', pattern='^[A-Za-z0-9_-]+$', ), ] payload: Annotated[ ResponsePayload, Field( description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' ), ] signature: Annotated[ str, Field( description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', pattern='^[A-Za-z0-9_-]+$', ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar payload : adcp.types.generated_poc.core.response_payload_jws_envelope.ResponsePayloadvar protected : strvar signature : str
Inherited members
class SchemaValidationError (tool: str,
side: str,
issues: list[ValidationIssue],
message: str | None = None)-
Expand source code
class SchemaValidationError(Exception): """Raised by strict-mode client hooks when a payload fails schema. Carries the full issue list via :attr:`issues` so callers can inspect every JSON Pointer, not just the first. Mirrors the shape of the AdCP L3 ``VALIDATION_ERROR`` error envelope. Attributes: tool: AdCP tool name that was being validated. side: ``"request"`` or ``"response"``. issues: Every failure, each with a sanitized message. code: Always ``"VALIDATION_ERROR"``. details: Structured payload mirroring the wire error envelope's ``details`` shape — tool/side/issues, ready for programmatic inspection by callers that don't want to parse the exception message. """ tool: str side: str issues: list[ValidationIssue] code: str details: dict[str, Any] def __init__( self, tool: str, side: str, issues: list[ValidationIssue], message: str | None = None, ) -> None: self.tool = tool self.side = side self.issues = issues self.code = "VALIDATION_ERROR" self.details = { "tool": tool, "side": side, "issues": [_issue_to_wire(i) for i in issues], } if message is None: first = issues[0] if issues else None if first is not None: message = ( f"{tool} {side} failed schema validation at " f"{first.pointer}: {first.message}" ) else: message = f"{tool} {side} failed schema validation" super().__init__(message)Raised by strict-mode client hooks when a payload fails schema.
Carries the full issue list via :attr:
issuesso callers can inspect every JSON Pointer, not just the first. Mirrors the shape of the AdCP L3VALIDATION_ERRORerror envelope.Attributes
tool- AdCP tool name that was being validated.
side"request"or"response".issues- Every failure, each with a sanitized message.
code- Always
"VALIDATION_ERROR". details- Structured payload mirroring the wire error envelope's
detailsshape — tool/side/issues, ready for programmatic inspection by callers that don't want to parse the exception message.
Ancestors
- builtins.Exception
- builtins.BaseException
Class variables
var code : strvar details : dict[str, typing.Any]var issues : list[ValidationIssue]var side : strvar tool : str
class SellerAgentReference (**data: Any)-
Expand source code
class SellerAgentReference(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) agent_url: Annotated[ AnyUrl, Field( description="The seller agent's API endpoint URL as declared in the property publisher's adagents.json `authorized_agents[].url`. MUST use the `https://` scheme. Receivers compare this URL against the `authorized_agents` list using the AdCP URL canonicalization rules — not byte-equality — and reject mismatches with `seller_not_authorized`. See docs/reference/url-canonicalization." ), ] id: Annotated[ str | None, Field( description='Reserved for a future registry-assigned stable seller identifier. Not used today — senders MUST NOT populate this field until a registry is defined. When a future release populates both `agent_url` and `id`, `agent_url` remains authoritative and `id` is advisory.', min_length=1, pattern='^[a-zA-Z0-9_-]+$', ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var agent_url : pydantic.networks.AnyUrlvar id : str | Nonevar model_config
Inherited members
class SiSendActionResponseRequest (**data: Any)-
Expand source code
class SiSendMessageRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for at-most-once execution. Each conversational turn is a distinct mutation of session transcript — without this key, a timeout-and-retry produces a duplicate turn and a duplicate model response. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each user turn.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] session_id: Annotated[str, Field(description='Active session identifier')] message: Annotated[str | None, Field(description="User's message to the brand agent")] = None action_response: Annotated[ ActionResponse | None, Field(description='Response to a previous action_button (e.g., user clicked checkout)'), ] = None sponsored_context_receipt: Annotated[ si_sponsored_context_receipt.SiSponsoredContextReceipt | None, Field( description="Host receipt for sponsored context accepted from a prior SI response in this session. This gives the brand/seller an audit-visible record of the host's accepted use mode and disclosure commitment for that context." ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var action_response : adcp.types.generated_poc.sponsored_intelligence.si_send_message_request.ActionResponse | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar message : str | Nonevar model_configvar session_id : strvar sponsored_context_receipt : adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context_receipt.SiSponsoredContextReceipt | None
class SiSendTextMessageRequest (**data: Any)-
Expand source code
class SiSendMessageRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for at-most-once execution. Each conversational turn is a distinct mutation of session transcript — without this key, a timeout-and-retry produces a duplicate turn and a duplicate model response. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each user turn.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] session_id: Annotated[str, Field(description='Active session identifier')] message: Annotated[str | None, Field(description="User's message to the brand agent")] = None action_response: Annotated[ ActionResponse | None, Field(description='Response to a previous action_button (e.g., user clicked checkout)'), ] = None sponsored_context_receipt: Annotated[ si_sponsored_context_receipt.SiSponsoredContextReceipt | None, Field( description="Host receipt for sponsored context accepted from a prior SI response in this session. This gives the brand/seller an audit-visible record of the host's accepted use mode and disclosure commitment for that context." ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var action_response : adcp.types.generated_poc.sponsored_intelligence.si_send_message_request.ActionResponse | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar message : str | Nonevar model_configvar session_id : strvar sponsored_context_receipt : adcp.types.generated_poc.sponsored_intelligence.si_sponsored_context_receipt.SiSponsoredContextReceipt | None
Inherited members
class SignalAvailabilityType (*args, **kwds)-
Expand source code
class SignalAvailabilityType(StrEnum): marketplace = 'marketplace' custom = 'custom' owned = 'owned'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var customvar marketplacevar owned
class SignalCatalogType (*args, **kwds)-
Expand source code
class SignalAvailabilityType(StrEnum): marketplace = 'marketplace' custom = 'custom' owned = 'owned'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var customvar marketplacevar owned
class SignalDefinitionEnrichment (**data: Any)-
Expand source code
class SignalDefinitionEnrichment(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) restricted_attributes: Annotated[ list[restricted_attribute.RestrictedAttribute] | None, Field(description='Restricted attribute categories this signal touches.', min_length=1), ] = None policy_categories: Annotated[ list[str] | None, Field(description='Policy categories this signal is sensitive for.', min_length=1), ] = None taxonomy: Annotated[ Taxonomy | None, Field( description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy.' ), ] = None segmentation_criteria: Annotated[str | None, Field(max_length=500)] = None criteria_url: AnyUrl | None = None data_sources: Annotated[list[DataSource] | None, Field(min_length=1)] = None methodology: Methodology | None = None audience_expansion: bool | None = None device_expansion: bool | None = None refresh_cadence: RefreshCadence | None = None lookback_window: RefreshCadence | None = None onboarder: Onboarder | None = None countries: Annotated[list[Country] | None, Field(min_length=1)] = None consent_basis: Annotated[ list[consent_basis_1.ConsentBasis] | None, Field( description="Data provider's declared GDPR Article 6 lawful basis or consent basis for the underlying signal definition, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own processing basis for the provider-declared basis.", min_length=1, ), ] = None art9_basis: Annotated[ Art9Basis | None, Field( description="Data provider's declared GDPR Article 9 basis for the underlying signal definition when special-category data is involved and Article 9 applies, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own Article 9 basis for the provider-declared basis." ), ] = None modeling: Modeling | None = None data_subject_rights: Annotated[ DataSubjectRights | None, Field( description='Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API.' ), ] = None last_updated: Annotated[ AwareDatetime | None, Field( description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' ), ] = None dts_compliant_version: str | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var art9_basis : adcp.types.generated_poc.core.signal_definition_enrichment.Art9Basis | Nonevar audience_expansion : bool | Nonevar consent_basis : list[adcp.types.generated_poc.enums.consent_basis.ConsentBasis] | Nonevar countries : list[adcp.types.generated_poc.core.signal_definition_enrichment.Country] | Nonevar criteria_url : pydantic.networks.AnyUrl | Nonevar data_sources : list[adcp.types.generated_poc.core.signal_definition_enrichment.DataSource] | Nonevar data_subject_rights : adcp.types.generated_poc.core.signal_definition_enrichment.DataSubjectRights | Nonevar device_expansion : bool | Nonevar dts_compliant_version : str | Nonevar last_updated : pydantic.types.AwareDatetime | Nonevar lookback_window : adcp.types.generated_poc.core.signal_definition_enrichment.RefreshCadence | Nonevar methodology : adcp.types.generated_poc.core.signal_definition_enrichment.Methodology | Nonevar model_configvar modeling : adcp.types.generated_poc.core.signal_definition_enrichment.Modeling | Nonevar onboarder : adcp.types.generated_poc.core.signal_definition_enrichment.Onboarder | Nonevar policy_categories : list[str] | Nonevar refresh_cadence : adcp.types.generated_poc.core.signal_definition_enrichment.RefreshCadence | Nonevar restricted_attributes : list[adcp.types.generated_poc.enums.restricted_attribute.RestrictedAttribute] | Nonevar segmentation_criteria : str | Nonevar taxonomy : adcp.types.generated_poc.core.signal_definition_enrichment.Taxonomy | None
Inherited members
class SignalFilters (**data: Any)-
Expand source code
class SignalFilters(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) catalog_types: Annotated[ list[signal_catalog_type.SignalAvailabilityType] | None, Field(description='Filter by catalog type', min_length=1), ] = None data_providers: Annotated[ list[str] | None, Field(description='Filter by specific data providers', min_length=1) ] = None max_cpm: Annotated[ float | None, Field(description="Maximum CPM filter. Applies only to signals with model='cpm'.", ge=0.0), ] = None max_percent: Annotated[ float | None, Field( description='Maximum percent-of-media rate filter. Signals where all percent_of_media pricing options exceed this value are excluded. Does not account for max_cpm caps.', ge=0.0, le=100.0, ), ] = None min_coverage_percentage: Annotated[ float | None, Field(description='Minimum coverage requirement', ge=0.0, le=100.0) ] = None ext: Annotated[ ext_1.ExtensionObject | None, Field( description='Vendor-namespaced extension parameters for seller- or platform-specific signal filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; avoid unbounded logging or labels, and do not interpolate values into caller-visible error strings, LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var catalog_types : list[adcp.types.generated_poc.enums.signal_catalog_type.SignalAvailabilityType] | Nonevar data_providers : list[str] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar max_cpm : float | Nonevar max_percent : float | Nonevar min_coverage_percentage : float | Nonevar model_config
Inherited members
class SignalListing (**data: Any)-
Expand source code
class SignalListing(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) signal_ref: Annotated[ signal_ref_1.SignalRef | None, Field( description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal." ), ] = None signal_id: Annotated[ signal_id_1.SignalId | None, Field( deprecated=True, description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', ), ] = None name: Annotated[ str | None, Field( description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." ), ] = None description: Annotated[ str | None, Field( description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' ), ] = None methodology_url: Annotated[ AnyUrl | None, Field( description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' ), ] = None last_updated: Annotated[ AwareDatetime | None, Field( description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' ), ] = None value_type: Annotated[ signal_value_type.SignalValueType | None, Field( description="The data type of this signal's values. Required when signal_ref.scope is 'product'." ), ] = None categories: Annotated[ list[str] | None, Field( description="Valid values for categorical signals. Present when value_type is 'categorical'.", min_length=1, ), ] = None range: Annotated[ Range | None, Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Subclasses
- adcp.types.generated_poc.core.product_signal_targeting_option.ProductSignalTargetingOption
- adcp.types.generated_poc.core.wholesale_feed_event.Signal
Class variables
var categories : list[str] | Nonevar description : str | Nonevar last_updated : pydantic.types.AwareDatetime | Nonevar methodology_url : pydantic.networks.AnyUrl | Nonevar model_configvar name : str | Nonevar range : adcp.types.generated_poc.core.signal_listing.Range | Nonevar signal_ref : adcp.types.generated_poc.core.signal_ref.SignalRef | Nonevar value_type : adcp.types.generated_poc.enums.signal_value_type.SignalValueType | None
Instance variables
var signal_id : adcp.types.generated_poc.core.signal_id.SignalId | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
Inherited members
class SignalPricingOption (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class SignalPricingOption(RootModel[vendor_pricing_option.VendorPricingOption]): root: Annotated[ vendor_pricing_option.VendorPricingOption, Field( description='Deprecated — use vendor-pricing-option.json for new implementations. This alias is retained for backward compatibility.', title='Signal Pricing Option', ), ]Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[VendorPricingOption]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.vendor_pricing_option.VendorPricingOption
class SignalRef (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class SignalRef(RootModel[SignalRef115 | SignalRef116 | SignalRef117]): root: Annotated[ SignalRef115 | SignalRef116 | SignalRef117, Field( description="Reference to a named signal definition. Uses scope as discriminator: 'data_provider' for a signal resolved through published adagents.json signals[], 'signal_source' for a source-native signal resolved through the issuing signal source, or 'product' for a product-local signal option. Scope is the resolution path, not provenance; authoritative enrichment lives on the seller, signal source, or data-provider signal definition, not on this reference.", discriminator='scope', title='Signal Ref', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[SignalRef115, SignalRef116, SignalRef117]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.signal_ref.SignalRef115 | adcp.types.generated_poc.core.signal_ref.SignalRef116 | adcp.types.generated_poc.core.signal_ref.SignalRef117
class SignalTargeting (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class SignalTargeting(RootModel[SignalTargeting9 | SignalTargeting10 | SignalTargeting11]): root: Annotated[ SignalTargeting9 | SignalTargeting10 | SignalTargeting11, Field( description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', discriminator='value_type', title='Signal Targeting', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[SignalTargeting9, SignalTargeting10, SignalTargeting11]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.signal_targeting.SignalTargeting9 | adcp.types.generated_poc.core.signal_targeting.SignalTargeting10 | adcp.types.generated_poc.core.signal_targeting.SignalTargeting11
class SignalTargetingExpression (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class SignalTargetingExpression( RootModel[SignalTargetingExpression1 | SignalTargetingExpression2 | SignalTargetingExpression3] ): root: Annotated[ SignalTargetingExpression1 | SignalTargetingExpression2 | SignalTargetingExpression3, Field( description='Predicate over a named signal definition. Signals are typed dimensions, similar to feature values: binary signals match true, categorical signals match one of a set of values, and numeric signals match a range. In package signal targeting groups, include/exclude semantics are controlled by the parent group operator, not by negating the expression.', discriminator='value_type', title='Signal Targeting Expression', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[SignalTargetingExpression1, SignalTargetingExpression2, SignalTargetingExpression3]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.signal_targeting_expression.SignalTargetingExpression1 | adcp.types.generated_poc.core.signal_targeting_expression.SignalTargetingExpression2 | adcp.types.generated_poc.core.signal_targeting_expression.SignalTargetingExpression3
class SignalTargetingRules (**data: Any)-
Expand source code
class SignalTargetingRules(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) resolution_model: Annotated[ ResolutionModel | None, Field( description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." ), ] = ResolutionModel.direct_targeting selection_mode: Annotated[ SelectionMode | None, Field( description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." ), ] = SelectionMode.optional min_selected_signals: Annotated[ int | None, Field( description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", ge=0, ), ] = None max_selected_signals: Annotated[ int | None, Field( description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', ge=1, ), ] = None max_selected_per_group: Annotated[ int | None, Field( description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', ge=1, ), ] = None max_signal_targeting_groups: Annotated[ int | None, Field( description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', ge=1, ), ] = None max_signals_per_targeting_group: Annotated[ int | None, Field( description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', ge=1, ), ] = None selection_group_rules: Annotated[ list[signal_selection_group_rule.SignalSelectionGroupRule] | None, Field( description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', min_length=1, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var max_selected_per_group : int | Nonevar max_selected_signals : int | Nonevar max_signal_targeting_groups : int | Nonevar max_signals_per_targeting_group : int | Nonevar min_selected_signals : int | Nonevar model_configvar resolution_model : adcp.types.generated_poc.core.signal_targeting_rules.ResolutionModel | Nonevar selection_group_rules : list[adcp.types.generated_poc.core.signal_selection_group_rule.SignalSelectionGroupRule] | Nonevar selection_mode : adcp.types.generated_poc.core.signal_targeting_rules.SelectionMode | None
Inherited members
class Snapshot (**data: Any)-
Expand source code
class Snapshot(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) as_of: Annotated[ AwareDatetime, Field(description='When this snapshot was captured by the platform') ] staleness_seconds: Annotated[ int, Field( description='Maximum age of this data in seconds. For example, 3600 means the data may be up to 1 hour old.', ge=0, ), ] impressions: Annotated[ int, Field( description='Lifetime impressions across all assignments. Not scoped to any date range.', ge=0, ), ] last_served: Annotated[ AwareDatetime | None, Field( description='Last time this creative served an impression. Absent when the creative has never served.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var as_of : pydantic.types.AwareDatetimevar impressions : intvar last_served : pydantic.types.AwareDatetime | Nonevar model_configvar staleness_seconds : int
Inherited members
-
Expand source code
class SnapshotUnavailableReason(StrEnum): SNAPSHOT_UNSUPPORTED = 'SNAPSHOT_UNSUPPORTED' SNAPSHOT_TEMPORARILY_UNAVAILABLE = 'SNAPSHOT_TEMPORARILY_UNAVAILABLE' SNAPSHOT_PERMISSION_DENIED = 'SNAPSHOT_PERMISSION_DENIED'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
class MediaBuyDeliveryStatus (*args, **kwds)-
Expand source code
class Status(StrEnum): pending_creatives = 'pending_creatives' pending_start = 'pending_start' pending = 'pending' active = 'active' paused = 'paused' completed = 'completed' rejected = 'rejected' canceled = 'canceled' failed = 'failed' reporting_delayed = 'reporting_delayed'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var activevar canceledvar completedvar failedvar pausedvar pendingvar pending_creativesvar pending_startvar rejectedvar reporting_delayed
class SyncAccountsRequest (**data: Any)-
Expand source code
class SyncAccountsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for at-most-once execution. Natural per-account upsert keys (brand, operator) handle resource-level dedup, but the envelope triggers onboarding webhooks, billing setup, and audit events — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] accounts: Annotated[ list[Accounts | Accounts3], Field( description='Per-account sync entries. Each entry uses one of two key shapes: the `account` field (AccountRef) for settings-update mode, or the flat `brand` + `operator` + `billing` trio for provisioning mode.', max_length=1000, ), ] delete_missing: Annotated[ bool | None, Field( description='When true, accounts previously synced by this agent but not included in this request will be deactivated. Scoped to the authenticated agent — does not affect accounts managed by other agents. Use with caution.' ), ] = False dry_run: Annotated[ bool | None, Field( description='When true, preview what would change without applying. Returns what would be created/updated/deactivated.' ), ] = False push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Webhook for async notifications when account status changes (e.g., pending_approval transitions to active).' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var accounts : list[adcp.types.generated_poc.account.sync_accounts_request.Accounts | adcp.types.generated_poc.account.sync_accounts_request.Accounts3]var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar delete_missing : bool | Nonevar dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_configvar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | None
Inherited members
class SyncAccountsSuccessResponse (**data: Any)-
Expand source code
class SyncAccountsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') dry_run: bool | None = None accounts: list[Account] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var accounts : list[adcp.types.generated_poc.account.sync_accounts_response.Account]var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
class SyncAccountsResponse1 (**data: Any)-
Expand source code
class SyncAccountsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') dry_run: bool | None = None accounts: list[Account] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var accounts : list[adcp.types.generated_poc.account.sync_accounts_response.Account]var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class SyncAccountsErrorResponse (**data: Any)-
Expand source code
class SyncAccountsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class SyncAudiencesRequest (**data: Any)-
Expand source code
class SyncAudiencesRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for at-most-once execution. `audience_id` gives resource-level dedup per audience, but the sync envelope emits audit events and may trigger downstream refreshes — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `audiences` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] account: Annotated[ account_ref.AccountReference, Field(description='Account to manage audiences for.') ] audiences: Annotated[ list[Audience] | None, Field( description='Audiences to sync (create or update). When omitted, the call is discovery-only and returns all existing audiences on the account without modification.', min_length=1, ), ] = None delete_missing: Annotated[ bool | None, Field( description='When true, buyer-managed audiences on the account not included in this sync will be removed. Does not affect seller-managed audiences. Do not combine with an omitted audiences array or all buyer-managed audiences will be deleted.' ), ] = False context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar audiences : list[adcp.types.generated_poc.media_buy.sync_audiences_request.Audience] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar delete_missing : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_config
Inherited members
class SyncAudiencesSuccessResponse (**data: Any)-
Expand source code
class SyncAudiencesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') audiences: list[Audience] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var audiences : list[adcp.types.generated_poc.media_buy.sync_audiences_response.Audience]var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
class SyncAudiencesResponse1 (**data: Any)-
Expand source code
class SyncAudiencesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') audiences: list[Audience] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var audiences : list[adcp.types.generated_poc.media_buy.sync_audiences_response.Audience]var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
Inherited members
class SyncAudiencesErrorResponse (**data: Any)-
Expand source code
class SyncAudiencesResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class SyncAudiencesSubmittedResponse (**data: Any)-
Expand source code
class SyncAudiencesResponse3(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal[<TaskStatus.submitted: 'submitted'>]var task_id : str
Inherited members
class SyncCatalogsInputRequired (**data: Any)-
Expand source code
class SyncCatalogsInputRequired(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) reason: Annotated[ Reason | None, Field( description='Reason code indicating why buyer input is needed. APPROVAL_REQUIRED: platform requires explicit approval before activating the catalog. FEED_VALIDATION: feed URL returned unexpected format or schema errors. ITEM_REVIEW: platform flagged items for manual review. FEED_ACCESS: platform cannot access the feed URL (authentication, CORS, etc.).' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar reason : adcp.types.generated_poc.core.async_response_refs.media_buy.sync_catalogs_async_response_input_required.Reason | None
Inherited members
class SyncCatalogsRequest (**data: Any)-
Expand source code
class SyncCatalogsRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for at-most-once execution. `catalog_id` gives resource-level dedup per catalog, but the sync envelope emits audit events and triggers platform review for large feeds — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `catalogs` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] account: Annotated[ account_ref.AccountReference, Field(description='Account that owns these catalogs.') ] catalogs: Annotated[ list[catalog.Catalog] | None, Field( description='Array of catalog feeds to sync (create or update). When omitted, the call is discovery-only and returns all existing catalogs on the account without modification.', max_length=50, min_length=1, ), ] = None catalog_ids: Annotated[ list[str] | None, Field( description='Optional filter to limit sync scope to specific catalog IDs. When provided, only these catalogs will be created/updated. Other catalogs on the account are unaffected.', max_length=50, min_length=1, ), ] = None delete_missing: Annotated[ bool | None, Field( description='When true, buyer-managed catalogs on the account not included in this sync will be removed. Does not affect seller-managed catalogs. Do not combine with an omitted catalogs array or all buyer-managed catalogs will be deleted.' ), ] = False dry_run: Annotated[ bool | None, Field( description='When true, preview changes without applying them. Returns what would be created/updated/deleted.' ), ] = False validation_mode: Annotated[ validation_mode_1.ValidationMode | None, Field( description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid catalogs and reports errors." ), ] = validation_mode_1.ValidationMode.strict push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (common for large feeds requiring platform review).' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar catalog_ids : list[str] | Nonevar catalogs : list[adcp.types.generated_poc.core.catalog.Catalog] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar delete_missing : bool | Nonevar dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_configvar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar validation_mode : adcp.types.generated_poc.enums.validation_mode.ValidationMode | None
Inherited members
class SyncCatalogsSuccessResponse (**data: Any)-
Expand source code
class SyncCatalogsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') dry_run: bool | None = None catalogs: list[Catalog] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var catalogs : list[adcp.types.generated_poc.media_buy.sync_catalogs_response.Catalog]var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
class SyncCatalogsResponse1 (**data: Any)-
Expand source code
class SyncCatalogsResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') dry_run: bool | None = None catalogs: list[Catalog] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var catalogs : list[adcp.types.generated_poc.media_buy.sync_catalogs_response.Catalog]var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
Inherited members
class SyncCatalogsErrorResponse (**data: Any)-
Expand source code
class SyncCatalogsResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class SyncCatalogsSubmittedResponse (**data: Any)-
Expand source code
class SyncCatalogsResponse3(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal[<TaskStatus.submitted: 'submitted'>]var task_id : str
Inherited members
class SyncCatalogsSubmitted (**data: Any)-
Expand source code
class SyncCatalogsSubmitted(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) status: Annotated[ Literal['submitted'], Field( description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose catalogs array is issued in-line. See task-status.json for the full task-status enum.' ), ] = 'submitted' task_id: Annotated[ str, Field( description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' ), ] message: Annotated[ str | None, Field( description="Optional human-readable explanation of why the task is submitted — e.g., 'Catalog ingestion queued; typical turnaround 5–15 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", max_length=2000, ), ] = None errors: Annotated[ list[error.Error] | None, Field( description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal['submitted']var task_id : str
Inherited members
class SyncCatalogsWorking (**data: Any)-
Expand source code
class SyncCatalogsWorking(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) percentage: Annotated[ float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) ] = None current_step: Annotated[ str | None, Field( description="Current step or phase of the operation (e.g., 'Fetching product feed', 'Validating items', 'Platform review')" ), ] = None total_steps: Annotated[ int | None, Field(description='Total number of steps in the operation', ge=1) ] = None step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None catalogs_processed: Annotated[ int | None, Field(description='Number of catalogs processed so far', ge=0) ] = None catalogs_total: Annotated[ int | None, Field(description='Total number of catalogs to process', ge=0) ] = None items_processed: Annotated[ int | None, Field(description='Total number of catalog items processed across all catalogs', ge=0), ] = None items_total: Annotated[ int | None, Field(description='Total number of catalog items to process across all catalogs', ge=0), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var catalogs_processed : int | Nonevar catalogs_total : int | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar current_step : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar items_processed : int | Nonevar items_total : int | Nonevar model_configvar percentage : float | Nonevar step_number : int | Nonevar total_steps : int | None
Inherited members
class SyncCreativesRequest (**data: Any)-
Expand source code
class SyncCreativesRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference, Field(description='Account that owns these creatives.') ] creatives: Annotated[ list[creative_asset.CreativeAsset], Field( description='Array of creative assets to sync (create or update)', max_length=100, min_length=1, ), ] creative_ids: Annotated[ list[str] | None, Field( description='Optional filter to limit sync scope to specific creative IDs. When provided, only these creatives will be created/updated. Other creatives in the library are unaffected. Useful for partial updates and error recovery.', max_length=100, min_length=1, ), ] = None assignments: Annotated[ list[Assignment] | None, Field( description='Optional bulk assignment of creatives to packages. Each entry maps one creative to one package with optional weight and placement targeting. Standalone creative agents that do not manage media buys ignore this field.', min_length=1, ), ] = None idempotency_key: Annotated[ str, Field( description='Client-generated idempotency key for safe retries. If a sync fails without a response, resending with the same idempotency_key guarantees at-most-once execution. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] delete_missing: Annotated[ bool | None, Field( description='When true, creatives not included in this sync will be archived. Use with caution for full library replacement. Invalid when creative_ids is provided — delete_missing applies to the entire library scope, not a filtered subset.' ), ] = False dry_run: Annotated[ bool | None, Field( description='When true, preview changes without applying them. Returns what would be created/updated/deleted.' ), ] = False validation_mode: Annotated[ validation_mode_1.ValidationMode | None, Field( description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors." ), ] = validation_mode_1.ValidationMode.strict push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async sync notifications. The agent will send a webhook when sync completes if the operation takes longer than immediate response time (typically for large bulk operations or manual approval/HITL).' ), ] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar assignments : list[adcp.types.generated_poc.creative.sync_creatives_request.Assignment] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creative_ids : list[str] | Nonevar creatives : list[adcp.types.generated_poc.core.creative_asset.CreativeAsset]var delete_missing : bool | Nonevar dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_configvar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar validation_mode : adcp.types.generated_poc.enums.validation_mode.ValidationMode | None
Inherited members
class SyncCreativesSuccessResponse (**data: Any)-
Expand source code
class SyncCreativesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') dry_run: bool | None = None creatives: list[Creative] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creatives : list[adcp.types.generated_poc.creative.sync_creatives_response.Creative]var dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
class SyncCreativesResponse1 (**data: Any)-
Expand source code
class SyncCreativesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') dry_run: bool | None = None creatives: list[Creative] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar creatives : list[adcp.types.generated_poc.creative.sync_creatives_response.Creative]var dry_run : bool | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
Inherited members
class SyncCreativesErrorResponse (**data: Any)-
Expand source code
class SyncCreativesResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class SyncCreativesResponse3 (**data: Any)-
Expand source code
class SyncCreativesResponse3(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal[<TaskStatus.submitted: 'submitted'>]var task_id : str
class SyncCreativesSubmittedResponse (**data: Any)-
Expand source code
class SyncCreativesResponse3(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal[<TaskStatus.submitted: 'submitted'>]var task_id : str
Inherited members
class SyncEventSourcesRequest (**data: Any)-
Expand source code
class SyncEventSourcesRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for at-most-once execution. `event_source_id` gives resource-level dedup per source, but the sync envelope emits audit events and can trigger downstream pixel provisioning — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `event_sources` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] account: Annotated[ account_ref.AccountReference, Field(description='Account to configure event sources for.') ] event_sources: Annotated[ list[EventSource] | None, Field( description='Event sources to sync (create or update). When omitted, the call is discovery-only and returns all existing event sources on the account without modification.', min_length=1, ), ] = None delete_missing: Annotated[ bool | None, Field(description='When true, event sources not included in this sync will be removed'), ] = False context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar delete_missing : bool | Nonevar event_sources : list[adcp.types.generated_poc.media_buy.sync_event_sources_request.EventSource] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_config
Inherited members
class SyncEventSourcesSuccessResponse (**data: Any)-
Expand source code
class SyncEventSourcesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') event_sources: list[EventSource] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar event_sources : list[adcp.types.generated_poc.media_buy.sync_event_sources_response.EventSource]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
class SyncEventSourcesResponse1 (**data: Any)-
Expand source code
class SyncEventSourcesResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') event_sources: list[EventSource] sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar event_sources : list[adcp.types.generated_poc.media_buy.sync_event_sources_response.EventSource]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar sandbox : bool | None
Inherited members
class SyncEventSourcesErrorResponse (**data: Any)-
Expand source code
class SyncEventSourcesResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class SyncPlansRequest (**data: Any)-
Expand source code
class SyncPlansRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) idempotency_key: Annotated[ str, Field( description='Client-generated unique key for at-most-once execution. `plan_id` gives resource-level dedup per plan, but the sync envelope emits audit events and can trigger governance reapproval — this key prevents those side effects from firing twice on retry. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] plans: Annotated[list[Plan], Field(description='One or more campaign plans to sync.')] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar model_configvar plans : list[adcp.types.generated_poc.governance.sync_plans_request.Plan]
Inherited members
class SyncPlansResponse (**data: Any)-
Expand source code
class SyncPlansResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) plans: Annotated[list[Plan], Field(description='Status for each synced plan.')] replayed: Annotated[ bool | None, Field( description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." ), ] = False context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar plans : list[adcp.types.generated_poc.governance.sync_plans_response.Plan]var replayed : bool | None
Inherited members
class TargetingOverlay (**data: Any)-
Expand source code
class TargetingOverlay(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) geo_countries: Annotated[ list[GeoCountry] | None, Field( description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", min_length=1, ), ] = None geo_countries_exclude: Annotated[ Sequence[GeoCountriesExcludeItem] | None, Field( description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", min_length=1, ), ] = None geo_regions: Annotated[ list[GeoRegion] | None, Field( description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", min_length=1, ), ] = None geo_regions_exclude: Annotated[ Sequence[GeoRegionsExcludeItem] | None, Field( description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", min_length=1, ), ] = None geo_metros: Annotated[ list[GeoMetro] | None, Field( description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', min_length=1, ), ] = None geo_metros_exclude: Annotated[ Sequence[GeoMetrosExcludeItem] | None, Field( description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', min_length=1, ), ] = None geo_postal_areas: Annotated[ list[postal_area.PostalArea] | None, Field( description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', min_length=1, ), ] = None geo_postal_areas_exclude: Annotated[ Sequence[postal_area.PostalArea] | None, Field( description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', min_length=1, ), ] = None daypart_targets: Annotated[ list[daypart_target.DaypartTarget] | None, Field( description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', min_length=1, ), ] = None axe_include_segment: Annotated[ str | None, Field( deprecated=True, description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', ), ] = None axe_exclude_segment: Annotated[ str | None, Field( deprecated=True, description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', ), ] = None audience_include: Annotated[ list[str] | None, Field( description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', min_length=1, ), ] = None audience_exclude: Annotated[ list[str] | None, Field( description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', min_length=1, ), ] = None signal_targeting_groups: Annotated[ package_signal_targeting_groups.PackageSignalTargetingGroups | None, Field( description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED." ), ] = None signal_targeting: Annotated[ list[signal_targeting_1.SignalTargeting] | None, Field( deprecated=True, description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', min_length=1, ), ] = None frequency_cap: frequency_cap_1.FrequencyCap | None = None property_list: Annotated[ property_list_ref.PropertyListReference | None, Field( description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false." ), ] = None collection_list: Annotated[ collection_list_ref.CollectionListReference | None, Field( description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.' ), ] = None collection_list_exclude: Annotated[ collection_list_ref.CollectionListReference | None, Field( description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities." ), ] = None age_restriction: Annotated[ AgeRestriction | None, Field( description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' ), ] = None device_platform: Annotated[ list[device_platform_1.DevicePlatform] | None, Field( description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', min_length=1, ), ] = None device_type: Annotated[ list[device_type_1.DeviceType] | None, Field( description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', min_length=1, ), ] = None device_type_exclude: Annotated[ list[device_type_1.DeviceType] | None, Field( description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', min_length=1, ), ] = None store_catchments: Annotated[ list[StoreCatchment] | None, Field( description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', min_length=1, ), ] = None geo_proximity: Annotated[ list[GeoProximityItem] | None, Field( description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', min_length=1, ), ] = None language: Annotated[ list[LanguageItem] | None, Field( description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", min_length=1, ), ] = None keyword_targets: Annotated[ list[KeywordTarget] | None, Field( description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', min_length=1, ), ] = None negative_keywords: Annotated[ list[NegativeKeyword] | None, Field( description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', min_length=1, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var age_restriction : adcp.types.generated_poc.core.targeting.AgeRestriction | Nonevar audience_exclude : list[str] | Nonevar audience_include : list[str] | Nonevar collection_list : adcp.types.generated_poc.core.collection_list_ref.CollectionListReference | Nonevar collection_list_exclude : adcp.types.generated_poc.core.collection_list_ref.CollectionListReference | Nonevar daypart_targets : list[adcp.types.generated_poc.core.daypart_target.DaypartTarget] | Nonevar device_platform : list[adcp.types.generated_poc.enums.device_platform.DevicePlatform] | Nonevar device_type : list[adcp.types.generated_poc.enums.device_type.DeviceType] | Nonevar device_type_exclude : list[adcp.types.generated_poc.enums.device_type.DeviceType] | Nonevar frequency_cap : adcp.types.generated_poc.core.frequency_cap.FrequencyCap | Nonevar geo_countries : list[adcp.types.generated_poc.core.targeting.GeoCountry] | Nonevar geo_countries_exclude : collections.abc.Sequence[adcp.types.generated_poc.core.targeting.GeoCountriesExcludeItem] | Nonevar geo_metros : list[adcp.types.generated_poc.core.targeting.GeoMetro] | Nonevar geo_metros_exclude : collections.abc.Sequence[adcp.types.generated_poc.core.targeting.GeoMetrosExcludeItem] | Nonevar geo_postal_areas : list[adcp.types.generated_poc.core.postal_area.PostalArea] | Nonevar geo_postal_areas_exclude : collections.abc.Sequence[adcp.types.generated_poc.core.postal_area.PostalArea] | Nonevar geo_proximity : list[adcp.types.generated_poc.core.targeting.GeoProximityItem] | Nonevar geo_regions : list[adcp.types.generated_poc.core.targeting.GeoRegion] | Nonevar geo_regions_exclude : collections.abc.Sequence[adcp.types.generated_poc.core.targeting.GeoRegionsExcludeItem] | Nonevar keyword_targets : list[adcp.types.generated_poc.core.targeting.KeywordTarget] | Nonevar language : list[adcp.types.generated_poc.core.targeting.LanguageItem] | Nonevar model_configvar negative_keywords : list[adcp.types.generated_poc.core.targeting.NegativeKeyword] | Nonevar property_list : adcp.types.generated_poc.core.property_list_ref.PropertyListReference | Nonevar signal_targeting_groups : adcp.types.generated_poc.core.package_signal_targeting_groups.PackageSignalTargetingGroups | Nonevar store_catchments : list[adcp.types.generated_poc.core.targeting.StoreCatchment] | None
Instance variables
var axe_exclude_segment : str | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
var axe_include_segment : str | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
var signal_targeting : list[adcp.types.generated_poc.core.signal_targeting.SignalTargeting] | None-
Expand source code
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: if obj is None: if self.wrapped_property is not None: return self.wrapped_property.__get__(None, obj_type) raise AttributeError(self.field_name) warnings.warn(self.msg, DeprecationWarning, stacklevel=2) if self.wrapped_property is not None: return self.wrapped_property.__get__(obj, obj_type) return obj.__dict__[self.field_name]Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
Attributes
msg- The deprecation message to be emitted.
wrapped_property- The property instance if the deprecated field is a computed field, or
None. field_name- The name of the field being deprecated.
Inherited members
class TaskResult (**data: Any)-
Expand source code
class TaskResult(BaseModel, Generic[T]): """Result from task execution.""" model_config = ConfigDict(arbitrary_types_allowed=True) status: TaskStatus data: T | None = None message: str | None = None # Human-readable message from agent (e.g., MCP content text) submitted: SubmittedInfo | None = None needs_input: NeedsInputInfo | None = None error: str | None = None # Structured AdCP error per transport-errors.mdx (``adcp_error`` object: # ``code``, ``message``, ``detail``, ``field_path``, ``recovery`` ...). # Always populated on the MCP FAILED path when the seller returned a # spec-shaped ``adcp_error`` — independent of ``debug``. Callers should # branch on ``adcp_error.code`` rather than regex-matching ``error``. adcp_error: dict[str, Any] | None = None success: bool = Field(default=True) metadata: dict[str, Any] | None = None debug_info: DebugInfo | None = None # The full idempotency_key the SDK used for this request — echoed here so # buyers can correlate against their own records. SENSITIVE inside the # seller's replay_ttl_seconds window (serves as a retry-pattern oracle); # do not emit to shared logs. The SDK's debug capture redacts keys by # default; avoid ``model_dump_json()``-ing a TaskResult into shared sinks. idempotency_key: str | None = None # True when the seller returned a cached response for a replayed key. # Agents that emit side effects on success (notifications, memory writes, # downstream tool calls) must check this flag and suppress duplicates. replayed: bool = FalseResult 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
- typing.Generic
Subclasses
- adcp.types.core.TaskResult[AdcpAsyncResponseData]
- adcp.types.core.TaskResult[Any]
- adcp.types.core.TaskResult[CheckGovernanceResponse]
- adcp.types.core.TaskResult[ComplyTestControllerResponse]
- adcp.types.core.TaskResult[ContextMatchResponse]
- adcp.types.core.TaskResult[CreateCollectionListResponse]
- adcp.types.core.TaskResult[CreateContentStandardsResponse]
- adcp.types.core.TaskResult[CreatePropertyListResponse]
- adcp.types.core.TaskResult[DeleteCollectionListResponse]
- adcp.types.core.TaskResult[DeletePropertyListResponse]
- adcp.types.core.TaskResult[GetAdcpCapabilitiesResponse]
- adcp.types.core.TaskResult[GetCollectionListResponse]
- adcp.types.core.TaskResult[GetCreativeDeliveryResponse]
- adcp.types.core.TaskResult[GetMediaBuyDeliveryResponse]
- adcp.types.core.TaskResult[GetMediaBuysResponse]
- adcp.types.core.TaskResult[GetPlanAuditLogsResponse]
- adcp.types.core.TaskResult[GetProductsResponse]
- adcp.types.core.TaskResult[GetPropertyListResponse]
- adcp.types.core.TaskResult[GetSignalsResponse]
- adcp.types.core.TaskResult[GetTaskStatusResponse]
- adcp.types.core.TaskResult[IdentityMatchResponse]
- adcp.types.core.TaskResult[ListAccountsResponse]
- adcp.types.core.TaskResult[ListCollectionListsResponse]
- adcp.types.core.TaskResult[ListContentStandardsResponse]
- adcp.types.core.TaskResult[ListCreativeFormatsResponse]
- adcp.types.core.TaskResult[ListCreativesResponse]
- adcp.types.core.TaskResult[ListPropertyListsResponse]
- adcp.types.core.TaskResult[ListTasksResponse]
- adcp.types.core.TaskResult[ListTransformersResponseCreativeAgent]
- adcp.types.core.TaskResult[ReportPlanOutcomeResponse]
- adcp.types.core.TaskResult[ReportUsageResponse]
- adcp.types.core.TaskResult[SiGetOfferingResponse]
- adcp.types.core.TaskResult[SiInitiateSessionResponse]
- adcp.types.core.TaskResult[SiSendMessageResponse]
- adcp.types.core.TaskResult[SiTerminateSessionResponse]
- adcp.types.core.TaskResult[SyncGovernanceResponse]
- adcp.types.core.TaskResult[SyncPlansResponse]
- adcp.types.core.TaskResult[Union[AcquireRightsResponse1, AcquireRightsResponse2, AcquireRightsResponse3, AcquireRightsResponse4]]
- adcp.types.core.TaskResult[Union[ActivateSignalResponse1, ActivateSignalResponse2]]
- adcp.types.core.TaskResult[Union[BuildCreativeResponse1, BuildCreativeResponse2, BuildCreativeResponse3, BuildCreativeResponse4, BuildCreativeResponse5, BuildCreativeResponse6]]
- adcp.types.core.TaskResult[Union[CalibrateContentResponse1, CalibrateContentResponse2]]
- adcp.types.core.TaskResult[Union[CreateMediaBuyResponse1, CreateMediaBuyResponse2, CreateMediaBuyResponse3]]
- adcp.types.core.TaskResult[Union[GetAccountFinancialsResponse1, GetAccountFinancialsResponse2]]
- adcp.types.core.TaskResult[Union[GetBrandIdentityResponse1, GetBrandIdentityResponse2]]
- adcp.types.core.TaskResult[Union[GetContentStandardsResponse1, GetContentStandardsResponse2]]
- adcp.types.core.TaskResult[Union[GetCreativeFeaturesResponse1, GetCreativeFeaturesResponse2]]
- adcp.types.core.TaskResult[Union[GetMediaBuyArtifactsResponse1, GetMediaBuyArtifactsResponse2]]
- adcp.types.core.TaskResult[Union[GetRightsResponse1, GetRightsResponse2]]
- adcp.types.core.TaskResult[Union[LogEventResponse1, LogEventResponse2]]
- adcp.types.core.TaskResult[Union[PreviewCreativeResponse1, PreviewCreativeResponse2, PreviewCreativeResponse3]]
- adcp.types.core.TaskResult[Union[ProvidePerformanceFeedbackResponse1, ProvidePerformanceFeedbackResponse2]]
- adcp.types.core.TaskResult[Union[SyncAccountsResponse1, SyncAccountsResponse2]]
- adcp.types.core.TaskResult[Union[SyncAudiencesResponse1, SyncAudiencesResponse2, SyncAudiencesResponse3]]
- adcp.types.core.TaskResult[Union[SyncCatalogsResponse1, SyncCatalogsResponse2, SyncCatalogsResponse3]]
- adcp.types.core.TaskResult[Union[SyncCreativesResponse1, SyncCreativesResponse2, SyncCreativesResponse3]]
- adcp.types.core.TaskResult[Union[SyncEventSourcesResponse1, SyncEventSourcesResponse2]]
- adcp.types.core.TaskResult[Union[UpdateMediaBuyResponse1, UpdateMediaBuyResponse2, UpdateMediaBuyResponse3]]
- adcp.types.core.TaskResult[Union[UpdateRightsResponse1, UpdateRightsResponse2]]
- adcp.types.core.TaskResult[Union[ValidateContentDeliveryResponse1, ValidateContentDeliveryResponse2]]
- adcp.types.core.TaskResult[UpdateCollectionListResponse]
- adcp.types.core.TaskResult[UpdateContentStandardsResponse]
- adcp.types.core.TaskResult[UpdatePropertyListResponse]
Class variables
var adcp_error : dict[str, typing.Any] | Nonevar data : ~T | Nonevar debug_info : DebugInfo | Nonevar error : str | Nonevar idempotency_key : str | Nonevar message : str | Nonevar metadata : dict[str, typing.Any] | Nonevar model_configvar needs_input : NeedsInputInfo | Nonevar replayed : boolvar status : TaskStatusvar submitted : SubmittedInfo | Nonevar 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 COMPLETEDvar FAILEDvar NEEDS_INPUTvar SUBMITTEDvar WORKING
class GeneratedTaskStatus (*args, **kwds)-
Expand source code
class TaskStatus(StrEnum): submitted = 'submitted' working = 'working' input_required = 'input-required' completed = 'completed' canceled = 'canceled' failed = 'failed' rejected = 'rejected' auth_required = 'auth-required' unknown = 'unknown'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var auth_requiredvar canceledvar completedvar failedvar input_requiredvar rejectedvar submittedvar unknownvar working
class TimeBasedPricingOption (**data: Any)-
Expand source code
class TimeBasedPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[ Literal['time'], Field(description='Cost per time unit - rate scales with campaign duration'), ] = 'time' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None parameters: Annotated[Parameters, Field(description='Time-based pricing parameters')] min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar min_spend_per_package : float | Nonevar model_configvar parameters : adcp.types.generated_poc.pricing_options.time_option.Parametersvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['time']var pricing_option_id : str
Inherited members
class TimeUnit (*args, **kwds)-
Expand source code
class TimeUnit(StrEnum): hour = 'hour' day = 'day' week = 'week' month = 'month'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var dayvar hourvar monthvar week
class Transform (*args, **kwds)-
Expand source code
class Transform(StrEnum): date = 'date' divide = 'divide' boolean = 'boolean' split = 'split' # type: ignore[assignment]Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var booleanvar datevar dividevar split
class UnknownFieldPolicy (*args, **kwds)-
Expand source code
class UnknownFieldPolicy(str, Enum): """Server-side policy for unknown top-level tool arguments. Runs at the transport boundary before Pydantic request-model coercion can silently accept or drop extra fields. """ REJECT = "reject" STRIP = "strip" IGNORE = "ignore"Server-side policy for unknown top-level tool arguments.
Runs at the transport boundary before Pydantic request-model coercion can silently accept or drop extra fields.
Ancestors
- builtins.str
- enum.Enum
Class variables
var IGNOREvar REJECTvar STRIP
class UpdateContentStandardsSuccessResponse (**data: Any)-
Expand source code
class UpdateContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
class UpdateContentStandardsResponse1 (**data: Any)-
Expand source code
class UpdateContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
class UpdateContentStandardsErrorResponse (**data: Any)-
Expand source code
class UpdateContentStandardsResponse(AdcpVersionEnvelope, ProtocolEnvelope): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class UpdateFrequency (*args, **kwds)-
Expand source code
class UpdateFrequency(StrEnum): realtime = 'realtime' hourly = 'hourly' daily = 'daily' weekly = 'weekly'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var dailyvar hourlyvar realtimevar weekly
class UpdateMediaBuyRequest (**data: Any)-
Expand source code
class UpdateMediaBuyRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference, Field( description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.' ), ] media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")] revision: Annotated[ int | None, Field( description="Expected current revision for optimistic concurrency. Optional for backward compatibility. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match, and MUST enforce that comparison atomically with the write. Obtain from get_media_buys or the most recent create/update response.", ge=1, ), ] = None paused: Annotated[ bool | None, Field(description='Pause/resume the entire media buy (true = paused, false = active)'), ] = None canceled: Annotated[ Literal[True] | None, Field( description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.' ), ] = None cancellation_reason: Annotated[ str | None, Field( description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.', max_length=500, ), ] = None start_time: start_timing.StartTiming | None = None end_time: Annotated[ AwareDatetime | None, Field(description='New end date/time in ISO 8601 format') ] = None packages: Annotated[ Sequence[package_update.PackageUpdate] | None, Field(description='Package-specific updates for existing packages', min_length=1), ] = None invoice_recipient: Annotated[ business_entity.BusinessEntity | None, Field( description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." ), ] = None new_packages: Annotated[ list[package_request.PackageRequest] | None, Field( description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. Sellers that support mid-flight package additions advertise `add_packages` in both `valid_actions[]` (deprecated) and as an entry in `available_actions[]` (authoritative). Sellers that do not support this MUST reject with ACTION_NOT_ALLOWED (preferred) or UNSUPPORTED_FEATURE (legacy).', min_length=1, ), ] = None reporting_webhook: Annotated[ reporting_webhook_1.ReportingWebhook | None, Field( description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.' ), ] = None idempotency_key: Annotated[ str, Field( description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar canceled : Literal[True] | Nonevar cancellation_reason : str | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar end_time : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar media_buy_id : strvar model_configvar new_packages : list[adcp.types.generated_poc.media_buy.package_request.PackageRequest] | Nonevar packages : collections.abc.Sequence[adcp.types.generated_poc.media_buy.package_update.PackageUpdate] | Nonevar paused : bool | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar reporting_webhook : adcp.types.generated_poc.core.reporting_webhook.ReportingWebhook | Nonevar revision : int | Nonevar start_time : adcp.types.generated_poc.core.start_timing.StartTiming | None
class UpdateMediaBuyPackagesRequest (**data: Any)-
Expand source code
class UpdateMediaBuyRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference, Field( description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.' ), ] media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")] revision: Annotated[ int | None, Field( description="Expected current revision for optimistic concurrency. Optional for backward compatibility. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match, and MUST enforce that comparison atomically with the write. Obtain from get_media_buys or the most recent create/update response.", ge=1, ), ] = None paused: Annotated[ bool | None, Field(description='Pause/resume the entire media buy (true = paused, false = active)'), ] = None canceled: Annotated[ Literal[True] | None, Field( description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.' ), ] = None cancellation_reason: Annotated[ str | None, Field( description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.', max_length=500, ), ] = None start_time: start_timing.StartTiming | None = None end_time: Annotated[ AwareDatetime | None, Field(description='New end date/time in ISO 8601 format') ] = None packages: Annotated[ Sequence[package_update.PackageUpdate] | None, Field(description='Package-specific updates for existing packages', min_length=1), ] = None invoice_recipient: Annotated[ business_entity.BusinessEntity | None, Field( description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." ), ] = None new_packages: Annotated[ list[package_request.PackageRequest] | None, Field( description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. Sellers that support mid-flight package additions advertise `add_packages` in both `valid_actions[]` (deprecated) and as an entry in `available_actions[]` (authoritative). Sellers that do not support this MUST reject with ACTION_NOT_ALLOWED (preferred) or UNSUPPORTED_FEATURE (legacy).', min_length=1, ), ] = None reporting_webhook: Annotated[ reporting_webhook_1.ReportingWebhook | None, Field( description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.' ), ] = None idempotency_key: Annotated[ str, Field( description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar canceled : Literal[True] | Nonevar cancellation_reason : str | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar end_time : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar media_buy_id : strvar model_configvar new_packages : list[adcp.types.generated_poc.media_buy.package_request.PackageRequest] | Nonevar packages : collections.abc.Sequence[adcp.types.generated_poc.media_buy.package_update.PackageUpdate] | Nonevar paused : bool | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar reporting_webhook : adcp.types.generated_poc.core.reporting_webhook.ReportingWebhook | Nonevar revision : int | Nonevar start_time : adcp.types.generated_poc.core.start_timing.StartTiming | None
class UpdateMediaBuyPropertiesRequest (**data: Any)-
Expand source code
class UpdateMediaBuyRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference, Field( description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.' ), ] media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")] revision: Annotated[ int | None, Field( description="Expected current revision for optimistic concurrency. Optional for backward compatibility. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match, and MUST enforce that comparison atomically with the write. Obtain from get_media_buys or the most recent create/update response.", ge=1, ), ] = None paused: Annotated[ bool | None, Field(description='Pause/resume the entire media buy (true = paused, false = active)'), ] = None canceled: Annotated[ Literal[True] | None, Field( description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.' ), ] = None cancellation_reason: Annotated[ str | None, Field( description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.', max_length=500, ), ] = None start_time: start_timing.StartTiming | None = None end_time: Annotated[ AwareDatetime | None, Field(description='New end date/time in ISO 8601 format') ] = None packages: Annotated[ Sequence[package_update.PackageUpdate] | None, Field(description='Package-specific updates for existing packages', min_length=1), ] = None invoice_recipient: Annotated[ business_entity.BusinessEntity | None, Field( description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request." ), ] = None new_packages: Annotated[ list[package_request.PackageRequest] | None, Field( description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. Sellers that support mid-flight package additions advertise `add_packages` in both `valid_actions[]` (deprecated) and as an entry in `available_actions[]` (authoritative). Sellers that do not support this MUST reject with ACTION_NOT_ALLOWED (preferred) or UNSUPPORTED_FEATURE (legacy).', min_length=1, ), ] = None reporting_webhook: Annotated[ reporting_webhook_1.ReportingWebhook | None, Field( description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.' ), ] = None push_notification_config: Annotated[ push_notification_config_1.PushNotificationConfig | None, Field( description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.' ), ] = None idempotency_key: Annotated[ str, Field( description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReferencevar canceled : Literal[True] | Nonevar cancellation_reason : str | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar end_time : pydantic.types.AwareDatetime | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar idempotency_key : strvar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar media_buy_id : strvar model_configvar new_packages : list[adcp.types.generated_poc.media_buy.package_request.PackageRequest] | Nonevar packages : collections.abc.Sequence[adcp.types.generated_poc.media_buy.package_update.PackageUpdate] | Nonevar paused : bool | Nonevar push_notification_config : adcp.types.generated_poc.core.push_notification_config.PushNotificationConfig | Nonevar reporting_webhook : adcp.types.generated_poc.core.reporting_webhook.ReportingWebhook | Nonevar revision : int | Nonevar start_time : adcp.types.generated_poc.core.start_timing.StartTiming | None
Inherited members
class UpdateMediaBuySuccessResponse (**data: Any)-
Expand source code
class UpdateMediaBuyResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str media_buy_status: media_buy_status_1.MediaBuyStatus | None = None status: Literal['completed'] revision: Annotated[int, Field(ge=1)] currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None total_budget: Annotated[float, Field(ge=0)] | None = None implementation_date: AwareDatetime | None = None invoice_recipient: business_entity_1.BusinessEntity | None = None affected_packages: Sequence[package_1.Package] | None = None valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = None @model_validator(mode='before') @classmethod def _normalize_legacy_status(cls, data: Any) -> Any: if not isinstance(data, dict): return data raw_status = unwrap_enum_value(data.get('status')) media_buy_status = unwrap_enum_value(data.get('media_buy_status')) if raw_status is None: data = dict(data) data['status'] = 'completed' elif raw_status == 'completed': data = dict(data) data['status'] = 'completed' elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: data = dict(data) data['media_buy_status'] = raw_status data['status'] = 'completed' elif media_buy_status is not None and raw_status == media_buy_status: data = dict(data) data['status'] = 'completed' return dataBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var affected_packages : collections.abc.Sequence[adcp.types.generated_poc.core.package.Package] | Nonevar available_actions : list[adcp.types.generated_poc.core.media_buy_available_action.MediaBuyAvailableAction] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar currency : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar implementation_date : pydantic.types.AwareDatetime | Nonevar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar media_buy_id : strvar media_buy_status : adcp.types.generated_poc.enums.media_buy_status.MediaBuyStatus | Nonevar model_configvar revision : intvar sandbox : bool | Nonevar status : Literal['completed']var total_budget : float | Nonevar valid_actions : list[adcp.types.generated_poc.enums.media_buy_valid_action.MediaBuyValidAction] | None
class UpdateMediaBuyResponse1 (**data: Any)-
Expand source code
class UpdateMediaBuyResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') media_buy_id: str media_buy_status: media_buy_status_1.MediaBuyStatus | None = None status: Literal['completed'] revision: Annotated[int, Field(ge=1)] currency: Annotated[str, StringConstraints(pattern='^[A-Z]{3}$')] | None = None total_budget: Annotated[float, Field(ge=0)] | None = None implementation_date: AwareDatetime | None = None invoice_recipient: business_entity_1.BusinessEntity | None = None affected_packages: Sequence[package_1.Package] | None = None valid_actions: list[media_buy_valid_action_1.MediaBuyValidAction] | None = None available_actions: list[media_buy_available_action_1.MediaBuyAvailableAction] | None = None sandbox: bool | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = None @model_validator(mode='before') @classmethod def _normalize_legacy_status(cls, data: Any) -> Any: if not isinstance(data, dict): return data raw_status = unwrap_enum_value(data.get('status')) media_buy_status = unwrap_enum_value(data.get('media_buy_status')) if raw_status is None: data = dict(data) data['status'] = 'completed' elif raw_status == 'completed': data = dict(data) data['status'] = 'completed' elif media_buy_status is None and raw_status in MEDIA_BUY_LEGACY_STATUS_VALUES: data = dict(data) data['media_buy_status'] = raw_status data['status'] = 'completed' elif media_buy_status is not None and raw_status == media_buy_status: data = dict(data) data['status'] = 'completed' return dataBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var affected_packages : collections.abc.Sequence[adcp.types.generated_poc.core.package.Package] | Nonevar available_actions : list[adcp.types.generated_poc.core.media_buy_available_action.MediaBuyAvailableAction] | Nonevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar currency : str | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar implementation_date : pydantic.types.AwareDatetime | Nonevar invoice_recipient : adcp.types.generated_poc.core.business_entity.BusinessEntity | Nonevar media_buy_id : strvar media_buy_status : adcp.types.generated_poc.enums.media_buy_status.MediaBuyStatus | Nonevar model_configvar revision : intvar sandbox : bool | Nonevar status : Literal['completed']var total_budget : float | Nonevar valid_actions : list[adcp.types.generated_poc.enums.media_buy_valid_action.MediaBuyValidAction] | None
Inherited members
class UpdateMediaBuyErrorResponse (**data: Any)-
Expand source code
class UpdateMediaBuyResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class UpdateMediaBuyResponse3 (**data: Any)-
Expand source code
class UpdateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal[<TaskStatus.submitted: 'submitted'>]var task_id : str
class UpdateMediaBuySubmittedResponse (**data: Any)-
Expand source code
class UpdateMediaBuyResponse3(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict(extra='allow', validate_default=True) status: Literal[task_status_1.TaskStatus.submitted] = task_status_1.TaskStatus.submitted task_id: str message: Annotated[str, StringConstraints(max_length=2000)] | None = None errors: list[error_1.Error] | None = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar message : str | Nonevar model_configvar status : Literal[<TaskStatus.submitted: 'submitted'>]var task_id : str
Inherited members
class ValidateContentDeliverySuccessResponse (**data: Any)-
Expand source code
class ValidateContentDeliveryResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') summary: Summary results: list[Result] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar results : list[adcp.types.generated_poc.content_standards.validate_content_delivery_response.Result]var summary : adcp.types.generated_poc.content_standards.validate_content_delivery_response.Summary
class ValidateContentDeliveryResponse1 (**data: Any)-
Expand source code
class ValidateContentDeliveryResponse1(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') summary: Summary results: list[Result] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar results : list[adcp.types.generated_poc.content_standards.validate_content_delivery_response.Result]var summary : adcp.types.generated_poc.content_standards.validate_content_delivery_response.Summary
Inherited members
class ValidateContentDeliveryErrorResponse (**data: Any)-
Expand source code
class ValidateContentDeliveryResponse2(AdcpVersionEnvelope): model_config = ConfigDict(extra='allow') errors: list[error_1.Error] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class ValidateInputRequest (**data: Any)-
Expand source code
class ValidateInputRequest(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) account: Annotated[ account_ref.AccountReference | None, Field( description='Optional account scope for seller-specific product validation. Required by sellers that route product declarations by buyer account.' ), ] = None brand: Annotated[ brand_ref.BrandReference | None, Field( description='Optional brand scope when account is omitted or the seller keys sandbox validation by brand identity.' ), ] = None manifest: Annotated[ creative_manifest.CreativeManifest, Field(description='Creative manifest to validate.') ] targets: Annotated[ list[Targets] | None, Field( description="Discriminated list of validation targets. Each entry mirrors the `target` shape on `validate-input-result.json` so the request/response wire shapes match exactly. Multi-target requests enable universal-creative scenarios where one manifest targets multiple sellers' format declarations in a single round-trip; the response carries one result per target in the same order.", max_length=50, min_length=1, ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account : adcp.types.generated_poc.core.account_ref.AccountReference | Nonevar brand : adcp.types.generated_poc.core.brand_ref.BrandReference | Nonevar manifest : adcp.types.generated_poc.core.creative_manifest.CreativeManifestvar model_configvar targets : list[adcp.types.generated_poc.creative.validate_input_request.Targets] | None
Inherited members
class ValidateInputResponse (**data: Any)-
Expand source code
class ValidateInputResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) results: Annotated[ list[validate_input_result.ValidateInputResult], Field(description='Per-target validation results.'), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar results : list[adcp.types.generated_poc.creative.validate_input_result.ValidateInputResult]
Inherited members
class ValidationError (*args, **kwargs)-
Expand source code
class ValidationError(ValueError): """Raised when runtime validation fails.""" passRaised when runtime validation fails.
Ancestors
- builtins.ValueError
- builtins.Exception
- builtins.BaseException
class ValidationHookConfig (requests: ValidationMode | None = None,
responses: ValidationMode | None = None,
unknown_fields: "UnknownFieldPolicy | Literal['reject', 'strip', 'ignore'] | None" = None)-
Expand source code
@dataclass(frozen=True) class ValidationHookConfig: """Per-side client validation modes. Defaults match the TS port (adcontextprotocol/adcp-client#694): * ``requests``: ``"warn"`` — strict would break callers that intentionally send partial payloads (error-path tests, exploratory probes). Storyboards and compliance runners that want hard-stop enforcement pass ``requests="strict"`` explicitly. * ``responses``: ``"strict"`` in dev/test, ``"warn"`` when ``ADCP_ENV`` is set to ``production`` / ``prod``. Strict-by-default makes the SDK a compliance harness: drift from an agent fails the task on the first call, not the Nth storyboard run. Resolution order for both sides at call time: 1. Explicit value on this config (``requests=`` / ``responses=``). 2. ``ADCP_VALIDATION_MODE`` env var (``strict`` / ``warn`` / ``off``) — applies to both sides unless overridden by an explicit value. Matches the TS port (adcontextprotocol/adcp-client). 3. ``ADCP_ENV=prod|production`` flips the response default to ``warn``; requests fall back to the type default. 4. Defaults: ``requests="warn"``, ``responses="strict"``. Only ``ADCP_ENV`` and ``ADCP_VALIDATION_MODE`` are consulted — generic ``ENV`` / ``ENVIRONMENT`` would collide with unrelated tooling (rails, postgres, 12-factor) and silently flip the SDK's default. """ requests: ValidationMode | None = None responses: ValidationMode | None = None #: Server-side policy for unsupported top-level tool arguments. #: ``None`` preserves existing permissive behavior. unknown_fields: UnknownFieldPolicy | Literal["reject", "strip", "ignore"] | None = NonePer-side client validation modes.
Defaults match the TS port (adcontextprotocol/adcp-client#694):
requests:"warn"— strict would break callers that intentionally send partial payloads (error-path tests, exploratory probes). Storyboards and compliance runners that want hard-stop enforcement passrequests="strict"explicitly.responses:"strict"in dev/test,"warn"whenADCP_ENVis set toproduction/prod. Strict-by-default makes the SDK a compliance harness: drift from an agent fails the task on the first call, not the Nth storyboard run.
Resolution order for both sides at call time:
- Explicit value on this config (
requests=/responses=). ADCP_VALIDATION_MODEenv var (strict/warn/off) — applies to both sides unless overridden by an explicit value. Matches the TS port (adcontextprotocol/adcp-client).ADCP_ENV=prod|productionflips the response default towarn; requests fall back to the type default.- Defaults:
requests="warn",responses="strict".
Only
ADCP_ENVandADCP_VALIDATION_MODEare consulted — genericENV/ENVIRONMENTwould collide with unrelated tooling (rails, postgres, 12-factor) and silently flip the SDK's default.Instance variables
var requests : Literal['strict', 'warn', 'off'] | Nonevar responses : Literal['strict', 'warn', 'off'] | Nonevar unknown_fields : UnknownFieldPolicy | Literal['reject', 'strip', 'ignore'] | None-
Server-side policy for unsupported top-level tool arguments.
Nonepreserves existing permissive behavior.
class ValidationIssue (pointer: str, message: str, keyword: str, schema_path: str, hint: str | None = None)-
Expand source code
@dataclass(frozen=True) class ValidationIssue: """A single validation failure. Attributes: pointer: RFC 6901 JSON Pointer to the offending field. message: Sanitized, value-free description of the failure. Safe to return over the wire; does not echo input data. keyword: jsonschema keyword that rejected the payload (``required``, ``type``, ``enum``, etc.). schema_path: Path inside the schema that rejected the payload. hint: Optional near-miss diagnostic naming the closest matching ``oneOf`` variant and the wrong discriminator key. Only populated when the heuristic in :mod:`adcp.validation.oneof_hints` picks a clear winner; ``None`` otherwise. Additive — clients that ignore the field behave as before. """ pointer: str message: str keyword: str schema_path: str hint: str | None = NoneA single validation failure.
Attributes
pointer- RFC 6901 JSON Pointer to the offending field.
message- Sanitized, value-free description of the failure. Safe to return over the wire; does not echo input data.
keyword- jsonschema keyword that rejected the payload
(
required,type,enum, etc.). schema_path- Path inside the schema that rejected the payload.
hint- Optional near-miss diagnostic naming the closest matching
oneOfvariant and the wrong discriminator key. Only populated when the heuristic in :mod:adcp.validation.oneof_hintspicks a clear winner;Noneotherwise. Additive — clients that ignore the field behave as before.
Instance variables
var hint : str | Nonevar keyword : strvar message : strvar pointer : strvar schema_path : str
class ValidationOutcome (valid: bool,
issues: list[ValidationIssue] = <factory>,
variant: str = 'skipped')-
Expand source code
@dataclass(frozen=True) class ValidationOutcome: valid: bool issues: list[ValidationIssue] = field(default_factory=list) variant: str = "skipped"ValidationOutcome(valid: 'bool', issues: 'list[ValidationIssue]' =
, variant: 'str' = 'skipped') Instance variables
var issues : list[ValidationIssue]var valid : boolvar variant : str
class ValidationResult (**data: Any)-
Expand source code
class ValidationResult(RegistryBaseModel): valid: bool domain: str | None = None url: str | None = None errors: list[str | dict[str, Any]] | None = None warnings: list[str | dict[str, Any]] | None = None status_code: int | None = None raw_data: dict[str, Any] | None = NoneBase model for registry API types.
Uses
extra='allow'so that new fields from the registry API are preserved rather than dropped. This differs from AdCPBaseModel which defaults toextra='ignore'for protocol types.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- RegistryBaseModel
- pydantic.main.BaseModel
Class variables
var domain : str | Nonevar errors : list[str | dict[str, typing.Any]] | Nonevar model_configvar raw_data : dict[str, typing.Any] | Nonevar status_code : int | Nonevar url : str | Nonevar valid : boolvar warnings : list[str | dict[str, typing.Any]] | None
class UrlVastAsset (**data: Any)-
Expand source code
class VastAsset1(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) asset_type: Annotated[ Literal['vast'], Field( description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' ), ] = 'vast' vast_version: Annotated[ vast_version_1.VastVersion | None, Field(description='VAST specification version') ] = None vpaid_enabled: Annotated[ bool | None, Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), ] = None duration_ms: Annotated[ int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) ] = None tracking_events: Annotated[ list[vast_tracking_event.VastTrackingEvent] | None, Field(description='Tracking events supported by this VAST tag'), ] = None captions_url: Annotated[ AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') ] = None audio_description_url: Annotated[ AnyUrl | None, Field(description='URL to audio description track for visually impaired users'), ] = None provenance: Annotated[ provenance_1.Provenance | None, Field( description='Provenance metadata for this asset, overrides manifest-level provenance' ), ] = None delivery_type: Annotated[ Literal['url'], Field(description='Discriminator indicating VAST is delivered via URL endpoint'), ] = 'url' url: Annotated[ str, Field( description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var asset_type : Literal['vast']var audio_description_url : pydantic.networks.AnyUrl | Nonevar captions_url : pydantic.networks.AnyUrl | Nonevar delivery_type : Literal['url']var duration_ms : int | Nonevar model_configvar provenance : adcp.types.generated_poc.core.provenance.Provenance | Nonevar tracking_events : list[adcp.types.generated_poc.enums.vast_tracking_event.VastTrackingEvent] | Nonevar url : strvar vast_version : adcp.types.generated_poc.enums.vast_version.VastVersion | Nonevar vpaid_enabled : bool | None
Inherited members
class InlineVastAsset (**data: Any)-
Expand source code
class VastAsset2(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) asset_type: Annotated[ Literal['vast'], Field( description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' ), ] = 'vast' vast_version: Annotated[ vast_version_1.VastVersion | None, Field(description='VAST specification version') ] = None vpaid_enabled: Annotated[ bool | None, Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), ] = None duration_ms: Annotated[ int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) ] = None tracking_events: Annotated[ list[vast_tracking_event.VastTrackingEvent] | None, Field(description='Tracking events supported by this VAST tag'), ] = None captions_url: Annotated[ AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') ] = None audio_description_url: Annotated[ AnyUrl | None, Field(description='URL to audio description track for visually impaired users'), ] = None provenance: Annotated[ provenance_1.Provenance | None, Field( description='Provenance metadata for this asset, overrides manifest-level provenance' ), ] = None delivery_type: Annotated[ Literal['inline'], Field(description='Discriminator indicating VAST is delivered as inline XML content'), ] = 'inline' content: Annotated[str, Field(description='Inline VAST XML content')]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var asset_type : Literal['vast']var audio_description_url : pydantic.networks.AnyUrl | Nonevar captions_url : pydantic.networks.AnyUrl | Nonevar content : strvar delivery_type : Literal['inline']var duration_ms : int | Nonevar model_configvar provenance : adcp.types.generated_poc.core.provenance.Provenance | Nonevar tracking_events : list[adcp.types.generated_poc.enums.vast_tracking_event.VastTrackingEvent] | Nonevar vast_version : adcp.types.generated_poc.enums.vast_version.VastVersion | Nonevar vpaid_enabled : bool | None
Inherited members
class VcpmPricingOption (**data: Any)-
Expand source code
class VcpmPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[ Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') ] = 'vcpm' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['vcpm']var pricing_option_id : str
class VcpmAuctionPricingOption (**data: Any)-
Expand source code
class VcpmPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[ Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') ] = 'vcpm' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['vcpm']var pricing_option_id : str
class VcpmFixedRatePricingOption (**data: Any)-
Expand source code
class VcpmPricingOption(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) pricing_option_id: Annotated[ str, Field(description='Unique identifier for this pricing option within the product') ] pricing_model: Annotated[ Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') ] = 'vcpm' currency: Annotated[ str, Field( description='ISO 4217 currency code', examples=['USD', 'EUR', 'GBP', 'JPY'], pattern='^[A-Z]{3}$', ), ] fixed_price: Annotated[ float | None, Field( description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', ge=0.0, ), ] = None floor_price: Annotated[ float | None, Field( description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', ge=0.0, ), ] = None max_bid: Annotated[ bool | None, Field( description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." ), ] = False price_guidance: Annotated[ price_guidance_1.PriceGuidance | None, Field(description='Optional pricing guidance for auction-based bidding'), ] = None min_spend_per_package: Annotated[ float | None, Field( description='Minimum spend requirement per package using this pricing option, in the specified currency', ge=0.0, ), ] = None price_breakdown: Annotated[ price_breakdown_1.PriceBreakdown | None, Field( description='Breakdown of how fixed_price was derived from the list (rate card) price. Only meaningful when fixed_price is present.' ), ] = None eligible_adjustments: Annotated[ list[adjustment_kind.PriceAdjustmentKind] | None, Field( description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var currency : strvar eligible_adjustments : list[adcp.types.generated_poc.enums.adjustment_kind.PriceAdjustmentKind] | Nonevar fixed_price : float | Nonevar floor_price : float | Nonevar max_bid : bool | Nonevar min_spend_per_package : float | Nonevar model_configvar price_breakdown : adcp.types.generated_poc.pricing_options.price_breakdown.PriceBreakdown | Nonevar price_guidance : adcp.types.generated_poc.pricing_options.price_guidance.PriceGuidance | Nonevar pricing_model : Literal['vcpm']var pricing_option_id : str
Inherited members
class VerifyBrandClaimPayload (**data: Any)-
Expand source code
class VerifyBrandClaimPayload(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) typ: Annotated[ Literal['adcp-response-payload+jws'], Field(description='Type discriminator preventing cross-profile replay.'), ] task: Annotated[ Literal['verify_brand_claim'], Field(description='Designated task whose response payload is signed.'), ] brand_domain: Annotated[ str, Field( description='Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields.', pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] agent_url: Annotated[ AnyUrl, Field( description='Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope.' ), ] request_hash: Annotated[ str, Field( description='sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call.', pattern='^sha256:[A-Za-z0-9_-]{43}$', ), ] iat: Annotated[int, Field(description='Issued-at time as Unix epoch seconds.', ge=0)] exp: Annotated[ int, Field( description='Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew.', ge=0, ), ] response: VerifyBrandClaimSignedSuccessPayloadBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var agent_url : pydantic.networks.AnyUrlvar brand_domain : strvar exp : intvar iat : intvar model_configvar request_hash : strvar response : adcp.types.generated_poc.brand.verify_brand_claim_response.VerifyBrandClaimSignedSuccessPayloadvar task : Literal['verify_brand_claim']var typ : Literal['adcp-response-payload+jws']
Inherited members
class VerifyBrandClaimRequest (**data: Any)-
Expand source code
class VerifyBrandClaimRequest(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) claim_type: Annotated[ ClaimType, Field(description='Discriminates the kind of brand claim being verified.'), ] claim: Annotated[ dict[str, Any], Field(description='Claim payload. Shape varies by claim_type.'), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var claim : dict[str, typing.Any]var claim_type : adcp.types.generated_poc.brand.verify_brand_claim_request.ClaimTypevar model_config
Inherited members
class VerifyBrandClaimSignedResponse (**data: Any)-
Expand source code
class VerifyBrandClaimSignedResponse(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) protected: Annotated[ str, Field( description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', pattern='^[A-Za-z0-9_-]+$', ), ] payload: Annotated[ VerifyBrandClaimPayload, Field( description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' ), ] signature: Annotated[ str, Field( description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', pattern='^[A-Za-z0-9_-]+$', ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar payload : adcp.types.generated_poc.brand.verify_brand_claim_response.VerifyBrandClaimPayloadvar protected : strvar signature : str
Inherited members
class VerifyBrandClaimSignedSuccessPayload (**data: Any)-
Expand source code
class VerifyBrandClaimSignedSuccessPayload(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) claim_type: ClaimType verification_status: verification_status.VerificationStatus details: dict[str, Any] | None = None context_note: Annotated[str | None, Field(max_length=500)] = None context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var claim_type : adcp.types.generated_poc.brand.verify_brand_claim_response.ClaimTypevar context : adcp.types.generated_poc.core.context.ContextObject | Nonevar context_note : str | Nonevar details : dict[str, typing.Any] | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar verification_status : adcp.types.generated_poc.brand.verification_status.VerificationStatus
Inherited members
class VerifyBrandClaimsErrorResponse (**data: Any)-
Expand source code
class VerifyBrandClaimsErrorResponse(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) errors: Annotated[list[error_1.Error], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar errors : list[adcp.types.generated_poc.core.error.Error]var ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_config
Inherited members
class VerifyBrandClaimsPayload (**data: Any)-
Expand source code
class VerifyBrandClaimsPayload(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) typ: Annotated[ Literal['adcp-response-payload+jws'], Field(description='Type discriminator preventing cross-profile replay.'), ] task: Annotated[ Literal['verify_brand_claims'], Field(description='Designated task whose response payload is signed.'), ] brand_domain: Annotated[ str, Field( description='Brand tenant whose policy store produced the answer. The signer MUST derive this from server-side tenant resolution, not caller-supplied request fields.', pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] agent_url: Annotated[ AnyUrl, Field( description='Canonical URL of the responding brand agent entry whose response-signing key verifies this envelope.' ), ] request_hash: Annotated[ str, Field( description='sha256: prefix plus unpadded base64url SHA-256 of the canonical request-binding object for this call.', pattern='^sha256:[A-Za-z0-9_-]{43}$', ), ] iat: Annotated[int, Field(description='Issued-at time as Unix epoch seconds.', ge=0)] exp: Annotated[ int, Field( description='Expiration time as Unix epoch seconds. Online verifiers reject envelopes after this time, allowing only implementation-defined clock skew.', ge=0, ), ] response: VerifyBrandClaimsSignedSuccessPayloadBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var agent_url : pydantic.networks.AnyUrlvar brand_domain : strvar exp : intvar iat : intvar model_configvar request_hash : strvar response : adcp.types.generated_poc.brand.verify_brand_claims_response.VerifyBrandClaimsSignedSuccessPayloadvar task : Literal['verify_brand_claims']var typ : Literal['adcp-response-payload+jws']
Inherited members
class VerifyBrandClaimsRequest (**data: Any)-
Expand source code
class VerifyBrandClaimsRequest(VerifyBrandClaimsRequestBulk): passBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.brand.verify_brand_claims_request.VerifyBrandClaimsRequestBulk
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_config
Inherited members
class VerifyBrandClaimsRequestBulk (**data: Any)-
Expand source code
class VerifyBrandClaimsRequestBulk(AdcpVersionEnvelope): model_config = ConfigDict( extra='allow', ) claims: Annotated[ list[ClaimEntry], Field( description='Ordered list of verification claims. The agent MUST return `results[]` in the same order (positional zip-by-index). Maximum batch size is 100 per call; agents MAY enforce a lower limit and SHOULD advertise it via `get_adcp_capabilities` (see the task page).', max_length=100, min_length=1, ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Subclasses
- adcp.types.generated_poc.brand.verify_brand_claims_request.VerifyBrandClaimsRequest
Class variables
var claims : list[adcp.types.generated_poc.brand.verify_brand_claims_request.ClaimEntry]var model_config
Inherited members
class VerifyBrandClaimsResponseBulk (**data: Any)-
Expand source code
class VerifyBrandClaimsResponseBulk(AdcpVersionEnvelope, ProtocolEnvelope): model_config = ConfigDict( extra='allow', ) results: Annotated[ list[ResultEntry], Field( description="Per-claim results, positionally aligned with the request's claims.", min_length=1, ), ] signed_response: Annotated[ VerifyBrandClaimsSignedResponse, Field( description='Payload-envelope JWS attesting the canonical bulk success response for verify_brand_claims. The signed payload response MUST match the unsigned task-body fields on this response, excluding signed_response and protocol/version envelope fields.' ), ] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- adcp.types.generated_poc.core.version_envelope.AdcpVersionEnvelope
- adcp.types.generated_poc.core.protocol_envelope.ProtocolEnvelope
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar results : list[adcp.types.generated_poc.brand.verify_brand_claims_response.ResultEntry]var signed_response : adcp.types.generated_poc.brand.verify_brand_claims_response.VerifyBrandClaimsSignedResponse
Inherited members
class VerifyBrandClaimsSignedResponse (**data: Any)-
Expand source code
class VerifyBrandClaimsSignedResponse(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) protected: Annotated[ str, Field( description='Base64url-encoded JWS protected header. The decoded header MUST include alg, kid, and typ: adcp-response-payload+jws, and MUST NOT include the RFC 7797 b64 header. Verifiers enforce the key purpose by resolving kid to a JWK with adcp_use: response-signing.', pattern='^[A-Za-z0-9_-]+$', ), ] payload: Annotated[ VerifyBrandClaimsPayload, Field( description='Decoded signed payload. Signers compute the JWS payload bytes from the RFC 8785/JCS canonicalization of this object.' ), ] signature: Annotated[ str, Field( description='Base64url-encoded JWS signature over the protected header and canonicalized payload.', pattern='^[A-Za-z0-9_-]+$', ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var model_configvar payload : adcp.types.generated_poc.brand.verify_brand_claims_response.VerifyBrandClaimsPayloadvar protected : strvar signature : str
Inherited members
class VerifyBrandClaimsSignedSuccessPayload (**data: Any)-
Expand source code
class VerifyBrandClaimsSignedSuccessPayload(AdCPBaseModel): model_config = ConfigDict( extra='allow', ) results: Annotated[list[ResultEntry], Field(min_length=1)] context: context_1.ContextObject | None = None ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var context : adcp.types.generated_poc.core.context.ContextObject | Nonevar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar model_configvar results : list[adcp.types.generated_poc.brand.verify_brand_claims_response.ResultEntry]
Inherited members
class WcagLevel (*args, **kwds)-
Expand source code
class WcagLevel(StrEnum): A = 'A' AA = 'AA' AAA = 'AAA'Enum where members are also (and must be) strings
Ancestors
- enum.StrEnum
- builtins.str
- enum.ReprEnum
- enum.Enum
Class variables
var Avar AAvar AAA
class WebhookChallenge (**data: Any)-
Expand source code
class WebhookChallenge(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) type: Annotated[ Literal['webhook.challenge'], Field(description='Discriminator for endpoint proof-of-control challenges.'), ] = 'webhook.challenge' challenge: Annotated[ str, Field( description='Opaque, cryptographically random value that the receiver must echo in the response body. Recommended encoding: base64url without padding.', max_length=255, min_length=32, pattern='^[A-Za-z0-9_.:-]{32,255}$', ), ] account_id: Annotated[ str, Field( description='Seller account identifier for the account whose notification_configs[] entry is being challenged.' ), ] subscriber_id: Annotated[ str, Field( description='Buyer-supplied subscriber identifier from the notification_configs[] entry being challenged.', max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$', ), ] seller_agent_url: Annotated[ AnyUrl, Field( description='Exact seller agent URL whose RFC 9421 webhook profile key signs this challenge and that will send subsequent webhooks.' ), ] delivery_auth: Annotated[ DeliveryAuth, Field( description='Authentication/signing mode the seller will use for subsequent webhooks delivered to this notification config.' ), ] event_types: Annotated[ list[notification_type.NotificationType], Field( description='Normalized notification types requested by the subscriber at the time of the challenge. Part of the endpoint proof scope; changing event_types[] requires a fresh challenge before the new set can become active.', min_length=1, ), ]Base model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account_id : strvar challenge : strvar delivery_auth : adcp.types.generated_poc.core.webhook_challenge.DeliveryAuthvar event_types : list[adcp.types.generated_poc.enums.notification_type.NotificationType]var model_configvar seller_agent_url : pydantic.networks.AnyUrlvar subscriber_id : strvar type : Literal['webhook.challenge']
Inherited members
class WebhookChallengeError (message: str,
*,
reason: str,
field: str | None = None,
url: str | None = None,
status_code: int | None = None,
suggestion: str | None = None)-
Expand source code
class WebhookChallengeError(ValueError): """Typed proof-of-control failure suitable for ``sync_accounts`` errors.""" code = "INVALID_REQUEST" def __init__( self, message: str, *, reason: str, field: str | None = None, url: str | None = None, status_code: int | None = None, suggestion: str | None = None, ) -> None: super().__init__(message) self.reason = reason self.field = field self.url = url self.status_code = status_code self.suggestion = suggestion def to_error(self) -> dict[str, str]: """Return a small ``errors[]``-compatible dict for seller handlers.""" error = {"code": self.code, "message": str(self)} if self.field is not None: error["field"] = self.field if self.suggestion is not None: error["suggestion"] = self.suggestion return errorTyped proof-of-control failure suitable for
sync_accountserrors.Ancestors
- builtins.ValueError
- builtins.Exception
- builtins.BaseException
Class variables
var code
Methods
def to_error(self) ‑> dict[str, str]-
Expand source code
def to_error(self) -> dict[str, str]: """Return a small ``errors[]``-compatible dict for seller handlers.""" error = {"code": self.code, "message": str(self)} if self.field is not None: error["field"] = self.field if self.suggestion is not None: error["suggestion"] = self.suggestion return errorReturn a small
errors[]-compatible dict for seller handlers.
class WebhookChallengeResponse (**data: Any)-
Expand source code
class WebhookChallengeResponse(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) challenge: Annotated[ str | None, Field( description='Echo of the challenge value supplied by the seller.', max_length=255, min_length=32, pattern='^[A-Za-z0-9_.:-]{32,255}$', ), ] = None token: Annotated[ str | None, Field( description='Backward-compatible alias for `challenge`. Receivers SHOULD prefer `challenge`; sellers MUST accept either field.', max_length=255, min_length=32, pattern='^[A-Za-z0-9_.:-]{32,255}$', ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var challenge : str | Nonevar model_configvar token : str | None
Inherited members
class WebhookChallengeResult (challenge: str,
echoed_field: str,
destination: WebhookDestinationValidation,
status_code: int,
response_headers: Mapping[str, str],
response_body: bytes)-
Expand source code
@dataclass(frozen=True) class WebhookChallengeResult: """Successful durable webhook proof-of-control challenge.""" challenge: str echoed_field: str destination: WebhookDestinationValidation status_code: int response_headers: Mapping[str, str] response_body: bytes @property def ok(self) -> bool: return 200 <= self.status_code < 300Successful durable webhook proof-of-control challenge.
Instance variables
var challenge : strvar destination : WebhookDestinationValidationvar echoed_field : strprop ok : bool-
Expand source code
@property def ok(self) -> bool: return 200 <= self.status_code < 300 var response_body : bytesvar response_headers : Mapping[str, str]var status_code : int
class WebhookDedupStore (backend: IdempotencyBackend,
ttl_seconds: int = 86400,
*,
namespace: str = 'webhook',
clock: Callable[[], float] = <built-in function time>)-
Expand source code
class WebhookDedupStore: """Dedup ``(sender_id, idempotency_key)`` pairs to suppress retried webhooks. :param backend: any :class:`IdempotencyBackend`. Same MemoryBackend or PgBackend type used by :class:`IdempotencyStore` is fine — the ``namespace`` parameter prefixes all sender IDs so request-side and webhook-side scopes can't alias even when sharing one backend instance. :param ttl_seconds: replay window. Must be within ``[86400, 604800]`` per the spec minimum. Defaults to 86400 (24h). :param namespace: prefix applied to every ``sender_id`` before it hits the backend. Defaults to ``"webhook"``, which is safe when the same backend is shared with :class:`IdempotencyStore` (request-side keys are scoped by a principal_id that isn't wrapped in this namespace, so collisions are impossible). Override only if you run multiple webhook scopes against one backend (e.g., separate dedup spaces for task webhooks vs list-change webhooks). """ def __init__( self, backend: IdempotencyBackend, ttl_seconds: int = _MIN_TTL_SECONDS, *, namespace: str = "webhook", clock: Callable[[], float] = time.time, ) -> None: if not _MIN_TTL_SECONDS <= ttl_seconds <= _MAX_TTL_SECONDS: raise ValueError( f"ttl_seconds must be in [{_MIN_TTL_SECONDS}, {_MAX_TTL_SECONDS}] " f"per webhook spec minimum, got {ttl_seconds}" ) if not namespace: raise ValueError("namespace must be a non-empty string") self.backend = backend self.ttl_seconds = ttl_seconds self.namespace = namespace self._clock = clock async def check_and_record(self, sender_id: str, idempotency_key: str) -> bool: """Atomically check for first-seen and record if new. Returns ``True`` when the pair is first-seen (event should be processed), ``False`` on duplicate (caller MUST still return 2xx to the sender — the event was delivered successfully, it's just a retry). Race note: the check-then-put pattern is not atomic across concurrent callers unless the backend provides its own atomicity. MemoryBackend serializes individual ``get`` and ``put`` under an ``asyncio.Lock`` but does NOT bracket them together — two concurrent retries of the same event CAN both observe "first-seen" and both process the event. That's a tolerable failure mode: the ultimate guarantee is "at most once per replay window in the common case"; a concurrent retry arriving in the same few milliseconds is rare and, if it happens, produces the same "duplicated side effect" outcome the at-least-once contract already warns callers to tolerate. PgBackend implementations SHOULD use ``INSERT ... ON CONFLICT DO NOTHING`` returning ``rowcount`` for lock-free atomicity. """ if not sender_id: raise ValueError("sender_id must be a non-empty string") if not idempotency_key: raise ValueError("idempotency_key must be a non-empty string") scoped_sender = f"{self.namespace}:{sender_id}" existing = await self.backend.get(scoped_sender, idempotency_key) if existing is not None: logger.debug( "webhook dedup: duplicate sender=%s key_prefix=%s", sender_id, idempotency_key[:8], ) return False entry = CachedResponse( payload_hash=_SENTINEL_HASH, response={}, expires_at_epoch=self._clock() + self.ttl_seconds, ) try: await self.backend.put(scoped_sender, idempotency_key, entry) except Exception: # Same fail-open reasoning as the request-side store: log and # process. Swallowing the put failure means this event MIGHT # reprocess on retry, not that we drop it. Better than raising, # which would look like handler failure to the sender. logger.warning( "webhook dedup put failed for sender=%s key_prefix=%s — " "event processed but next retry will reprocess", sender_id, idempotency_key[:8], exc_info=True, ) return TrueDedup
(sender_id, idempotency_key)pairs to suppress retried webhooks.:param backend: any :class:
IdempotencyBackend. Same MemoryBackend or PgBackend type used by :class:IdempotencyStoreis fine — thenamespaceparameter prefixes all sender IDs so request-side and webhook-side scopes can't alias even when sharing one backend instance. :param ttl_seconds: replay window. Must be within[86400, 604800]per the spec minimum. Defaults to 86400 (24h). :param namespace: prefix applied to everysender_idbefore it hits the backend. Defaults to"webhook", which is safe when the same backend is shared with :class:IdempotencyStore(request-side keys are scoped by a principal_id that isn't wrapped in this namespace, so collisions are impossible). Override only if you run multiple webhook scopes against one backend (e.g., separate dedup spaces for task webhooks vs list-change webhooks).Methods
async def check_and_record(self, sender_id: str, idempotency_key: str) ‑> bool-
Expand source code
async def check_and_record(self, sender_id: str, idempotency_key: str) -> bool: """Atomically check for first-seen and record if new. Returns ``True`` when the pair is first-seen (event should be processed), ``False`` on duplicate (caller MUST still return 2xx to the sender — the event was delivered successfully, it's just a retry). Race note: the check-then-put pattern is not atomic across concurrent callers unless the backend provides its own atomicity. MemoryBackend serializes individual ``get`` and ``put`` under an ``asyncio.Lock`` but does NOT bracket them together — two concurrent retries of the same event CAN both observe "first-seen" and both process the event. That's a tolerable failure mode: the ultimate guarantee is "at most once per replay window in the common case"; a concurrent retry arriving in the same few milliseconds is rare and, if it happens, produces the same "duplicated side effect" outcome the at-least-once contract already warns callers to tolerate. PgBackend implementations SHOULD use ``INSERT ... ON CONFLICT DO NOTHING`` returning ``rowcount`` for lock-free atomicity. """ if not sender_id: raise ValueError("sender_id must be a non-empty string") if not idempotency_key: raise ValueError("idempotency_key must be a non-empty string") scoped_sender = f"{self.namespace}:{sender_id}" existing = await self.backend.get(scoped_sender, idempotency_key) if existing is not None: logger.debug( "webhook dedup: duplicate sender=%s key_prefix=%s", sender_id, idempotency_key[:8], ) return False entry = CachedResponse( payload_hash=_SENTINEL_HASH, response={}, expires_at_epoch=self._clock() + self.ttl_seconds, ) try: await self.backend.put(scoped_sender, idempotency_key, entry) except Exception: # Same fail-open reasoning as the request-side store: log and # process. Swallowing the put failure means this event MIGHT # reprocess on retry, not that we drop it. Better than raising, # which would look like handler failure to the sender. logger.warning( "webhook dedup put failed for sender=%s key_prefix=%s — " "event processed but next retry will reprocess", sender_id, idempotency_key[:8], exc_info=True, ) return TrueAtomically check for first-seen and record if new.
Returns
Truewhen the pair is first-seen (event should be processed),Falseon duplicate (caller MUST still return 2xx to the sender — the event was delivered successfully, it's just a retry).Race note: the check-then-put pattern is not atomic across concurrent callers unless the backend provides its own atomicity. MemoryBackend serializes individual
getandputunder anasyncio.Lockbut does NOT bracket them together — two concurrent retries of the same event CAN both observe "first-seen" and both process the event. That's a tolerable failure mode: the ultimate guarantee is "at most once per replay window in the common case"; a concurrent retry arriving in the same few milliseconds is rare and, if it happens, produces the same "duplicated side effect" outcome the at-least-once contract already warns callers to tolerate. PgBackend implementations SHOULD useINSERT … ON CONFLICT DO NOTHINGreturningrowcountfor lock-free atomicity.
class WebhookDestinationPolicy (require_https: bool = True,
allow_private_destinations: bool = False,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = (),
name: str = 'production')-
Expand source code
@dataclass(frozen=True) class WebhookDestinationPolicy: """Registration-time policy for durable buyer webhook URLs. Use :meth:`production` before persisting buyer-provided ``push_notification_config.url`` or ``accounts[].notification_configs[].url``. Use :meth:`local_development` only for tests and local fixtures that need HTTP localhost or private-network endpoints. """ require_https: bool = True allow_private_destinations: bool = False allowed_destination_ports: frozenset[int] | None = None transport_hooks: tuple[TransportHook, ...] = () name: str = "production" @classmethod def production( cls, *, allowed_destination_ports: frozenset[int] | None = None, transport_hooks: tuple[TransportHook, ...] = (), ) -> WebhookDestinationPolicy: """Production webhook policy: HTTPS and public routable IPs only.""" return cls( require_https=True, allow_private_destinations=False, allowed_destination_ports=allowed_destination_ports, transport_hooks=transport_hooks, name="production", ) @classmethod def local_development( cls, *, allowed_destination_ports: frozenset[int] | None = None, transport_hooks: tuple[TransportHook, ...] = (), ) -> WebhookDestinationPolicy: """Explicit dev/test policy: allows HTTP and private destinations. Cloud metadata endpoints remain blocked by the shared SSRF validator even when private destinations are allowed. """ return cls( require_https=False, allow_private_destinations=True, allowed_destination_ports=allowed_destination_ports, transport_hooks=transport_hooks, name="local_development", )Registration-time policy for durable buyer webhook URLs.
Use :meth:
productionbefore persisting buyer-providedpush_notification_config.urloraccounts[].notification_configs[].url. Use :meth:local_developmentonly for tests and local fixtures that need HTTP localhost or private-network endpoints.Static methods
def local_development(*,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = ()) ‑> WebhookDestinationPolicy-
Explicit dev/test policy: allows HTTP and private destinations.
Cloud metadata endpoints remain blocked by the shared SSRF validator even when private destinations are allowed.
def production(*,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = ()) ‑> WebhookDestinationPolicy-
Production webhook policy: HTTPS and public routable IPs only.
Instance variables
var allow_private_destinations : boolvar allowed_destination_ports : frozenset[int] | Nonevar name : strvar require_https : boolvar transport_hooks : tuple[TransportHook, ...]
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: strMetadata 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.main.BaseModel
Class variables
var agent_id : strvar model_configvar notification_type : Literal['scheduled', 'final', 'delayed'] | Nonevar operation_id : strvar sequence_number : int | Nonevar status : TaskStatusvar task_type : strvar timestamp : str
class WebhookReceiver (config: WebhookReceiverConfig)-
Expand source code
class WebhookReceiver: """Stateless webhook entry point, one instance per receiver configuration. Instance state (``config``) is read-only after construction. Per-request state lives in the :class:`WebhookOutcome` returned from :meth:`receive`. """ def __init__(self, config: WebhookReceiverConfig) -> None: self._config = config async def receive( self, *, method: str, url: str, headers: Mapping[str, str], body: bytes, ) -> WebhookOutcome: """Verify, dedupe, parse. Returns a :class:`WebhookOutcome`. Never raises for sender-caused cryptographic or protocol failures — returns an outcome with ``rejected=True`` and populated ``response_headers`` so the caller can convert to an HTTP response without try/except around every call. Operational failures inside the dedup backend or verify-options factory MAY still raise; wrap the call if you need to 5xx cleanly on internal errors. """ if not _content_type_is_json(headers): return _reject("content_type_invalid", sender_identity=None) signer, rejection = await self._verify(method=method, url=url, headers=headers, body=body) if rejection is not None: return rejection assert signer is not None # verification succeeded sender_id = signer.as_sender_identity() try: payload_dict = json.loads(body) except json.JSONDecodeError: return _reject("body_invalid_json", sender_identity=sender_id) if not isinstance(payload_dict, dict): return _reject("body_invalid_json", sender_identity=sender_id) idempotency_key = payload_dict.get("idempotency_key") if not isinstance(idempotency_key, str) or not idempotency_key: # Spec 3.0-rc: idempotency_key is REQUIRED on every webhook payload. return _reject("idempotency_key_missing", sender_identity=sender_id) if not _IDEMPOTENCY_KEY_RE.match(idempotency_key): # Non-conformant format — charset or length out of bounds. return _reject("idempotency_key_invalid", sender_identity=sender_id) parsed = self._parse(payload_dict) if parsed is None: return _reject("payload_invalid", sender_identity=sender_id) is_first_seen = await self._config.dedup.check_and_record( sender_id=sender_id, idempotency_key=idempotency_key ) return WebhookOutcome( sender_identity=sender_id, payload=parsed, duplicate=not is_first_seen, idempotency_key=idempotency_key, ) def receive_sync( self, *, method: str, url: str, headers: Mapping[str, str], body: bytes, ) -> WebhookOutcome: """Synchronous wrapper around :meth:`receive` for WSGI-style frameworks. Use this from Flask, Gunicorn sync workers, ``http.server``, or any other sync-only HTTP entry point where wrapping every call in ``asyncio.run(...)`` is just noise:: @app.post("/webhooks/adcp") def hook(): outcome = receiver.receive_sync( method=request.method, url=request.url, headers=dict(request.headers), body=request.get_data(), ) ... Raises :class:`RuntimeError` if invoked from a thread that already has a running event loop — the underlying verify / dedup path is async and cannot be driven from inside an active loop without blocking it. From async code, call :meth:`receive` directly. """ try: asyncio.get_running_loop() except RuntimeError: # No running loop in this thread — safe to spin one up. return asyncio.run(self.receive(method=method, url=url, headers=headers, body=body)) raise RuntimeError( "WebhookReceiver.receive_sync() cannot be called from a running " "event loop. Use `await receiver.receive(...)` instead." ) async def _verify( self, *, method: str, url: str, headers: Mapping[str, str], body: bytes, ) -> tuple[VerifiedSignerLike | None, WebhookOutcome | None]: """Returns (signer, None) on success or (None, rejection_outcome).""" has_9421 = _has_9421_headers(headers) if has_9421: try: signer = verify_webhook_signature( method=method, url=url, headers=headers, body=body, options=self._config.verify_options, ) return signer, None except SignatureVerificationError as exc: # Downgrade defense: when 9421 IS present but fails, do NOT # consult HMAC fallback by default. A MITM that stripped a # valid 9421 signature and replaced it with a forged HMAC one # is exactly what the downgrade guard exists for. fallback = self._config.legacy_hmac allow_hmac = fallback is not None and not fallback.only_when_9421_absent if not allow_hmac: return None, WebhookOutcome( rejected=True, rejection_reason="signature_invalid", response_headers=_www_authenticate_header(exc.code), ) logger.warning( "9421 webhook verify failed (%s); trying HMAC legacy because " "legacy_hmac.only_when_9421_absent=False is set", exc.code, ) fallback = self._config.legacy_hmac if fallback is None: # No 9421 headers AND no HMAC fallback configured → spec says 9421 # is baseline-required in 3.0, so this is non-conformant. return None, WebhookOutcome( rejected=True, rejection_reason="signature_missing", response_headers=_www_authenticate_header("webhook_signature_required"), ) hmac_options = fallback.options_for(headers) if hmac_options is None: return None, WebhookOutcome( rejected=True, rejection_reason="signature_missing", response_headers=_www_authenticate_header("webhook_signature_required"), ) try: legacy_signer = verify_webhook_hmac(headers=headers, body=body, options=hmac_options) return legacy_signer, None except LegacyWebhookHmacError: return None, WebhookOutcome( rejected=True, rejection_reason="signature_legacy_failed", response_headers=_www_authenticate_header("webhook_signature_invalid"), ) def _parse(self, payload_dict: dict[str, Any]) -> WebhookPayload | None: model = _MODEL_BY_KIND[self._config.kind] try: return cast(WebhookPayload, model.model_validate(payload_dict)) except ValidationError as exc: # Operators need the field-level reason to diagnose sender bugs. # The receiver still returns payload_invalid downstream; this is # just observability. logger.warning( "webhook payload failed %s validation: %s", self._config.kind, exc.errors(include_url=False), ) return NoneStateless webhook entry point, one instance per receiver configuration.
Instance state (
adcp.config) is read-only after construction. Per-request state lives in the :class:WebhookOutcomereturned from :meth:receive.Methods
async def receive(self, *, method: str, url: str, headers: Mapping[str, str], body: bytes) ‑> WebhookOutcome-
Expand source code
async def receive( self, *, method: str, url: str, headers: Mapping[str, str], body: bytes, ) -> WebhookOutcome: """Verify, dedupe, parse. Returns a :class:`WebhookOutcome`. Never raises for sender-caused cryptographic or protocol failures — returns an outcome with ``rejected=True`` and populated ``response_headers`` so the caller can convert to an HTTP response without try/except around every call. Operational failures inside the dedup backend or verify-options factory MAY still raise; wrap the call if you need to 5xx cleanly on internal errors. """ if not _content_type_is_json(headers): return _reject("content_type_invalid", sender_identity=None) signer, rejection = await self._verify(method=method, url=url, headers=headers, body=body) if rejection is not None: return rejection assert signer is not None # verification succeeded sender_id = signer.as_sender_identity() try: payload_dict = json.loads(body) except json.JSONDecodeError: return _reject("body_invalid_json", sender_identity=sender_id) if not isinstance(payload_dict, dict): return _reject("body_invalid_json", sender_identity=sender_id) idempotency_key = payload_dict.get("idempotency_key") if not isinstance(idempotency_key, str) or not idempotency_key: # Spec 3.0-rc: idempotency_key is REQUIRED on every webhook payload. return _reject("idempotency_key_missing", sender_identity=sender_id) if not _IDEMPOTENCY_KEY_RE.match(idempotency_key): # Non-conformant format — charset or length out of bounds. return _reject("idempotency_key_invalid", sender_identity=sender_id) parsed = self._parse(payload_dict) if parsed is None: return _reject("payload_invalid", sender_identity=sender_id) is_first_seen = await self._config.dedup.check_and_record( sender_id=sender_id, idempotency_key=idempotency_key ) return WebhookOutcome( sender_identity=sender_id, payload=parsed, duplicate=not is_first_seen, idempotency_key=idempotency_key, )Verify, dedupe, parse. Returns a :class:
WebhookOutcome.Never raises for sender-caused cryptographic or protocol failures — returns an outcome with
rejected=Trueand populatedresponse_headersso the caller can convert to an HTTP response without try/except around every call. Operational failures inside the dedup backend or verify-options factory MAY still raise; wrap the call if you need to 5xx cleanly on internal errors. def receive_sync(self, *, method: str, url: str, headers: Mapping[str, str], body: bytes) ‑> WebhookOutcome-
Expand source code
def receive_sync( self, *, method: str, url: str, headers: Mapping[str, str], body: bytes, ) -> WebhookOutcome: """Synchronous wrapper around :meth:`receive` for WSGI-style frameworks. Use this from Flask, Gunicorn sync workers, ``http.server``, or any other sync-only HTTP entry point where wrapping every call in ``asyncio.run(...)`` is just noise:: @app.post("/webhooks/adcp") def hook(): outcome = receiver.receive_sync( method=request.method, url=request.url, headers=dict(request.headers), body=request.get_data(), ) ... Raises :class:`RuntimeError` if invoked from a thread that already has a running event loop — the underlying verify / dedup path is async and cannot be driven from inside an active loop without blocking it. From async code, call :meth:`receive` directly. """ try: asyncio.get_running_loop() except RuntimeError: # No running loop in this thread — safe to spin one up. return asyncio.run(self.receive(method=method, url=url, headers=headers, body=body)) raise RuntimeError( "WebhookReceiver.receive_sync() cannot be called from a running " "event loop. Use `await receiver.receive(...)` instead." )Synchronous wrapper around :meth:
receivefor WSGI-style frameworks.Use this from Flask, Gunicorn sync workers,
http.server, or any other sync-only HTTP entry point where wrapping every call inasyncio.run(…)is just noise::@app.post("/webhooks/adcp") def hook(): outcome = receiver.receive_sync( method=request.method, url=request.url, headers=dict(request.headers), body=request.get_data(), ) ...Raises :class:
RuntimeErrorif invoked from a thread that already has a running event loop — the underlying verify / dedup path is async and cannot be driven from inside an active loop without blocking it. From async code, call :meth:receivedirectly.
class WebhookReceiverConfig (verify_options: WebhookVerifyOptions,
dedup: WebhookDedupStore,
legacy_hmac: LegacyHmacFallback | None = None,
kind: WebhookKind = 'mcp')-
Expand source code
@dataclass(frozen=True) class WebhookReceiverConfig: """Configuration bundle. :param verify_options: verifier configuration (JWKS, replay store, etc.). A single instance is reused for every request — the verifier stamps ``now`` itself via ``verify_options.clock()``, so there's no need to refresh a time field per request. :param dedup: webhook-dedup store. :param legacy_hmac: optional HMAC-SHA256 fallback for 3.x migration. :param kind: which webhook payload type to parse into. Default ``"mcp"`` (the task-status webhook that dominates most integrations); pass explicitly for list-change / artifact / revocation receivers. """ verify_options: WebhookVerifyOptions dedup: WebhookDedupStore legacy_hmac: LegacyHmacFallback | None = None kind: WebhookKind = "mcp"Configuration bundle.
:param verify_options: verifier configuration (JWKS, replay store, etc.). A single instance is reused for every request — the verifier stamps
nowitself viaverify_options.clock(), so there's no need to refresh a time field per request. :param dedup: webhook-dedup store. :param legacy_hmac: optional HMAC-SHA256 fallback for 3.x migration. :param kind: which webhook payload type to parse into. Default"mcp"(the task-status webhook that dominates most integrations); pass explicitly for list-change / artifact / revocation receivers.Instance variables
var dedup : WebhookDedupStorevar kind : Literal['mcp', 'revocation_notification', 'collection_list_changed', 'property_list_changed', 'artifact']var legacy_hmac : LegacyHmacFallback | Nonevar verify_options : WebhookVerifyOptions
class WebhookSender (*,
private_key: PrivateKey,
key_id: str,
alg: str,
client: httpx.AsyncClient | None = None,
timeout_seconds: float = 10.0,
allow_private_destinations: bool = False,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = ())-
Expand source code
class WebhookSender: """Outbound signed-webhook delivery client. Owns one webhook-signing private key. Reuses a single :class:`httpx.AsyncClient` across requests for connection pooling — pass your own via ``client=`` if you want to share it with other SDK surfaces. Thread/task safety: safe to call concurrent ``send_*`` from many asyncio tasks. The underlying ``httpx.AsyncClient`` manages its own pool. """ def __init__( self, *, private_key: PrivateKey, key_id: str, alg: str, client: httpx.AsyncClient | None = None, timeout_seconds: float = _DEFAULT_TIMEOUT_SECONDS, allow_private_destinations: bool = False, allowed_destination_ports: frozenset[int] | None = None, transport_hooks: tuple[TransportHook, ...] = (), ) -> None: """Construct a sender wired to RFC 9421 JWK signing. The HMAC and bearer modes are reached via :meth:`from_bearer_token`, :meth:`from_adcp_legacy_hmac`, and :meth:`from_standard_webhooks_secret` — those classmethods bypass this initializer through :meth:`_from_strategy` because their key material has different types (``bytes`` / ``str`` rather than ``PrivateKey``). ``transport_hooks`` runs URL rewrites before SSRF validation — see :class:`adcp.webhook_transport_hooks.DockerLocalhostRewrite` for the canonical use case. SSRF remains authoritative on the rewritten URL; hooks cannot punch through the range check. """ self._auth: WebhookAuthStrategy = JwkSignerStrategy( private_key=private_key, key_id=key_id, alg=alg ) self._key_id = key_id self._timeout = timeout_seconds self._client = client self._owns_client = client is None self._allow_private_destinations = allow_private_destinations self._allowed_destination_ports = allowed_destination_ports self._transport_hooks = tuple(transport_hooks) _validate_hooks(self._transport_hooks, allow_private_destinations) @classmethod def _from_strategy( cls, auth: WebhookAuthStrategy, *, key_id: str, client: httpx.AsyncClient | None, timeout_seconds: float, allow_private_destinations: bool, allowed_destination_ports: frozenset[int] | None, transport_hooks: tuple[TransportHook, ...] = (), ) -> WebhookSender: """Build a sender around a pre-constructed auth strategy. Internal constructor for the HMAC/bearer paths. The public ``__init__`` is locked to the JWK signature for back-compat; new modes don't fit that signature, so they bypass it here. """ sender = cls.__new__(cls) sender._auth = auth sender._key_id = key_id sender._timeout = timeout_seconds sender._client = client sender._owns_client = client is None sender._allow_private_destinations = allow_private_destinations sender._allowed_destination_ports = allowed_destination_ports sender._transport_hooks = tuple(transport_hooks) _validate_hooks(sender._transport_hooks, allow_private_destinations) return sender @classmethod def from_jwk( cls, jwk: Mapping[str, Any], *, d_field: str = "d", client: httpx.AsyncClient | None = None, timeout_seconds: float = _DEFAULT_TIMEOUT_SECONDS, allow_private_destinations: bool = False, allowed_destination_ports: frozenset[int] | None = None, transport_hooks: tuple[TransportHook, ...] = (), ) -> WebhookSender: """Construct from a JWK that includes the private scalar. The JWK MUST have ``adcp_use == "webhook-signing"`` — the sender doesn't validate this (you're signing with your own key; validation happens at the receiver), but a key whose adcp_use is wrong will be rejected by every conformant verifier. ``allow_private_destinations`` and ``allowed_destination_ports`` forward to :meth:`__init__` — see that signature for semantics. """ # Snapshot the mapping once — a live Mapping could otherwise return # different values across the adcp_use / kid / d / alg reads. jwk_snapshot = dict(jwk) if jwk_snapshot.get("adcp_use") != "webhook-signing": raise ValueError( f"WebhookSender requires a JWK with adcp_use='webhook-signing' " f"(got {jwk_snapshot.get('adcp_use')!r}). Webhook-signing and " f"request-signing keys MUST be distinct so a signature from one " f"surface cannot be replayed as the other. Generate a separate " f"key with adcp_use='webhook-signing' and publish it in your " f"adagents.json alongside your request-signing key. See " f"https://adcontextprotocol.org/docs/building/implementation/security" ) alg = jwk_snapshot.get("alg") if alg == "EdDSA": alg = "ed25519" elif alg == "ES256": alg = "ecdsa-p256-sha256" if alg not in ("ed25519", "ecdsa-p256-sha256"): raise ValueError(f"unsupported JWK alg {jwk_snapshot.get('alg')!r}") private_key = private_key_from_jwk(jwk_snapshot, d_field=d_field) return cls( private_key=private_key, key_id=str(jwk_snapshot["kid"]), alg=alg, client=client, timeout_seconds=timeout_seconds, allow_private_destinations=allow_private_destinations, allowed_destination_ports=allowed_destination_ports, transport_hooks=transport_hooks, ) @classmethod def from_pem( cls, pem_path: str | Path | bytes, *, key_id: str, alg: str = "ed25519", passphrase: bytes | None = None, client: httpx.AsyncClient | None = None, timeout_seconds: float = _DEFAULT_TIMEOUT_SECONDS, allow_private_destinations: bool = False, allowed_destination_ports: frozenset[int] | None = None, transport_hooks: tuple[TransportHook, ...] = (), ) -> WebhookSender: """Load a private key from a PEM file and bind it as a webhook sender. Companion to ``adcp-keygen --purpose webhook-signing``, which writes the PEM and prints the public JWK. The JWK is published at your ``jwks_uri``; the PEM holds the private key material. ``from_pem`` reads the PEM, constructs the right ``PrivateKey`` type for ``alg``, and returns a sender ready to send. Args: pem_path: Path to the PKCS#8 PEM, or the PEM bytes directly. key_id: JWK ``kid`` claim — must match the published JWK. alg: Signature algorithm. ``ed25519`` (default) or ``es256``. Also accepts the RFC 9421 form ``ecdsa-p256-sha256``. passphrase: Required if the PEM is encrypted (``adcp-keygen --encrypt``). client: Optional pre-built :class:`httpx.AsyncClient` to share across the SDK; the sender owns its own client when omitted. timeout_seconds: Per-request timeout for the owned client. allow_private_destinations: Forwarded to :meth:`__init__`. allowed_destination_ports: Forwarded to :meth:`__init__`. Raises: ValueError: ``alg`` is not ed25519 / es256, or the PEM contains a key whose type doesn't match ``alg``. """ if alg in ("es256", "ES256"): alg = ALG_ES256 elif alg == "EdDSA": alg = ALG_ED25519 if alg not in (ALG_ED25519, ALG_ES256): raise ValueError( f"unsupported alg {alg!r} — use 'ed25519' or 'es256' " f"(the two AdCP webhook-signing algorithms)" ) if isinstance(pem_path, bytes): pem_bytes = pem_path else: pem_bytes = Path(pem_path).read_bytes() private_key = load_private_key_pem(pem_bytes, password=passphrase) # The PEM's key type must match the requested alg — mixing them # would produce signatures no verifier can validate, and the # resulting error at delivery time would point at the receiver. # Fail here so the misconfiguration surfaces at construction. if alg == ALG_ED25519 and not isinstance(private_key, ed25519.Ed25519PrivateKey): raise ValueError( f"PEM holds a {type(private_key).__name__} but alg='ed25519' " f"was requested. Re-run adcp-keygen with --alg ed25519, or " f"pass alg='es256' to match the existing PEM." ) if alg == ALG_ES256 and not isinstance(private_key, ec.EllipticCurvePrivateKey): raise ValueError( f"PEM holds a {type(private_key).__name__} but alg='es256' " f"was requested. Re-run adcp-keygen with --alg es256, or " f"pass alg='ed25519' to match the existing PEM." ) return cls( private_key=private_key, key_id=key_id, alg=alg, client=client, timeout_seconds=timeout_seconds, allow_private_destinations=allow_private_destinations, allowed_destination_ports=allowed_destination_ports, transport_hooks=transport_hooks, ) @classmethod def from_bearer_token( cls, token: str, *, client: httpx.AsyncClient | None = None, timeout_seconds: float = _DEFAULT_TIMEOUT_SECONDS, allow_private_destinations: bool = False, allowed_destination_ports: frozenset[int] | None = None, transport_hooks: tuple[TransportHook, ...] = (), ) -> WebhookSender: """Build a sender that POSTs with ``Authorization: Bearer <token>``. For buyers who authenticate the sender at the gateway and don't verify body signatures. The sender's marshaling guarantees still apply (byte-exact JSON, idempotency_key in body); body signing is skipped. A buyer treating bearer tokens as the sole authenticity signal SHOULD also enforce TLS/mTLS at the transport layer — a stolen token is a complete forgery. Prefer JWK signing (:meth:`from_jwk`) for AdCP-conformant deliveries. """ if not isinstance(token, str) or not token: raise ValueError("bearer token must be a non-empty string") return cls._from_strategy( BearerTokenStrategy(token=token), key_id="bearer", client=client, timeout_seconds=timeout_seconds, allow_private_destinations=allow_private_destinations, allowed_destination_ports=allowed_destination_ports, transport_hooks=transport_hooks, ) @classmethod def from_adcp_legacy_hmac( cls, secret: bytes, *, key_id: str, client: httpx.AsyncClient | None = None, timeout_seconds: float = _DEFAULT_TIMEOUT_SECONDS, allow_private_destinations: bool = False, allowed_destination_ports: frozenset[int] | None = None, transport_hooks: tuple[TransportHook, ...] = (), ) -> WebhookSender: """Build a sender wired to AdCP-legacy HMAC-SHA256. Wire format matches :func:`adcp.signing.webhook_hmac.verify_webhook_hmac`: ``X-AdCP-Signature: sha256=<hex>`` over ``f"{timestamp}.{body}"``, with ``X-AdCP-Timestamp`` set fresh per delivery (resends produce a new signature over the same body). ``secret`` is the raw HMAC key — the AdCP-legacy scheme has no canonical encoding, so callers pass bytes directly. ``key_id`` is echoed in ``X-AdCP-Key-Id`` for receiver-side multi-key rotation; it is not used in the signature itself. AdCP-legacy HMAC will be removed in AdCP 4.0 — operators SHOULD migrate to JWK signing (:meth:`from_jwk`) ahead of that boundary. """ if not isinstance(secret, bytes) or not secret: raise ValueError("hmac secret must be non-empty bytes") if not isinstance(key_id, str) or not key_id: raise ValueError("key_id must be a non-empty string") # Mirror the receiver-side _warn_once() in webhook_hmac so a # sender-only operator (no receiver in this process) still sees # the AdCP 4.0 deprecation signal at runtime, not just in the # docstring. _warn_legacy_hmac_once() return cls._from_strategy( AdcpLegacyHmacStrategy(secret=secret, key_id=key_id), key_id=key_id, client=client, timeout_seconds=timeout_seconds, allow_private_destinations=allow_private_destinations, allowed_destination_ports=allowed_destination_ports, transport_hooks=transport_hooks, ) @classmethod def from_standard_webhooks_secret( cls, secret: str, *, key_id: str, client: httpx.AsyncClient | None = None, timeout_seconds: float = _DEFAULT_TIMEOUT_SECONDS, allow_private_destinations: bool = False, allowed_destination_ports: frozenset[int] | None = None, transport_hooks: tuple[TransportHook, ...] = (), ) -> WebhookSender: """Build a sender wired to standardwebhooks.com v1 (Svix/Resend interop). ``secret`` is the canonical ``whsec_<base64>`` form distributed by buyers running Svix, Resend, or any other Standard Webhooks verifier. The constructor base64-decodes the prefix-stripped payload internally — passing the literal ``whsec_...`` to :meth:`from_adcp_legacy_hmac` would silently produce signatures Svix rejects, which is exactly the footgun this typed split prevents. Wire format per spec: ``webhook-id`` / ``webhook-timestamp`` / ``webhook-signature: v1,<base64>`` over ``f"{webhook_id}.{webhook_timestamp}.{body}"``. Each delivery gets a fresh ``webhook-id`` so a receiver using webhook-id for its own replay cache doesn't false-positive on a legitimate retry — :meth:`resend` re-signs and gets a new id. """ if not isinstance(secret, str) or not secret: raise ValueError("secret must be a non-empty string (whsec_<base64>)") if not isinstance(key_id, str) or not key_id: raise ValueError("key_id must be a non-empty string") decoded = _decode_sw_secret(secret) return cls._from_strategy( StandardWebhooksHmacStrategy(secret=decoded, key_id=key_id), key_id=key_id, client=client, timeout_seconds=timeout_seconds, allow_private_destinations=allow_private_destinations, allowed_destination_ports=allowed_destination_ports, transport_hooks=transport_hooks, ) def __repr__(self) -> str: # Explicit repr so no future debug helper or error traceback auto- # renders self.__dict__ and pulls the private key (or HMAC secret / # bearer token) into logs. return f"WebhookSender(auth={type(self._auth).__name__}, " f"key_id={self._key_id!r})" @property def signs_with_rfc9421(self) -> bool: """``True`` iff this sender uses the RFC 9421 webhook-signing profile. Boot-time validators read this to enforce the ``webhook_signing.supported=true`` capability invariant: capabilities advertise RFC 9421 → wired sender must produce ``Signature`` / ``Signature-Input`` headers. ``from_bearer_token``, ``from_adcp_legacy_hmac``, and ``from_standard_webhooks_secret`` senders return ``False``. """ return isinstance(self._auth, JwkSignerStrategy) async def aclose(self) -> None: """Close the internal httpx client if we own it.""" if self._owns_client and self._client is not None: await self._client.aclose() self._client = None async def __aenter__(self) -> WebhookSender: if not self._owns_client: await self._get_client() return self async def __aexit__(self, *args: Any) -> None: await self.aclose() async def _get_client(self) -> httpx.AsyncClient: if self._client is None: self._client = httpx.AsyncClient(timeout=self._timeout) return self._client async def send_mcp( self, *, url: str, task_id: str, status: GeneratedTaskStatus | str, task_type: TaskType | str, result: AdcpAsyncResponseData | dict[str, Any] | None = None, timestamp: datetime | None = None, operation_id: str | None = None, message: str | None = None, context_id: str | None = None, protocol: AdcpProtocol | str | None = None, idempotency_key: str | None = None, token: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed MCP-style task-status webhook. On retry, prefer :meth:`resend` over calling this again — ``resend`` replays the exact same bytes, whereas re-invoking ``send_mcp`` with the "same" args would produce a fresh ``timestamp`` and potentially a different serialized body, which the receiver would dedupe but with different observed payload data. :param token: Buyer-supplied token from ``push_notification_config.token`` echoed back on the payload's ``token`` field per spec (``schemas/cache/core/push_notification_config.json``: "Echoed back in webhook payload to validate request authenticity"). Cross-language wire-parity with the JS implementation. """ payload = create_mcp_webhook_payload( task_id=task_id, status=status, task_type=task_type, result=result, timestamp=timestamp, operation_id=operation_id, message=message, context_id=context_id, protocol=protocol, idempotency_key=idempotency_key, token=token, ) return await self.send_raw( url=url, idempotency_key=payload.idempotency_key, payload=to_wire_dict(payload), extra_headers=extra_headers, ) async def send_revocation_notification( self, *, url: str, rights_id: str, brand_id: str, reason: str, effective_at: str, idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed rights-revocation notification.""" key = idempotency_key or generate_webhook_idempotency_key() payload: dict[str, Any] = { "idempotency_key": key, "rights_id": rights_id, "brand_id": brand_id, "reason": reason, "effective_at": effective_at, } return await self.send_raw( url=url, idempotency_key=key, payload=payload, extra_headers=extra_headers ) async def send_artifact_webhook( self, *, url: str, media_buy_id: str, batch_id: str, timestamp: str, artifacts: list[dict[str, Any]], idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed content-standards artifact webhook.""" key = idempotency_key or generate_webhook_idempotency_key() payload: dict[str, Any] = { "idempotency_key": key, "media_buy_id": media_buy_id, "batch_id": batch_id, "timestamp": timestamp, "artifacts": artifacts, } return await self.send_raw( url=url, idempotency_key=key, payload=payload, extra_headers=extra_headers ) async def send_collection_list_changed( self, *, url: str, list_id: str, resolved_at: str, signature: str, idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed governance collection-list-changed webhook. ``signature`` is the payload-level signature field that predates 9421 webhook transport signing — it remains required by the schema. The 9421 signature this method adds protects the transport envelope. """ key = idempotency_key or generate_webhook_idempotency_key() payload: dict[str, Any] = { "idempotency_key": key, "event": "collection_list_changed", "list_id": list_id, "resolved_at": resolved_at, "signature": signature, } return await self.send_raw( url=url, idempotency_key=key, payload=payload, extra_headers=extra_headers ) async def send_property_list_changed( self, *, url: str, list_id: str, resolved_at: str, signature: str, idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed governance property-list-changed webhook.""" key = idempotency_key or generate_webhook_idempotency_key() payload: dict[str, Any] = { "idempotency_key": key, "event": "property_list_changed", "list_id": list_id, "resolved_at": resolved_at, "signature": signature, } return await self.send_raw( url=url, idempotency_key=key, payload=payload, extra_headers=extra_headers ) async def send_wholesale_feed( self, *, url: str, subscriber_id: str, account_id: str, notification_type: str, wholesale_feed_version: str, cache_scope: str, event: WholesaleFeedEvent | Mapping[str, Any], previous_wholesale_feed_version: str | None = None, fired_at: datetime | None = None, idempotency_key: str | None = None, subscription_event_types: Sequence[Any] | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed account-scoped wholesale feed notification. ``subscription_event_types`` is optional but recommended when the caller is sending to an ``accounts[].notification_configs[]`` entry: pass that entry's ``event_types`` to fail closed if the subscription did not request this notification type. """ if not isinstance(subscriber_id, str) or not subscriber_id: raise ValueError("subscriber_id must be a non-empty string") if not isinstance(account_id, str) or not account_id: raise ValueError("account_id must be a non-empty string") if not isinstance(wholesale_feed_version, str) or not wholesale_feed_version: raise ValueError("wholesale_feed_version must be a non-empty string") event_model = event if not isinstance(event_model, WholesaleFeedEvent): event_model = WholesaleFeedEvent.model_validate(event_model) notification_type_value = _enum_value(notification_type) event_type = _enum_value(event_model.event_type) entity_type = _enum_value(event_model.entity_type) if notification_type_value != event_type: raise ValueError( "notification_type must match event.event_type " f"(got {notification_type_value!r}, event has {event_type!r})" ) if subscription_event_types is not None: allowed_event_types = {_enum_value(item) for item in subscription_event_types} else: allowed_event_types = None if allowed_event_types is not None and notification_type_value not in allowed_event_types: raise ValueError( "notification_type is not present in the subscription's event_types; " "sellers must not silently widen account notification filters" ) expected_entity_type = _entity_type_for_wholesale_notification(notification_type_value) if entity_type != expected_entity_type: raise ValueError( "event.entity_type does not match notification_type " f"(got {entity_type!r}, expected {expected_entity_type!r})" ) cache_scope_value = _enum_value(cache_scope) applies_to = getattr(event_model.payload, "applies_to", None) applies_to_scope = _enum_value(getattr(applies_to, "scope", None)) if applies_to_scope != cache_scope_value: raise ValueError( "cache_scope must match event.payload.applies_to.scope " f"(got {cache_scope_value!r}, event has {applies_to_scope!r})" ) key = idempotency_key or generate_webhook_idempotency_key() timestamp = fired_at or datetime.now(timezone.utc) webhook = WholesaleFeedWebhook.model_validate( { "idempotency_key": key, "notification_id": event_model.event_id, "notification_type": notification_type_value, "fired_at": timestamp, "subscriber_id": subscriber_id, "account_id": account_id, "wholesale_feed_version": wholesale_feed_version, "previous_wholesale_feed_version": previous_wholesale_feed_version, "cache_scope": cache_scope_value, "event": event_model, } ) return await self.send_raw( url=url, idempotency_key=key, payload=webhook.model_dump(mode="json", exclude_none=True), extra_headers=extra_headers, ) async def send_wholesale_feed_to_subscription( self, *, subscription: NotificationConfig | Mapping[str, Any], account_id: str, notification_type: str, wholesale_feed_version: str, cache_scope: str, event: WholesaleFeedEvent | Mapping[str, Any], previous_wholesale_feed_version: str | None = None, fired_at: datetime | None = None, idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a wholesale feed notification to a ``NotificationConfig``. This convenience wrapper keeps ``url``, ``subscriber_id``, and ``event_types`` coupled to the same persisted subscription entry. """ config = ( subscription if isinstance(subscription, NotificationConfig) else NotificationConfig.model_validate(subscription) ) return await self.send_wholesale_feed( url=str(config.url), subscriber_id=config.subscriber_id, account_id=account_id, notification_type=notification_type, wholesale_feed_version=wholesale_feed_version, cache_scope=cache_scope, event=event, previous_wholesale_feed_version=previous_wholesale_feed_version, fired_at=fired_at, idempotency_key=idempotency_key, subscription_event_types=config.event_types, extra_headers=extra_headers, ) async def send_webhook_challenge( self, *, url: str, account_id: str, subscriber_id: str, challenge: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed durable-subscription proof-of-control challenge. The body matches the durable ``notification_configs[]`` challenge shape and intentionally does not inject ``idempotency_key``: ``{"type":"webhook.challenge","challenge":"...", ...}`` Pair this low-level sender method with :func:`adcp.webhooks.challenge_webhook_destination` when you also want URL validation and response echo checking in one call. """ payload = create_webhook_challenge_payload( account_id=account_id, subscriber_id=subscriber_id, challenge=challenge, ) challenge_value = str(payload["challenge"]) body = json.dumps(payload, separators=(",", ":")).encode("utf-8") return await self._send_bytes( url=url, body=body, idempotency_key=challenge_value, extra_headers=extra_headers, ) async def send_raw( self, *, url: str, idempotency_key: str, payload: dict[str, Any], extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """Low-level escape hatch: sign + POST an arbitrary payload. The ``idempotency_key`` kwarg is required and is injected into the payload before signing — the visible signature makes the contract impossible to forget, unlike a runtime dict check. If ``payload`` already carries an ``idempotency_key``, the kwarg wins so the two cannot disagree. """ if not isinstance(idempotency_key, str) or not idempotency_key: raise ValueError("idempotency_key must be a non-empty string") body_dict = {**payload, "idempotency_key": idempotency_key} # Byte-exact serialization — this is the ONLY representation that # gets signed AND posted. Do not allow an httpx `json=` path anywhere # in the stack because it would reserialize and break the digest. body = json.dumps(body_dict).encode("utf-8") if len(body) > _MAX_BODY_BYTES: raise ValueError( f"serialized webhook body is {len(body):,} bytes, over the " f"{_MAX_BODY_BYTES:,}-byte cap. Split into smaller webhooks " "or use batch-reporting endpoints." ) return await self._send_bytes( url=url, body=body, idempotency_key=idempotency_key, extra_headers=extra_headers, ) async def resend(self, result: WebhookDeliveryResult) -> WebhookDeliveryResult: """Replay an earlier delivery under a fresh signature. The bytes are identical (same ``idempotency_key``, same payload fields, same serialization) — only the Signature / Signature-Input / Content-Digest headers are regenerated. The receiver dedupes via ``idempotency_key``, so the replayed event is a spec-correct retry that won't cause double-processing. """ if not result.sent_body: raise ValueError( "cannot resend: result has no captured sent_body (likely constructed " "externally). Call a send_* method on this sender first." ) return await self._send_bytes( url=result.url, body=result.sent_body, idempotency_key=result.idempotency_key, extra_headers=result.sent_extra_headers or None, ) async def _send_bytes( self, *, url: str, body: bytes, idempotency_key: str, extra_headers: Mapping[str, str] | None, ) -> WebhookDeliveryResult: """Sign + POST a pre-serialized body through an SSRF-validated transport. When the sender owns its httpx client (the default — ``client=None`` was passed to ``__init__``), every delivery builds a per-request :class:`adcp.signing.ip_pinned_transport.AsyncIpPinnedTransport` that resolves the destination, runs the full SSRF range check (loopback / RFC 1918 / link-local / CGNAT / IPv6 ULA / multicast / cloud metadata), enforces the port allowlist, and pins the connection to the validated IP. This closes the DNS-rebinding TOCTOU between validate and connect. When the operator supplied their own client (``WebhookSender(client=...)`` — typically a vetted egress proxy with mTLS to a known buyer set, or an ASGI transport for testing), the sender trusts the operator's transport completely. Pin-and-bind is skipped; the operator's transport owns SSRF. On the owned-client path, SSRF validation runs **before** signing so a hostile URL is rejected without first generating an Ed25519/ES256 signature over the body. That signature would otherwise sit in process memory until the SSRF rejection — anything that snapshots locals on exception (faulthandler, custom logging) could capture it. Validate first, sign second. Transport hooks run before SSRF; the rewritten URL is what gets validated, signed, and POSTed. The signature covers the URL the request actually lands at, not the URL the caller typed — otherwise a receiver computing ``@target-uri`` from its observed Host header would see a different value and verification would fail. The hook output is bounded (hostname-only rewrite, scheme and port preserved), so this can't widen the destination space. """ effective_url = apply_hooks(url, self._transport_hooks) # Build the pinned transport up-front for the owned-client path. # SSRF + port validation runs against the *post-hook* URL — the # one we'll actually connect to. A hostile URL raises # SSRFValidationError here and the body never gets signed (no # signature material to leak via faulthandler / custom logging # on exception). transport: AsyncIpPinnedTransport | None = None if self._owns_client: transport = build_async_ip_pinned_transport( effective_url, allow_private=self._allow_private_destinations, allowed_ports=self._allowed_destination_ports, ) base_headers = {"Content-Type": "application/json"} auth_headers = self._auth.build_auth_headers(method="POST", url=effective_url, body=body) headers = merge_extra_headers( base={**base_headers, **auth_headers}, extra=extra_headers, reserved=self._auth.reserved_headers(), ) if transport is not None: # Owned-client path. ``trust_env=False`` prevents httpx from # routing the request through ``HTTPS_PROXY`` / ``HTTP_PROXY`` # env vars — every other pinned-transport callsite in the # codebase sets this for the same reason (default_jwks_fetcher, # async_default_jwks_fetcher, revocation_fetcher). Without it, # an attacker who controls process env can route the signed # webhook through their endpoint, defeating the IP pin entirely. async with httpx.AsyncClient( transport=transport, timeout=self._timeout, follow_redirects=False, trust_env=False, ) as client: response = await client.post(effective_url, content=body, headers=headers) else: # Operator-supplied client — they own the SSRF guarantees on # their transport (proxy allowlist, mTLS, etc.). Reachable as # None after aclose(); explicit raise survives ``python -O`` # which would strip an assert. if self._client is None: raise RuntimeError( "WebhookSender's operator-supplied client was already " "closed. Construct a new sender or pass a fresh client." ) response = await self._client.post(effective_url, content=body, headers=headers) return WebhookDeliveryResult( status_code=response.status_code, idempotency_key=idempotency_key, url=effective_url, response_headers=dict(response.headers), response_body=response.content, sent_body=body, sent_extra_headers=dict(extra_headers) if extra_headers else {}, )Outbound signed-webhook delivery client.
Owns one webhook-signing private key. Reuses a single :class:
httpx.AsyncClientacross requests for connection pooling — pass your own viaclient=if you want to share it with other SDK surfaces.Thread/task safety: safe to call concurrent
send_*from many asyncio tasks. The underlyinghttpx.AsyncClientmanages its own pool.Construct a sender wired to RFC 9421 JWK signing.
The HMAC and bearer modes are reached via :meth:
from_bearer_token, :meth:from_adcp_legacy_hmac, and :meth:from_standard_webhooks_secret— those classmethods bypass this initializer through :meth:_from_strategybecause their key material has different types (bytes/strrather thanPrivateKey).transport_hooksruns URL rewrites before SSRF validation — see :class:DockerLocalhostRewritefor the canonical use case. SSRF remains authoritative on the rewritten URL; hooks cannot punch through the range check.Static methods
def from_adcp_legacy_hmac(secret: bytes,
*,
key_id: str,
client: httpx.AsyncClient | None = None,
timeout_seconds: float = 10.0,
allow_private_destinations: bool = False,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = ()) ‑> WebhookSender-
Build a sender wired to AdCP-legacy HMAC-SHA256.
Wire format matches :func:
verify_webhook_hmac():X-AdCP-Signature: sha256=<hex>overf"{timestamp}.{body}", withX-AdCP-Timestampset fresh per delivery (resends produce a new signature over the same body).secretis the raw HMAC key — the AdCP-legacy scheme has no canonical encoding, so callers pass bytes directly.key_idis echoed inX-AdCP-Key-Idfor receiver-side multi-key rotation; it is not used in the signature itself.AdCP-legacy HMAC will be removed in AdCP 4.0 — operators SHOULD migrate to JWK signing (:meth:
from_jwk) ahead of that boundary. def from_bearer_token(token: str,
*,
client: httpx.AsyncClient | None = None,
timeout_seconds: float = 10.0,
allow_private_destinations: bool = False,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = ()) ‑> WebhookSender-
Build a sender that POSTs with
Authorization: Bearer <token>.For buyers who authenticate the sender at the gateway and don't verify body signatures. The sender's marshaling guarantees still apply (byte-exact JSON, idempotency_key in body); body signing is skipped.
A buyer treating bearer tokens as the sole authenticity signal SHOULD also enforce TLS/mTLS at the transport layer — a stolen token is a complete forgery. Prefer JWK signing (:meth:
from_jwk) for AdCP-conformant deliveries. def from_jwk(jwk: Mapping[str, Any],
*,
d_field: str = 'd',
client: httpx.AsyncClient | None = None,
timeout_seconds: float = 10.0,
allow_private_destinations: bool = False,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = ()) ‑> WebhookSender-
Construct from a JWK that includes the private scalar.
The JWK MUST have
adcp_use == "webhook-signing"— the sender doesn't validate this (you're signing with your own key; validation happens at the receiver), but a key whose adcp_use is wrong will be rejected by every conformant verifier.allow_private_destinationsandallowed_destination_portsforward to :meth:__init__— see that signature for semantics. def from_pem(pem_path: str | Path | bytes,
*,
key_id: str,
alg: str = 'ed25519',
passphrase: bytes | None = None,
client: httpx.AsyncClient | None = None,
timeout_seconds: float = 10.0,
allow_private_destinations: bool = False,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = ()) ‑> WebhookSender-
Load a private key from a PEM file and bind it as a webhook sender.
Companion to
adcp-keygen --purpose webhook-signing, which writes the PEM and prints the public JWK. The JWK is published at yourjwks_uri; the PEM holds the private key material.from_pemreads the PEM, constructs the rightPrivateKeytype foralg, and returns a sender ready to send.Args
pem_path- Path to the PKCS#8 PEM, or the PEM bytes directly.
key_id- JWK
kidclaim — must match the published JWK. alg- Signature algorithm.
ed25519(default) ores256. Also accepts the RFC 9421 formecdsa-p256-sha256. passphrase- Required if the PEM is encrypted
(
adcp-keygen --encrypt). client- Optional pre-built :class:
httpx.AsyncClientto share across the SDK; the sender owns its own client when omitted. timeout_seconds- Per-request timeout for the owned client.
allow_private_destinations- Forwarded to :meth:
__init__. allowed_destination_ports- Forwarded to :meth:
__init__.
Raises
ValueErroralgis not ed25519 / es256, or the PEM contains a key whose type doesn't matchalg.
def from_standard_webhooks_secret(secret: str,
*,
key_id: str,
client: httpx.AsyncClient | None = None,
timeout_seconds: float = 10.0,
allow_private_destinations: bool = False,
allowed_destination_ports: frozenset[int] | None = None,
transport_hooks: tuple[TransportHook, ...] = ()) ‑> WebhookSender-
Build a sender wired to standardwebhooks.com v1 (Svix/Resend interop).
secretis the canonicalwhsec_<base64>form distributed by buyers running Svix, Resend, or any other Standard Webhooks verifier. The constructor base64-decodes the prefix-stripped payload internally — passing the literalwhsec_…to :meth:from_adcp_legacy_hmacwould silently produce signatures Svix rejects, which is exactly the footgun this typed split prevents.Wire format per spec:
webhook-id/webhook-timestamp/webhook-signature: v1,<base64>overf"{webhook_id}.{webhook_timestamp}.{body}". Each delivery gets a freshwebhook-idso a receiver using webhook-id for its own replay cache doesn't false-positive on a legitimate retry — :meth:resendre-signs and gets a new id.
Instance variables
prop signs_with_rfc9421 : bool-
Expand source code
@property def signs_with_rfc9421(self) -> bool: """``True`` iff this sender uses the RFC 9421 webhook-signing profile. Boot-time validators read this to enforce the ``webhook_signing.supported=true`` capability invariant: capabilities advertise RFC 9421 → wired sender must produce ``Signature`` / ``Signature-Input`` headers. ``from_bearer_token``, ``from_adcp_legacy_hmac``, and ``from_standard_webhooks_secret`` senders return ``False``. """ return isinstance(self._auth, JwkSignerStrategy)Trueiff this sender uses the RFC 9421 webhook-signing profile.Boot-time validators read this to enforce the
webhook_signing.supported=truecapability invariant: capabilities advertise RFC 9421 → wired sender must produceSignature/Signature-Inputheaders.from_bearer_token,from_adcp_legacy_hmac, andfrom_standard_webhooks_secretsenders returnFalse.
Methods
async def aclose(self) ‑> None-
Expand source code
async def aclose(self) -> None: """Close the internal httpx client if we own it.""" if self._owns_client and self._client is not None: await self._client.aclose() self._client = NoneClose the internal httpx client if we own it.
async def resend(self, result: WebhookDeliveryResult) ‑> WebhookDeliveryResult-
Expand source code
async def resend(self, result: WebhookDeliveryResult) -> WebhookDeliveryResult: """Replay an earlier delivery under a fresh signature. The bytes are identical (same ``idempotency_key``, same payload fields, same serialization) — only the Signature / Signature-Input / Content-Digest headers are regenerated. The receiver dedupes via ``idempotency_key``, so the replayed event is a spec-correct retry that won't cause double-processing. """ if not result.sent_body: raise ValueError( "cannot resend: result has no captured sent_body (likely constructed " "externally). Call a send_* method on this sender first." ) return await self._send_bytes( url=result.url, body=result.sent_body, idempotency_key=result.idempotency_key, extra_headers=result.sent_extra_headers or None, )Replay an earlier delivery under a fresh signature.
The bytes are identical (same
idempotency_key, same payload fields, same serialization) — only the Signature / Signature-Input / Content-Digest headers are regenerated. The receiver dedupes viaidempotency_key, so the replayed event is a spec-correct retry that won't cause double-processing. async def send_artifact_webhook(self,
*,
url: str,
media_buy_id: str,
batch_id: str,
timestamp: str,
artifacts: list[dict[str, Any]],
idempotency_key: str | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_artifact_webhook( self, *, url: str, media_buy_id: str, batch_id: str, timestamp: str, artifacts: list[dict[str, Any]], idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed content-standards artifact webhook.""" key = idempotency_key or generate_webhook_idempotency_key() payload: dict[str, Any] = { "idempotency_key": key, "media_buy_id": media_buy_id, "batch_id": batch_id, "timestamp": timestamp, "artifacts": artifacts, } return await self.send_raw( url=url, idempotency_key=key, payload=payload, extra_headers=extra_headers )POST a signed content-standards artifact webhook.
async def send_collection_list_changed(self,
*,
url: str,
list_id: str,
resolved_at: str,
signature: str,
idempotency_key: str | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_collection_list_changed( self, *, url: str, list_id: str, resolved_at: str, signature: str, idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed governance collection-list-changed webhook. ``signature`` is the payload-level signature field that predates 9421 webhook transport signing — it remains required by the schema. The 9421 signature this method adds protects the transport envelope. """ key = idempotency_key or generate_webhook_idempotency_key() payload: dict[str, Any] = { "idempotency_key": key, "event": "collection_list_changed", "list_id": list_id, "resolved_at": resolved_at, "signature": signature, } return await self.send_raw( url=url, idempotency_key=key, payload=payload, extra_headers=extra_headers )POST a signed governance collection-list-changed webhook.
signatureis the payload-level signature field that predates 9421 webhook transport signing — it remains required by the schema. The 9421 signature this method adds protects the transport envelope. async def send_mcp(self,
*,
url: str,
task_id: str,
status: TaskStatus | str,
task_type: TaskType | str,
result: AdcpAsyncResponseData | dict[str, Any] | None = None,
timestamp: datetime | None = None,
operation_id: str | None = None,
message: str | None = None,
context_id: str | None = None,
protocol: AdcpProtocol | str | None = None,
idempotency_key: str | None = None,
token: str | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_mcp( self, *, url: str, task_id: str, status: GeneratedTaskStatus | str, task_type: TaskType | str, result: AdcpAsyncResponseData | dict[str, Any] | None = None, timestamp: datetime | None = None, operation_id: str | None = None, message: str | None = None, context_id: str | None = None, protocol: AdcpProtocol | str | None = None, idempotency_key: str | None = None, token: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed MCP-style task-status webhook. On retry, prefer :meth:`resend` over calling this again — ``resend`` replays the exact same bytes, whereas re-invoking ``send_mcp`` with the "same" args would produce a fresh ``timestamp`` and potentially a different serialized body, which the receiver would dedupe but with different observed payload data. :param token: Buyer-supplied token from ``push_notification_config.token`` echoed back on the payload's ``token`` field per spec (``schemas/cache/core/push_notification_config.json``: "Echoed back in webhook payload to validate request authenticity"). Cross-language wire-parity with the JS implementation. """ payload = create_mcp_webhook_payload( task_id=task_id, status=status, task_type=task_type, result=result, timestamp=timestamp, operation_id=operation_id, message=message, context_id=context_id, protocol=protocol, idempotency_key=idempotency_key, token=token, ) return await self.send_raw( url=url, idempotency_key=payload.idempotency_key, payload=to_wire_dict(payload), extra_headers=extra_headers, )POST a signed MCP-style task-status webhook.
On retry, prefer :meth:
resendover calling this again —resendreplays the exact same bytes, whereas re-invokingsend_mcpwith the "same" args would produce a freshtimestampand potentially a different serialized body, which the receiver would dedupe but with different observed payload data.:param token: Buyer-supplied token from
push_notification_config.tokenechoed back on the payload'stokenfield per spec (schemas/cache/core/push_notification_config.json: "Echoed back in webhook payload to validate request authenticity"). Cross-language wire-parity with the JS implementation. async def send_property_list_changed(self,
*,
url: str,
list_id: str,
resolved_at: str,
signature: str,
idempotency_key: str | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_property_list_changed( self, *, url: str, list_id: str, resolved_at: str, signature: str, idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed governance property-list-changed webhook.""" key = idempotency_key or generate_webhook_idempotency_key() payload: dict[str, Any] = { "idempotency_key": key, "event": "property_list_changed", "list_id": list_id, "resolved_at": resolved_at, "signature": signature, } return await self.send_raw( url=url, idempotency_key=key, payload=payload, extra_headers=extra_headers )POST a signed governance property-list-changed webhook.
async def send_raw(self,
*,
url: str,
idempotency_key: str,
payload: dict[str, Any],
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_raw( self, *, url: str, idempotency_key: str, payload: dict[str, Any], extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """Low-level escape hatch: sign + POST an arbitrary payload. The ``idempotency_key`` kwarg is required and is injected into the payload before signing — the visible signature makes the contract impossible to forget, unlike a runtime dict check. If ``payload`` already carries an ``idempotency_key``, the kwarg wins so the two cannot disagree. """ if not isinstance(idempotency_key, str) or not idempotency_key: raise ValueError("idempotency_key must be a non-empty string") body_dict = {**payload, "idempotency_key": idempotency_key} # Byte-exact serialization — this is the ONLY representation that # gets signed AND posted. Do not allow an httpx `json=` path anywhere # in the stack because it would reserialize and break the digest. body = json.dumps(body_dict).encode("utf-8") if len(body) > _MAX_BODY_BYTES: raise ValueError( f"serialized webhook body is {len(body):,} bytes, over the " f"{_MAX_BODY_BYTES:,}-byte cap. Split into smaller webhooks " "or use batch-reporting endpoints." ) return await self._send_bytes( url=url, body=body, idempotency_key=idempotency_key, extra_headers=extra_headers, )Low-level escape hatch: sign + POST an arbitrary payload.
The
idempotency_keykwarg is required and is injected into the payload before signing — the visible signature makes the contract impossible to forget, unlike a runtime dict check. Ifpayloadalready carries anidempotency_key, the kwarg wins so the two cannot disagree. async def send_revocation_notification(self,
*,
url: str,
rights_id: str,
brand_id: str,
reason: str,
effective_at: str,
idempotency_key: str | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_revocation_notification( self, *, url: str, rights_id: str, brand_id: str, reason: str, effective_at: str, idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed rights-revocation notification.""" key = idempotency_key or generate_webhook_idempotency_key() payload: dict[str, Any] = { "idempotency_key": key, "rights_id": rights_id, "brand_id": brand_id, "reason": reason, "effective_at": effective_at, } return await self.send_raw( url=url, idempotency_key=key, payload=payload, extra_headers=extra_headers )POST a signed rights-revocation notification.
async def send_webhook_challenge(self,
*,
url: str,
account_id: str,
subscriber_id: str,
challenge: str | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_webhook_challenge( self, *, url: str, account_id: str, subscriber_id: str, challenge: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed durable-subscription proof-of-control challenge. The body matches the durable ``notification_configs[]`` challenge shape and intentionally does not inject ``idempotency_key``: ``{"type":"webhook.challenge","challenge":"...", ...}`` Pair this low-level sender method with :func:`adcp.webhooks.challenge_webhook_destination` when you also want URL validation and response echo checking in one call. """ payload = create_webhook_challenge_payload( account_id=account_id, subscriber_id=subscriber_id, challenge=challenge, ) challenge_value = str(payload["challenge"]) body = json.dumps(payload, separators=(",", ":")).encode("utf-8") return await self._send_bytes( url=url, body=body, idempotency_key=challenge_value, extra_headers=extra_headers, )POST a signed durable-subscription proof-of-control challenge.
The body matches the durable
notification_configs[]challenge shape and intentionally does not injectidempotency_key:{"type":"webhook.challenge","challenge":"...", ...}Pair this low-level sender method with :func:
challenge_webhook_destination()when you also want URL validation and response echo checking in one call. async def send_wholesale_feed(self,
*,
url: str,
subscriber_id: str,
account_id: str,
notification_type: str,
wholesale_feed_version: str,
cache_scope: str,
event: WholesaleFeedEvent | Mapping[str, Any],
previous_wholesale_feed_version: str | None = None,
fired_at: datetime | None = None,
idempotency_key: str | None = None,
subscription_event_types: Sequence[Any] | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_wholesale_feed( self, *, url: str, subscriber_id: str, account_id: str, notification_type: str, wholesale_feed_version: str, cache_scope: str, event: WholesaleFeedEvent | Mapping[str, Any], previous_wholesale_feed_version: str | None = None, fired_at: datetime | None = None, idempotency_key: str | None = None, subscription_event_types: Sequence[Any] | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a signed account-scoped wholesale feed notification. ``subscription_event_types`` is optional but recommended when the caller is sending to an ``accounts[].notification_configs[]`` entry: pass that entry's ``event_types`` to fail closed if the subscription did not request this notification type. """ if not isinstance(subscriber_id, str) or not subscriber_id: raise ValueError("subscriber_id must be a non-empty string") if not isinstance(account_id, str) or not account_id: raise ValueError("account_id must be a non-empty string") if not isinstance(wholesale_feed_version, str) or not wholesale_feed_version: raise ValueError("wholesale_feed_version must be a non-empty string") event_model = event if not isinstance(event_model, WholesaleFeedEvent): event_model = WholesaleFeedEvent.model_validate(event_model) notification_type_value = _enum_value(notification_type) event_type = _enum_value(event_model.event_type) entity_type = _enum_value(event_model.entity_type) if notification_type_value != event_type: raise ValueError( "notification_type must match event.event_type " f"(got {notification_type_value!r}, event has {event_type!r})" ) if subscription_event_types is not None: allowed_event_types = {_enum_value(item) for item in subscription_event_types} else: allowed_event_types = None if allowed_event_types is not None and notification_type_value not in allowed_event_types: raise ValueError( "notification_type is not present in the subscription's event_types; " "sellers must not silently widen account notification filters" ) expected_entity_type = _entity_type_for_wholesale_notification(notification_type_value) if entity_type != expected_entity_type: raise ValueError( "event.entity_type does not match notification_type " f"(got {entity_type!r}, expected {expected_entity_type!r})" ) cache_scope_value = _enum_value(cache_scope) applies_to = getattr(event_model.payload, "applies_to", None) applies_to_scope = _enum_value(getattr(applies_to, "scope", None)) if applies_to_scope != cache_scope_value: raise ValueError( "cache_scope must match event.payload.applies_to.scope " f"(got {cache_scope_value!r}, event has {applies_to_scope!r})" ) key = idempotency_key or generate_webhook_idempotency_key() timestamp = fired_at or datetime.now(timezone.utc) webhook = WholesaleFeedWebhook.model_validate( { "idempotency_key": key, "notification_id": event_model.event_id, "notification_type": notification_type_value, "fired_at": timestamp, "subscriber_id": subscriber_id, "account_id": account_id, "wholesale_feed_version": wholesale_feed_version, "previous_wholesale_feed_version": previous_wholesale_feed_version, "cache_scope": cache_scope_value, "event": event_model, } ) return await self.send_raw( url=url, idempotency_key=key, payload=webhook.model_dump(mode="json", exclude_none=True), extra_headers=extra_headers, )POST a signed account-scoped wholesale feed notification.
subscription_event_typesis optional but recommended when the caller is sending to anaccounts[].notification_configs[]entry: pass that entry'sevent_typesto fail closed if the subscription did not request this notification type. async def send_wholesale_feed_to_subscription(self,
*,
subscription: NotificationConfig | Mapping[str, Any],
account_id: str,
notification_type: str,
wholesale_feed_version: str,
cache_scope: str,
event: WholesaleFeedEvent | Mapping[str, Any],
previous_wholesale_feed_version: str | None = None,
fired_at: datetime | None = None,
idempotency_key: str | None = None,
extra_headers: Mapping[str, str] | None = None) ‑> WebhookDeliveryResult-
Expand source code
async def send_wholesale_feed_to_subscription( self, *, subscription: NotificationConfig | Mapping[str, Any], account_id: str, notification_type: str, wholesale_feed_version: str, cache_scope: str, event: WholesaleFeedEvent | Mapping[str, Any], previous_wholesale_feed_version: str | None = None, fired_at: datetime | None = None, idempotency_key: str | None = None, extra_headers: Mapping[str, str] | None = None, ) -> WebhookDeliveryResult: """POST a wholesale feed notification to a ``NotificationConfig``. This convenience wrapper keeps ``url``, ``subscriber_id``, and ``event_types`` coupled to the same persisted subscription entry. """ config = ( subscription if isinstance(subscription, NotificationConfig) else NotificationConfig.model_validate(subscription) ) return await self.send_wholesale_feed( url=str(config.url), subscriber_id=config.subscriber_id, account_id=account_id, notification_type=notification_type, wholesale_feed_version=wholesale_feed_version, cache_scope=cache_scope, event=event, previous_wholesale_feed_version=previous_wholesale_feed_version, fired_at=fired_at, idempotency_key=idempotency_key, subscription_event_types=config.event_types, extra_headers=extra_headers, )POST a wholesale feed notification to a
NotificationConfig.This convenience wrapper keeps
url,subscriber_id, andevent_typescoupled to the same persisted subscription entry.
class WebhookVerifyOptions (*,
jwks_resolver: JwksResolver,
replay_store: ReplayStore | None = None,
revocation_checker: RevocationChecker | None = None,
revocation_list: RevocationList | None = None,
max_skew_seconds: int = 60,
max_window_seconds: int = 300,
label: str = 'sig1',
allowed_algs: frozenset[str] = frozenset({'ed25519', 'ecdsa-p256-sha256'}),
sender_url: str | None = None,
clock: Callable[[], float] = <built-in function time>)-
Expand source code
@dataclass(frozen=True, kw_only=True) class WebhookVerifyOptions: """Options for the webhook verifier. Subset of :class:`VerifyOptions` — several fields are pinned (tag, adcp_use, content-digest policy) because the webhook profile doesn't leave them as caller choices. Unlike the request verifier, there is no ``now`` field — the webhook verifier stamps time-of-check itself, so the same :class:`WebhookVerifyOptions` instance can live for the lifetime of your receiver without a factory closure around it. Override via ``clock=`` for deterministic tests. """ jwks_resolver: JwksResolver replay_store: ReplayStore | None = None revocation_checker: RevocationChecker | None = None revocation_list: RevocationList | None = None max_skew_seconds: int = DEFAULT_SKEW_SECONDS max_window_seconds: int = MAX_WINDOW_SECONDS label: str = SIG_LABEL_DEFAULT allowed_algs: frozenset[str] = ALLOWED_ALGS sender_url: str | None = None clock: Callable[[], float] = time.timeOptions for the webhook verifier.
Subset of :class:
VerifyOptions— several fields are pinned (tag, adcp_use, content-digest policy) because the webhook profile doesn't leave them as caller choices.Unlike the request verifier, there is no
nowfield — the webhook verifier stamps time-of-check itself, so the same :class:WebhookVerifyOptionsinstance can live for the lifetime of your receiver without a factory closure around it. Override viaclock=for deterministic tests.Instance variables
var allowed_algs : frozenset[str]var jwks_resolver : JwksResolvervar label : strvar max_skew_seconds : intvar max_window_seconds : intvar replay_store : ReplayStore | Nonevar revocation_checker : RevocationChecker | Nonevar revocation_list : RevocationList | Nonevar sender_url : str | None
Methods
def clock(...) ‑> Callable[[], float]-
time() -> floating point number
Return the current time in seconds since the Epoch. Fractions of a second may be present if the system clock provides them.
class WholesaleFeedEvent (root: RootModelRootType = PydanticUndefined, **data)-
Expand source code
class WholesaleFeedEvent( RootModel[ WholesaleFeedEvent1 | WholesaleFeedEvent2 | WholesaleFeedEvent3 | WholesaleFeedEvent4 | WholesaleFeedEvent5 | WholesaleFeedEvent6 | WholesaleFeedEvent7 | WholesaleFeedEvent8 | WholesaleFeedEvent9 ] ): root: Annotated[ WholesaleFeedEvent1 | WholesaleFeedEvent2 | WholesaleFeedEvent3 | WholesaleFeedEvent4 | WholesaleFeedEvent5 | WholesaleFeedEvent6 | WholesaleFeedEvent7 | WholesaleFeedEvent8 | WholesaleFeedEvent9, Field( description="A single change event emitted by an AdCP agent's wholesale product feed or wholesale signals feed and delivered inside wholesale-feed-webhook payloads. Events are denormalized — the payload carries the post-change state of a buyable product or signal so consumers can update local state without a follow-up get_products / get_signals call. This is distinct from buyer-provided feeds managed by sync_catalogs. The discriminator is `event_type`; each branch defines the payload shape. See specs/wholesale-feed-webhooks.md for webhook delivery and reconciliation semantics.", discriminator='event_type', title='Wholesale Feed Event', ), ] def __getattr__(self, name: str) -> Any: """Proxy attribute access to the wrapped type.""" if name.startswith('_'): raise AttributeError(name) return getattr(self.root, name)Usage Documentation
A Pydantic
BaseModelfor 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.selfis explicitly positional-only to allowselfas a field name.Ancestors
- pydantic.root_model.RootModel[Union[WholesaleFeedEvent1, WholesaleFeedEvent2, WholesaleFeedEvent3, WholesaleFeedEvent4, WholesaleFeedEvent5, WholesaleFeedEvent6, WholesaleFeedEvent7, WholesaleFeedEvent8, WholesaleFeedEvent9]]
- pydantic.root_model.RootModel
- pydantic.main.BaseModel
- typing.Generic
Class variables
var model_configvar root : adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent1 | adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent2 | adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent3 | adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent4 | adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent5 | adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent6 | adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent7 | adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent8 | adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEvent9
class WholesaleFeedWebhook (**data: Any)-
Expand source code
class WholesaleFeedWebhook(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) idempotency_key: Annotated[ str, Field( description='Sender-generated key stable across retries of the same webhook fire. Receivers MUST dedupe by this key, scoped to the authenticated sender identity.', max_length=255, min_length=16, pattern='^[A-Za-z0-9_.:-]{16,255}$', ), ] notification_id: Annotated[ UUID, Field( description='Stable identifier for this logical wholesale feed event. MUST equal event.event_id. Re-emissions of the same logical event reuse this value under a new idempotency_key.' ), ] notification_type: Annotated[ NotificationType, Field( description='Wholesale feed notification type discriminator. MUST match event.event_type.' ), ] fired_at: Annotated[ AwareDatetime, Field( description='ISO 8601 timestamp when the seller initiated this webhook fire. Distinct from event.created_at, which is when the seller observed or recorded the feed change.' ), ] subscriber_id: Annotated[ str, Field( description='Identifies which notification_configs[] entry is receiving this fire. Echoed from the registered subscriber_id.', max_length=64, min_length=1, pattern='^[A-Za-z0-9_.:-]{1,64}$', ), ] account_id: Annotated[ str, Field( description='Seller account identifier for the account scope that registered this webhook through sync_accounts.accounts[].notification_configs[]. Required because wholesale feed webhooks are account-anchored notifications.' ), ] wholesale_feed_version: Annotated[ str, Field( description='Opaque version token for the affected wholesale feed after this change. Receivers store it with their mirror and can pass it to get_products / get_signals as if_wholesale_feed_version to verify whether their local state is current.' ), ] previous_wholesale_feed_version: Annotated[ str | None, Field( description='Opaque version token for the affected wholesale feed before this change, when the seller can cheaply provide it. Receivers MAY use this to detect obvious gaps, but MUST NOT require it.' ), ] = None cache_scope: Annotated[ CacheScope, Field( description='Cache layer affected by this change. MUST equal event.payload.applies_to.scope. Mirrors the cache_scope returned by get_products / get_signals for the affected wholesale feed.' ), ] event: Annotated[ wholesale_feed_event.WholesaleFeedEvent, Field( description='The actual product, signal, or bulk-change event. Consumers MAY apply this payload to their local mirror. Before any binding action, or when ordering/gap checks fail, consumers MUST reconcile through get_products / get_signals.' ), ] ext: ext_1.ExtensionObject | None = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var account_id : strvar cache_scope : adcp.types.generated_poc.core.wholesale_feed_webhook.CacheScopevar event : adcp.types.generated_poc.core.wholesale_feed_event.WholesaleFeedEventvar ext : adcp.types.generated_poc.core.ext.ExtensionObject | Nonevar fired_at : pydantic.types.AwareDatetimevar idempotency_key : strvar model_configvar notification_id : uuid.UUIDvar notification_type : adcp.types.generated_poc.core.wholesale_feed_webhook.NotificationTypevar previous_wholesale_feed_version : str | Nonevar subscriber_id : strvar wholesale_feed_version : str
Inherited members