Module adcp.registry
Client for the AdCP registry API (brand, property, member, and policy lookups).
Functions
def build_community_mirror_adagents(config: dict[str, Any]) ‑> dict[str, typing.Any]-
Expand source code
def build_community_mirror_adagents(config: dict[str, Any]) -> dict[str, Any]: """Build a catalog-only community mirror adagents.json publish body. The publish endpoint is catalog-only: it has no ``authorized_agents`` field and the service forces ``authorized_agents: []`` server-side. This helper refuses caller-supplied authorization claims or generator-only flags and strips the ``platform`` key, which is a publish-time routing key rather than catalog content. Args: config: Catalog config with ``catalog_etag`` and a non-empty ``formats`` list. May include ``properties`` and ``superseded_by``. Returns: The catalog publish body (no ``authorized_agents`` key). Raises: RegistryError: If authorization claims, generator-only flags, or the ``catalog_etag``/``formats`` requirements are violated. """ if "authorized_agents" in config: raise RegistryError( "authorized_agents is not accepted for community mirror adagents catalogs" ) if "include_schema" in config or "include_timestamp" in config: raise RegistryError( "include_schema and include_timestamp are not accepted for " "community mirror adagents catalogs" ) catalog_etag = config.get("catalog_etag") if not isinstance(catalog_etag, str) or not catalog_etag.strip(): raise RegistryError("catalog_etag is required") formats = config.get("formats") if not isinstance(formats, list) or len(formats) == 0: raise RegistryError("formats must contain at least one catalog format") return {k: v for k, v in config.items() if k != "platform"}Build a catalog-only community mirror adagents.json publish body.
The publish endpoint is catalog-only: it has no
authorized_agentsfield and the service forcesauthorized_agents: []server-side. This helper refuses caller-supplied authorization claims or generator-only flags and strips theplatformkey, which is a publish-time routing key rather than catalog content.Args
config- Catalog config with
catalog_etagand a non-emptyformatslist. May includepropertiesandsuperseded_by.
Returns
The catalog publish body (no
authorized_agentskey).Raises
RegistryError- If authorization claims, generator-only flags, or the
catalog_etag/formatsrequirements are violated.
Classes
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.