Skip to content

Commit 13e26c0

Browse files
authored
Merge pull request #26 from IABTechLab/docs/seller-discovery-reference
Add Seller Discovery API reference documentation
2 parents a69ed94 + a78ddb0 commit 13e26c0

2 files changed

Lines changed: 358 additions & 0 deletions

File tree

docs/api/seller-discovery.md

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
# Seller Discovery
2+
3+
The buyer agent discovers seller agents through the **IAB AAMP agent registry** — a centralized directory where agents register their identity, capabilities, and connection details. The `RegistryClient` handles discovery, agent card fetching, buyer registration, and trust verification, with built-in caching to minimize registry round-trips.
4+
5+
## Overview
6+
7+
```mermaid
8+
sequenceDiagram
9+
participant Buyer as Buyer Agent
10+
participant Client as RegistryClient
11+
participant Cache as SellerCache
12+
participant Registry as AAMP Registry
13+
participant Seller as Seller Agent
14+
15+
Buyer->>Client: discover_sellers(["ctv"])
16+
Client->>Cache: Check cache
17+
Cache-->>Client: Miss
18+
Client->>Registry: GET /agents?agent_type=seller&capabilities=ctv
19+
Registry-->>Client: Agent list
20+
Client->>Cache: Store results (TTL 5m)
21+
Client-->>Buyer: list[AgentCard]
22+
Buyer->>Client: fetch_agent_card(seller_url)
23+
Client->>Seller: GET /.well-known/agent.json
24+
Seller-->>Client: Agent card JSON
25+
Client-->>Buyer: AgentCard
26+
Buyer->>Client: verify_agent(seller_url)
27+
Client->>Registry: GET /verify?agent_url=...
28+
Registry-->>Client: Trust status
29+
Client-->>Buyer: AgentTrustInfo
30+
```
31+
32+
## RegistryClient
33+
34+
The `RegistryClient` manages all interactions with the AAMP agent registry. It wraps HTTP calls with caching, error handling, and Pydantic validation.
35+
36+
### Initialization
37+
38+
```python
39+
from ad_buyer.registry import RegistryClient
40+
41+
# Default — local development registry
42+
client = RegistryClient()
43+
44+
# Production registry with custom TTL and timeout
45+
client = RegistryClient(
46+
registry_url="https://registry.aamp.iab.com/agent-registry",
47+
cache_ttl_seconds=600, # 10 minute cache
48+
timeout=30.0, # 30 second HTTP timeout
49+
)
50+
```
51+
52+
| Parameter | Type | Default | Description |
53+
|-----------|------|---------|-------------|
54+
| `registry_url` | `str` | `http://localhost:8080/agent-registry` | Base URL of the AAMP agent registry |
55+
| `cache_ttl_seconds` | `float` | `300.0` | TTL for cached entries (seconds) |
56+
| `timeout` | `float` | `15.0` | HTTP request timeout (seconds) |
57+
58+
## Discovering Sellers
59+
60+
### Discover All Sellers
61+
62+
Query the registry for all registered seller agents:
63+
64+
```python
65+
sellers = await client.discover_sellers()
66+
67+
for seller in sellers:
68+
print(f"{seller.name} ({seller.agent_id})")
69+
print(f" URL: {seller.url}")
70+
print(f" Protocols: {seller.protocols}")
71+
print(f" Trust: {seller.trust_level.value}")
72+
```
73+
74+
### Filter by Capability
75+
76+
Pass a capabilities filter to narrow results to sellers that support specific inventory types:
77+
78+
```python
79+
# Only CTV sellers
80+
ctv_sellers = await client.discover_sellers(
81+
capabilities_filter=["ctv"],
82+
)
83+
84+
# Sellers with both display and video
85+
av_sellers = await client.discover_sellers(
86+
capabilities_filter=["display", "video"],
87+
)
88+
```
89+
90+
The filter is sent as a comma-separated query parameter. The registry returns sellers that match any of the requested capabilities.
91+
92+
### Fetch an Agent Card Directly
93+
94+
If you already know a seller's URL, fetch its agent card from the standard `.well-known/agent.json` endpoint without going through the registry:
95+
96+
```python
97+
card = await client.fetch_agent_card("https://seller.example.com")
98+
99+
if card:
100+
print(f"Agent: {card.name}")
101+
for cap in card.capabilities:
102+
print(f" Capability: {cap.name}{cap.description}")
103+
else:
104+
print("Agent card not available")
105+
```
106+
107+
This is useful when you have a known seller URL (e.g., from a previous deal or a direct partnership) and want to refresh its capabilities without a full registry query.
108+
109+
### Register the Buyer
110+
111+
Register the buyer agent in the registry so that sellers can discover and verify it:
112+
113+
```python
114+
from ad_buyer.registry import AgentCard, AgentCapability
115+
116+
buyer_card = AgentCard(
117+
agent_id="buyer-ttd-001",
118+
name="IAB Buyer Agent (TTD)",
119+
url="https://buyer.example.com",
120+
protocols=["opendirect-2.1", "openrtb-2.6"],
121+
capabilities=[
122+
AgentCapability(
123+
name="ctv",
124+
description="Connected TV buying",
125+
tags=["premium", "programmatic"],
126+
),
127+
AgentCapability(
128+
name="display",
129+
description="Display ad buying",
130+
tags=["programmatic"],
131+
),
132+
],
133+
)
134+
135+
success = await client.register_buyer(buyer_card)
136+
if success:
137+
print("Buyer registered in AAMP registry")
138+
```
139+
140+
### Verify Agent Trust
141+
142+
Before transacting with a seller, verify its trust status in the registry:
143+
144+
```python
145+
trust = await client.verify_agent("https://seller.example.com")
146+
147+
print(f"Registered: {trust.is_registered}")
148+
print(f"Trust level: {trust.trust_level.value}")
149+
print(f"Registry ID: {trust.registry_id}")
150+
151+
if trust.trust_level == TrustLevel.BLOCKED:
152+
print("WARNING: This agent is blocked — do not transact")
153+
```
154+
155+
## SellerCache
156+
157+
The `SellerCache` is an in-memory TTL cache that sits in front of all registry calls. It stores both individual `AgentCard` objects and lists of cards returned by discovery queries.
158+
159+
### Caching Strategy
160+
161+
| Behavior | Detail |
162+
|----------|--------|
163+
| **Storage** | In-memory dictionary (no external dependencies) |
164+
| **TTL** | Configurable via `cache_ttl_seconds` (default 5 minutes) |
165+
| **Expiry** | Lazy — expired entries are evicted on access, not on a timer |
166+
| **Scope** | Per-`RegistryClient` instance |
167+
| **Key format** | `discover:<sorted-capabilities>` for lists, `card:<url>` for individual cards |
168+
169+
### Cache Behavior by Method
170+
171+
| Method | Cache Key | Cache Type |
172+
|--------|-----------|------------|
173+
| `discover_sellers()` | `discover:` (empty filter) | List cache |
174+
| `discover_sellers(["ctv"])` | `discover:ctv` | List cache |
175+
| `discover_sellers(["ctv", "display"])` | `discover:ctv,display` | List cache |
176+
| `fetch_agent_card(url)` | `card:<url>` | Individual cache |
177+
| `verify_agent(url)` | Not cached ||
178+
179+
Discovery results are also cross-cached: each individual `AgentCard` from a discovery response is stored in the individual cache by `agent_id`, so subsequent lookups by ID avoid re-fetching.
180+
181+
### Manual Cache Management
182+
183+
```python
184+
# Access the cache directly
185+
cache = client._cache
186+
187+
# Invalidate a specific seller
188+
cache.invalidate("card:https://seller.example.com")
189+
190+
# Clear everything (e.g., after a known registry update)
191+
cache.clear()
192+
```
193+
194+
!!! tip "When to Invalidate"
195+
Invalidate a seller's cache entry after a failed transaction or when you receive an error suggesting the seller's capabilities have changed. Clear the entire cache when the registry announces a bulk update.
196+
197+
## Data Models
198+
199+
### AgentCard
200+
201+
Represents a discovered agent (seller or buyer) in the registry. Modeled after the [A2A agent card](https://google.github.io/A2A/) served at `.well-known/agent.json`.
202+
203+
```python
204+
from ad_buyer.registry import AgentCard
205+
206+
class AgentCard(BaseModel):
207+
agent_id: str # Unique identifier
208+
name: str # Human-readable name
209+
url: str # Base URL of the agent
210+
protocols: list[str] = [] # ["opendirect-2.1", "openrtb-2.6"]
211+
capabilities: list[AgentCapability] = [] # What the agent can do
212+
trust_level: TrustLevel = "unknown" # Registry trust status
213+
```
214+
215+
### AgentCapability
216+
217+
A declared capability of an agent — what inventory types or deal types it supports:
218+
219+
```python
220+
from ad_buyer.registry import AgentCapability
221+
222+
class AgentCapability(BaseModel):
223+
name: str # "ctv", "display", "video", "audio"
224+
description: str # Human-readable description
225+
tags: list[str] = [] # ["premium", "programmatic", "direct"]
226+
```
227+
228+
### TrustLevel
229+
230+
Trust status of an agent as determined by the AAMP registry:
231+
232+
| Level | Value | Meaning |
233+
|-------|-------|---------|
234+
| `UNKNOWN` | `"unknown"` | Not found in any registry |
235+
| `REGISTERED` | `"registered"` | Found in the AAMP registry |
236+
| `VERIFIED` | `"verified"` | Verified by the registry operator |
237+
| `PREFERRED` | `"preferred"` | Strategic partner with elevated trust |
238+
| `BLOCKED` | `"blocked"` | Explicitly blocked — do not transact |
239+
240+
### AgentTrustInfo
241+
242+
Result of a trust verification check against the registry:
243+
244+
```python
245+
from ad_buyer.registry import AgentTrustInfo
246+
247+
class AgentTrustInfo(BaseModel):
248+
agent_url: str # URL that was verified
249+
is_registered: bool # Whether agent is in registry
250+
trust_level: TrustLevel = "unknown" # Trust status
251+
registry_id: Optional[str] = None # Registry-assigned ID (if registered)
252+
```
253+
254+
## Multi-Seller Discovery Workflow
255+
256+
A typical buying workflow discovers multiple sellers, verifies trust, then shops across their media kits:
257+
258+
```mermaid
259+
graph LR
260+
A[Discover Sellers] --> B[Filter by Capability]
261+
B --> C[Verify Trust]
262+
C --> D[Fetch Agent Cards]
263+
D --> E[Browse Media Kits]
264+
E --> F[Compare & Book]
265+
```
266+
267+
### Portfolio Shopping Example
268+
269+
Discover CTV sellers, verify trust, and aggregate their media kits for comparison:
270+
271+
```python
272+
from ad_buyer.registry import RegistryClient, TrustLevel
273+
from ad_buyer.media_kit import MediaKitClient
274+
275+
registry = RegistryClient(
276+
registry_url="https://registry.aamp.iab.com/agent-registry",
277+
)
278+
279+
# Step 1: Discover CTV sellers
280+
sellers = await registry.discover_sellers(
281+
capabilities_filter=["ctv"],
282+
)
283+
print(f"Found {len(sellers)} CTV sellers")
284+
285+
# Step 2: Verify trust — only transact with registered+ sellers
286+
trusted_sellers = []
287+
for seller in sellers:
288+
trust = await registry.verify_agent(seller.url)
289+
if trust.trust_level in (
290+
TrustLevel.REGISTERED,
291+
TrustLevel.VERIFIED,
292+
TrustLevel.PREFERRED,
293+
):
294+
trusted_sellers.append(seller)
295+
else:
296+
print(f"Skipping {seller.name} (trust: {trust.trust_level.value})")
297+
298+
print(f"{len(trusted_sellers)} trusted sellers")
299+
300+
# Step 3: Browse media kits across trusted sellers
301+
async with MediaKitClient(api_key="my-key") as media_client:
302+
seller_urls = [s.url for s in trusted_sellers]
303+
all_packages = await media_client.aggregate_across_sellers(seller_urls)
304+
305+
print(f"Found {len(all_packages)} packages across {len(trusted_sellers)} sellers")
306+
```
307+
308+
### Capability-Based Routing
309+
310+
Route campaign requirements to the right sellers based on declared capabilities:
311+
312+
```python
313+
# Campaign needs CTV + display
314+
campaign_needs = ["ctv", "display"]
315+
316+
# Find sellers that support these capabilities
317+
matching_sellers = await registry.discover_sellers(
318+
capabilities_filter=campaign_needs,
319+
)
320+
321+
# Group by capability for mixed-media campaigns
322+
ctv_sellers = [
323+
s for s in matching_sellers
324+
if any(c.name == "ctv" for c in s.capabilities)
325+
]
326+
display_sellers = [
327+
s for s in matching_sellers
328+
if any(c.name == "display" for c in s.capabilities)
329+
]
330+
```
331+
332+
## Error Handling
333+
334+
The `RegistryClient` handles errors gracefully — failed calls return empty results or `None` rather than raising exceptions:
335+
336+
| Scenario | Method | Return Value |
337+
|----------|--------|-------------|
338+
| Registry unreachable | `discover_sellers()` | `[]` (empty list) |
339+
| Registry returns non-200 | `discover_sellers()` | `[]` (empty list) |
340+
| Agent card not found | `fetch_agent_card()` | `None` |
341+
| Agent card endpoint down | `fetch_agent_card()` | `None` |
342+
| Verification fails | `verify_agent()` | `AgentTrustInfo(is_registered=False, trust_level=UNKNOWN)` |
343+
| Registration fails | `register_buyer()` | `False` |
344+
| Invalid JSON / validation error | Any | Logged warning, graceful fallback |
345+
346+
All errors are logged at `WARNING` or `DEBUG` level. The client never raises `httpx.HTTPError` or `pydantic.ValidationError` to the caller.
347+
348+
!!! warning "Silent Failures"
349+
Because the client returns empty results on failure, always check the length of discovery results. An empty list could mean "no sellers match" or "the registry is down." Monitor logs for `WARNING`-level messages to distinguish the two.
350+
351+
## Related
352+
353+
- [Seller Agent Discovery](https://iabtechlab.github.io/seller-agent/api/agent-discovery/) — How seller agents register and expose their agent cards
354+
- [Media Kit Discovery](media-kit.md) — Browse seller inventory after discovering sellers
355+
- [Authentication](authentication.md) — API key setup for accessing seller endpoints
356+
- [A2A Client](a2a-client.md) — Agent-to-agent communication protocol
357+
- [Seller Agent Integration](../integration/seller-agent.md) — Full integration guide

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ nav:
6464
- Media Kit: api/media-kit.md
6565
- Deals: api/deals.md
6666
- Bookings: api/bookings.md
67+
- Seller Discovery: api/seller-discovery.md
6768
- Products: api/products.md
6869
- Architecture:
6970
- Overview: architecture/overview.md

0 commit comments

Comments
 (0)