An AI-powered media buying system for DSPs, agencies, and advertisers to automate programmatic direct purchases using IAB OpenDirect standards.
The Ad Buyer System lets you:
- Automate media buying with AI agents that understand your campaign goals and budget
- Plan audiences using IAB Tech Lab Agentic Audience for real-time matching
- Search and discover inventory across publishers using natural language or structured queries
- Book deals programmatically via IAB OpenDirect 2.1 protocol
- Obtain Deal IDs for DSP activation - present buyer identity (agency, advertiser) to unlock tiered pricing, then get Deal IDs for activation in The Trade Desk, DV360, Amazon DSP, and other platforms
- Manage campaigns through CLI, REST API, or conversational chat interface
- Connect to any OpenDirect-compliant seller including the live IAB Tech Lab server
- Media agencies automating programmatic direct buying and leveraging identity-based tiered pricing
- Advertisers with in-house media teams seeking better rates through direct seller relationships
- Trading desks looking to scale deal operations and manage Deal IDs across multiple DSPs
- DSP operators who need to discover inventory, negotiate pricing, and obtain Deal IDs for programmatic activation
- Anyone wanting to experiment with agentic advertising workflows
┌─────────────────────────────────────────────────────────────────────────────┐
│ AD BUYER SYSTEM │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ╔═══════════════════════════════════════════════════════════════════════╗ │
│ ║ LEVEL 1: ORCHESTRATION (Claude Opus) ║ │
│ ║ ┌─────────────────────────────────────────────────────────────────┐ ║ │
│ ║ │ Portfolio Manager │ ║ │
│ ║ │ • Budget allocation • Strategic decisions │ ║ │
│ ║ │ • Channel optimization • Performance management │ ║ │
│ ║ └─────────────────────────────────────────────────────────────────┘ ║ │
│ ╚═══════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════╗ │
│ ║ LEVEL 2: CHANNEL SPECIALISTS (Claude Sonnet) ║ │
│ ║ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ║ │
│ ║ │Branding │ │ Mobile │ │ CTV │ │ Perfor- │ │ DSP │ ║ │
│ ║ │ Agent │ │App Agent│ │ Agent │ │ mance │ │ Agent │ ║ │
│ ║ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ ║ │
│ ╚═══════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════╗ │
│ ║ LEVEL 3: FUNCTIONAL AGENTS (Claude Sonnet) ║ │
│ ║ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ║ │
│ ║ │ Research │ │Execution │ │Reporting │ │ Audience │ ║ │
│ ║ │ Agent │ │ Agent │ │ Agent │ │ Planner │ ║ │
│ ║ └──────────┘ └──────────┘ └──────────┘ └──────────┘ ║ │
│ ╚═══════════════════════════════════════════════════════════════════════╝ │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ TOOLS │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Research: ProductSearch, AvailsCheck │ │
│ │ Execution: CreateOrder, CreateLine, BookLine, ReserveLine │ │
│ │ DSP: DiscoverInventory, GetPricing, RequestDeal │ │
│ │ Audience: AudienceDiscovery, AudienceMatching, CoverageEstimation │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────┤
│ INTERFACES: CLI │ REST API │ Chat │
├─────────────────────────────────────────────────────────────────────────────┤
│ STORAGE: SQLite (dev) │ Redis (prod) │
├─────────────────────────────────────────────────────────────────────────────┤
│ PROTOCOLS │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────┐ │
│ │ MCP (33 OpenDirect │ │ A2A (Natural Language│ │ AA (Agentic Audience │ │
│ │ Tools) │ │ Queries) │ │ Embeddings) │ │
│ └──────────────────────┘ └──────────────────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────┤
│ SERVER: IAB Tech Lab agentic-direct (OpenDirect 2.1) │
│ https://agentic-direct-server-hwgrypmndq-uk.a.run.app │
└─────────────────────────────────────────────────────────────────────────────┘
| Level | Agent | Model | Temperature | Role |
|---|---|---|---|---|
| 1 | Portfolio Manager | Claude Opus | 0.3 | Strategic orchestration, budget allocation, channel optimization |
| 2 | Branding Agent | Claude Sonnet | 0.5 | Brand awareness campaigns, premium placements |
| 2 | Mobile App Agent | Claude Sonnet | 0.5 | In-app advertising, rewarded video, interstitials |
| 2 | CTV Agent | Claude Sonnet | 0.5 | Connected TV, streaming, household targeting |
| 2 | Performance Agent | Claude Sonnet | 0.5 | Direct response, conversion optimization |
| 2 | DSP Agent | Claude Sonnet | 0.5 | Deal ID discovery, programmatic activation |
| 3 | Research Agent | Claude Sonnet | 0.2 | Inventory search, availability checking |
| 3 | Execution Agent | Claude Sonnet | 0.2 | Order creation, line booking |
| 3 | Reporting Agent | Claude Sonnet | 0.3 | Performance reporting, analytics |
| 3 | Audience Planner | Claude Sonnet | 0.3 | Agentic Audience based audience planning, coverage estimation |
The Ad Buyer System integrates with the IAB Tech Lab Agentic Audience (AA) for intelligent audience planning and matching.
Agentic Audience enables real-time audience matching between buyer and seller agents by exchanging embeddings (256-1024 dimension vectors) that encode:
- Identity Signals - Hashed user IDs, device graphs
- Contextual Signals - Page content, keywords, categories
- Reinforcement Signals - Conversion data, feedback loops
The Audience Planner Agent (Level 3) uses Agentic Audience to:
- Discover Capabilities - Query seller audience capabilities via Agentic Audience
- Match Requirements - Align campaign audience requirements to inventory
- Estimate Coverage - Calculate what percentage of inventory matches the audience
- Identify Gaps - Find audience requirements the seller cannot support
- Suggest Alternatives - Recommend similar audiences when exact match unavailable
| Tool | Purpose |
|---|---|
AudienceDiscoveryTool |
Discover available audience signals from sellers via Agentic Audience |
AudienceMatchingTool |
Match campaign audiences to inventory capabilities |
CoverageEstimationTool |
Estimate audience coverage for targeting combinations |
Campaign Brief → Audience Planner Agent → Agentic Audience Discovery → Coverage Estimates → Budget Allocation
│
├─ Discover seller capabilities via Agentic Audience
├─ Match audience requirements to inventory
├─ Estimate coverage per channel
└─ Identify gaps and alternatives
# Campaign brief with audience targeting
campaign_brief = {
"name": "Q1 Brand Campaign",
"budget": 100000,
"objectives": ["brand_awareness", "reach"],
"target_audience": {
"demographics": {"age": "25-54", "gender": "all"},
"interests": ["technology", "business"],
"behaviors": ["in-market-auto"],
},
"start_date": "2026-02-01",
"end_date": "2026-03-31",
}
# The flow will:
# 1. Analyze target_audience via Audience Planner Agent
# 2. Discover seller capabilities using Agentic Audience
# 3. Estimate coverage: {"branding": 75%, "ctv": 55%, ...}
# 4. Identify gaps: ["behavioral_targeting: coverage limited to 35-45%"]
# 5. Adjust budget allocation based on coverage| Property | Value |
|---|---|
| Content-Type | application/vnd.ucp.embedding+json; v=1 |
| Embedding Dimensions | 256-1024 |
| Similarity Metric | Cosine (default), Dot Product, L2 |
| Consent Required | Yes (IAB TCF v2) |
- Python 3.11 or higher
- An Anthropic API key for Claude
- Internet access (connects to IAB Tech Lab's hosted server by default)
Note: You do not need to install the IAB agentic-direct server locally. This system connects to their hosted instance by default.
git clone https://github.com/mobtownlabs/ad_buyer_system.git
cd ad_buyer_system
# Install the package
pip install -e .
# Or with dev tools (for testing)
pip install -e ".[dev]"cp .env.example .envEdit .env with your settings:
# Required: Your Anthropic API key
ANTHROPIC_API_KEY=sk-ant-api03-xxxxx
# OpenDirect server (default: live IAB Tech Lab server)
IAB_SERVER_URL=https://agentic-direct-server-hwgrypmndq-uk.a.run.app
# Storage
DATABASE_URL=sqlite:///./ad_buyer.db
# Optional: Redis for caching
# REDIS_URL=redis://localhost:6379/0# Test the CLI
ad-buyer --help
# Test connection to IAB server
python scripts/test_unified_client.pyimport asyncio
from ad_buyer.clients import UnifiedClient, Protocol
async def main():
async with UnifiedClient(protocol=Protocol.MCP) as client:
# List available products from seller
result = await client.list_products()
if result.success:
for product in result.data[:5]:
print(f"- {product.get('name')}: ${product.get('rate', 'N/A')} CPM")
asyncio.run(main())Output:
- Premium Display: $15.00 CPM
- CTV Streaming: $35.00 CPM
- Mobile App Interstitial: $12.00 CPM
import asyncio
from ad_buyer.clients import UnifiedClient, Protocol
async def main():
async with UnifiedClient(protocol=Protocol.A2A) as client:
# Ask in natural language
result = await client.send_natural_language(
"Find CTV inventory with household targeting under $30 CPM"
)
print(f"Response: {result.data}")
asyncio.run(main())Output:
Response: I found 3 CTV products matching your criteria:
1. Streaming Plus - $28 CPM, household + demographic targeting
2. Connected TV Basic - $25 CPM, household targeting
3. OTT Premium - $29 CPM, household + interest targeting
import asyncio
from ad_buyer.clients import UnifiedClient, Protocol
async def main():
async with UnifiedClient(protocol=Protocol.MCP) as client:
# Create an account
account = await client.create_account(name="Acme Corp")
account_id = account.data["id"]
print(f"Created account: {account_id}")
# Create an order
order = await client.create_order(
account_id=account_id,
name="Q1 2026 Brand Campaign",
budget=50000
)
order_id = order.data["id"]
print(f"Created order: {order_id}")
# Create a line item
line = await client.create_line(
order_id=order_id,
product_id="ctv-premium",
name="CTV Streaming Buy",
quantity=1_500_000 # 1.5M impressions
)
print(f"Created line: {line.data['id']}")
print(f"Total cost: ${line.data.get('cost', 'TBD')}")
asyncio.run(main())Output:
Created account: acc-12345
Created order: ord-67890
Created line: line-11111
Total cost: $45,000
import asyncio
from ad_buyer.clients import UnifiedClient, Protocol
async def main():
async with UnifiedClient(protocol=Protocol.MCP) as client:
# Connect to both protocols
await client.connect_both()
# Use MCP for fast, deterministic operations
products = await client.list_products(protocol=Protocol.MCP)
print(f"Found {len(products.data)} products")
# Use A2A for natural language recommendations
recs = await client.send_natural_language(
"Which of these products would work best for a brand awareness campaign targeting millennials?"
)
print(f"Recommendation: {recs.data}")
asyncio.run(main())The Ad Buyer System supports DSP (Demand Side Platform) workflows where you obtain Deal IDs from sellers for activation in traditional DSP platforms (The Trade Desk, DV360, Amazon DSP, etc.).
Sellers offer different pricing based on revealed buyer identity:
| Tier | Identity Required | Discount | Access |
|---|---|---|---|
| Public | None | 0% | Price ranges, limited catalog |
| Seat | DSP seat ID | 5% | Fixed prices |
| Agency | Agency ID | 10% | Premium inventory, negotiation |
| Advertiser | Agency + Advertiser ID | 15% | Volume discounts, full negotiation |
import asyncio
from ad_buyer.clients import UnifiedClient
from ad_buyer.models import BuyerIdentity
async def main():
# Create buyer identity for best pricing
identity = BuyerIdentity(
seat_id="ttd-seat-123",
agency_id="omnicom-456",
agency_name="OMD",
advertiser_id="coca-cola-789",
advertiser_name="Coca-Cola",
)
async with UnifiedClient(buyer_identity=identity) as client:
# Step 1: Discover inventory with tiered pricing
inventory = await client.discover_inventory(
channel="ctv",
max_cpm=25.0,
)
print(f"Found {len(inventory.data)} products")
# Step 2: Get pricing for a product
pricing = await client.get_pricing(
product_id="ctv-premium-001",
volume=5_000_000,
)
print(f"Tiered price: ${pricing.data['pricing']['tiered_price']} CPM")
# Step 3: Request a Deal ID
deal = await client.request_deal(
product_id="ctv-premium-001",
deal_type="PD", # Preferred Deal
impressions=5_000_000,
flight_start="2026-02-01",
flight_end="2026-02-28",
)
print(f"Deal ID: {deal.data['deal_id']}")
print(f"Final CPM: ${deal.data['price']}")
print(f"Activate in TTD: {deal.data['activation_instructions']['ttd']}")
asyncio.run(main())Output:
Found 12 products
Tiered price: $17.00 CPM
Deal ID: DEAL-A1B2C3D4
Final CPM: $17.00
Activate in TTD: The Trade Desk > Inventory > Private Marketplace > Add Deal ID: DEAL-A1B2C3D4
| Type | Code | Description |
|---|---|---|
| Programmatic Guaranteed | PG |
Fixed price, guaranteed impressions |
| Preferred Deal | PD |
Fixed price, non-guaranteed first-look |
| Private Auction | PA |
Floor price with auction dynamics |
The system provides specialized tools for DSP workflows:
DiscoverInventoryTool- Query sellers with identity contextGetPricingTool- Get tier-specific pricing with volume discountsRequestDealTool- Request Deal IDs with activation instructions
cd ad_buyer_system
pip install -e .
python examples/dsp_deal_discovery.pyThe system supports multiple protocols for communicating with OpenDirect servers:
| Protocol | Best For | Speed | Flexibility |
|---|---|---|---|
| MCP | Structured operations (create, update, list) | Fast | Deterministic, 33 tools |
| A2A | Natural language queries and discovery | Moderate | Flexible, conversational |
| Agentic Audience | Audience embedding exchange | Fast | Privacy-preserving matching |
- MCP: Booking deals, creating orders, listing inventory, automated workflows
- A2A: Discovery queries, recommendations, complex questions, conversational interfaces
- Agentic Audience: Audience planning, coverage estimation, capability discovery
The IAB server provides 33 OpenDirect tools:
| Category | Tools |
|---|---|
| Accounts | create_account, update_account, get_account, list_accounts |
| Orders | create_order, update_order, get_order, list_orders |
| Lines | create_line, update_line, get_line, list_lines |
| Products | get_product, list_products, search_products |
| Creatives | create_creative, update_creative, get_creative, list_creatives |
| Assignments | create_assignment, delete_assignment, get_assignment, list_assignments |
| Organizations | create_organization, update_organization, get_organization, list_organizations |
| Change Requests | create_changerequest, get_changerequest, list_changerequests |
| Messages | create_message, get_message, list_messages |
Start the API server:
python -m ad_buyer.interfaces.api.main
# Server runs at http://localhost:8000| Method | Endpoint | Description |
|---|---|---|
GET |
/products |
List available products |
GET |
/products/{id} |
Get product details |
POST |
/accounts |
Create an account |
POST |
/orders |
Create an order |
POST |
/lines |
Create a line item |
POST |
/search |
Search inventory |
POST |
/chat |
Natural language query |
POST |
/audience/plan |
Plan audience targeting (AA) |
curl -X POST http://localhost:8000/search \
-H "Content-Type: application/json" \
-d '{
"query": "CTV inventory under $30 CPM",
"channel": "ctv",
"max_cpm": 30
}'Response:
{
"results": [
{
"product_id": "ctv-001",
"name": "Streaming Plus",
"cpm": 28.00,
"targeting": ["household", "demographic"]
},
{
"product_id": "ctv-002",
"name": "Connected TV Basic",
"cpm": 25.00,
"targeting": ["household"]
}
],
"total": 2
}# View help
ad-buyer --help
# Initialize a campaign brief template
ad-buyer init
# Book a campaign from brief
ad-buyer book campaign_brief.json
# Search inventory
ad-buyer search --channel ctv --max-cpm 30
# Start interactive chat mode
ad-buyer chat
# List your orders
ad-buyer orders listAll settings can be configured via environment variables or .env file:
# ─────────────────────────────────────────────────────────────────
# REQUIRED
# ─────────────────────────────────────────────────────────────────
ANTHROPIC_API_KEY=sk-ant-api03-xxxxx
# ─────────────────────────────────────────────────────────────────
# OPENDIRECT SERVER
# ─────────────────────────────────────────────────────────────────
# Live IAB Tech Lab server (default, recommended)
IAB_SERVER_URL=https://agentic-direct-server-hwgrypmndq-uk.a.run.app
# Local server (for development/testing)
# OPENDIRECT_BASE_URL=http://localhost:3000
# ─────────────────────────────────────────────────────────────────
# STORAGE (choose one)
# ─────────────────────────────────────────────────────────────────
# SQLite (default, good for development)
DATABASE_URL=sqlite:///./ad_buyer.db
# Redis (recommended for production)
# REDIS_URL=redis://localhost:6379/0
# ─────────────────────────────────────────────────────────────────
# Agentic Audience
# ─────────────────────────────────────────────────────────────────
UCP_ENABLED=true
UCP_EMBEDDING_DIMENSION=512
UCP_SIMILARITY_THRESHOLD=0.5
UCP_CONSENT_REQUIRED=true
# ─────────────────────────────────────────────────────────────────
# LLM CONFIGURATION
# ─────────────────────────────────────────────────────────────────
DEFAULT_LLM_MODEL=anthropic/claude-sonnet-4-5-20250929
MANAGER_LLM_MODEL=anthropic/claude-opus-4-20250514
LLM_TEMPERATURE=0.3
LLM_MAX_TOKENS=4096
# ─────────────────────────────────────────────────────────────────
# ENVIRONMENT
# ─────────────────────────────────────────────────────────────────
ENVIRONMENT=development
LOG_LEVEL=INFOad_buyer_system/
├── examples/ # Runnable example scripts
│ ├── basic_mcp_usage.py
│ ├── natural_language_a2a.py
│ ├── protocol_switching.py
│ ├── dsp_deal_discovery.py
│ └── campaign_brief.json
├── src/ad_buyer/
│ ├── agents/ # CrewAI agents
│ │ ├── level1/ # Portfolio Manager
│ │ ├── level2/ # Channel Specialists (incl. DSP Agent)
│ │ └── level3/ # Functional Agents (incl. Audience Planner)
│ ├── clients/ # API clients
│ │ ├── unified_client.py # Unified MCP + A2A + DSP methods
│ │ ├── mcp_client.py # Direct MCP access
│ │ ├── a2a_client.py # Natural language
│ │ └── ucp_client.py # Agentic Audience embedding exchange
│ ├── crews/ # CrewAI crews
│ ├── flows/ # Workflow orchestration
│ │ ├── deal_booking_flow.py # Campaign booking flow (with audience planning)
│ │ └── dsp_deal_flow.py # DSP Deal ID discovery flow
│ ├── interfaces/ # User interfaces
│ │ ├── api/ # FastAPI REST server
│ │ └── cli/ # Typer CLI
│ ├── models/ # Pydantic models
│ │ ├── opendirect.py # OpenDirect entities
│ │ ├── flow_state.py # Flow state models
│ │ ├── buyer_identity.py # DSP buyer identity models
│ │ └── ucp.py # Agentic Audience models (embeddings, capabilities)
│ └── tools/ # CrewAI tools
│ ├── research/ # Research tools
│ ├── execution/ # Booking tools
│ ├── reporting/ # Reporting tools
│ ├── dsp/ # DSP tools (discover, pricing, deals)
│ └── audience/ # Audience tools (discovery, matching, coverage)
├── tests/
│ └── unit/ # Unit tests
└── scripts/ # Test and utility scripts
# Install dev dependencies
pip install -e ".[dev]"
# Run all tests
ANTHROPIC_API_KEY=test pytest tests/ -v
# Run with coverage
pytest tests/ --cov=ad_buyer --cov-report=html
# Test against live IAB server
python scripts/test_unified_client.py
python scripts/test_mcp_e2e.py
python scripts/test_a2a_e2e.py# Linting
ruff check src/
# Type checking
mypy src/
# Format code
ruff format src/Make sure your .env file exists and contains a valid API key:
echo "ANTHROPIC_API_KEY=sk-ant-api03-xxxxx" > .envVerify connectivity to the IAB Tech Lab server:
curl https://agentic-direct-server-hwgrypmndq-uk.a.run.app/healthEnsure you're using Protocol.MCP and the client is connected:
async with UnifiedClient(protocol=Protocol.MCP) as client:
# Client is automatically connected
result = await client.list_products()A2A involves an LLM on the server side, so responses take longer than MCP. Use MCP for performance-critical operations.
- seller-agent - Publisher/SSP-side agent system
- agentic-direct - IAB Tech Lab Agentic Direct (OpenDirect) MCP server example implementation
- Agentic Audience Specification - Agentic Audience documentation