Before starting this tutorial, ensure you have:
test-token for API access).fastmcp package installed: uv pip install fastmcpAll examples in this tutorial use the mock adapter, which simulates a complete ad server without requiring any external services. The mock adapter supports all campaign lifecycle operations and responds with realistic data.
TechCorp, a technology company, wants to run a display advertising campaign targeting US tech enthusiasts. Here are the campaign details:
| Parameter | Value |
|---|---|
| Advertiser | TechCorp |
| Goal | Drive awareness for a new developer tool |
| Budget | $5,000 |
| Flight Dates | April 1 – April 30, 2026 |
| Target Audience | US-based tech enthusiasts, developers |
| Creative Format | Display banners (300x250, 728x90) |
| Pricing Model | CPM (cost per thousand impressions) |
We will walk through every step of the campaign lifecycle using the Sales Agent’s MCP tools, showing the exact requests and responses at each stage.
The first step is always to call get_adcp_capabilities to understand what the publisher supports. This tells you the available protocols, targeting options, creative formats, and business rules.
result = await client.call_tool("get_adcp_capabilities")
{
"tenant": {
"name": "Demo Publisher",
"portfolio_description": "A premium digital media network reaching 50M monthly unique visitors across technology, business, and lifestyle verticals.",
"adapter": "mock",
"protocols": ["mcp", "a2a", "rest"]
},
"capabilities": {
"supported_channels": ["display", "video", "audio"],
"supported_pricing_models": ["cpm", "cpc", "cpd", "flat_rate"],
"supported_targeting": {
"geographic": ["country", "state", "city", "dma"],
"demographic": ["age_range", "gender", "income"],
"contextual": ["category", "keyword", "topic"],
"behavioral": ["interest", "intent"],
"device": ["device_type", "os", "browser"]
},
"currency": "USD",
"min_budget": 500,
"max_budget": 1000000,
"workflow_required": true,
"creative_review_required": true
},
"tools": [
"get_adcp_capabilities",
"get_products",
"list_creative_formats",
"list_authorized_properties",
"create_media_buy",
"update_media_buy",
"get_media_buys",
"get_media_buy_delivery",
"sync_creatives",
"list_creatives",
"update_performance_index"
]
}
Next, call get_products with a natural-language brief describing the campaign goal. The AI ranking agent scores products by relevance to the brief and returns them in ranked order.
result = await client.call_tool("get_products", {
"brief": "display ads targeting US tech enthusiasts, developers, $5000 budget"
})
{
"products": [
{
"id": "prod-tech-display-001",
"name": "Tech Audience - Premium Display",
"description": "Reach technology professionals and enthusiasts across our network of developer blogs, tech news sites, and software review portals. High-intent audience with strong engagement metrics.",
"channels": ["display"],
"pricing_options": [
{
"id": "price-001",
"model": "cpm",
"rate": 12.50,
"currency": "USD",
"minimum_spend": 1000
},
{
"id": "price-002",
"model": "cpc",
"rate": 2.75,
"currency": "USD",
"minimum_spend": 500
}
],
"targeting": {
"contextual": ["technology", "software", "programming"],
"geographic": ["US"]
},
"estimated_reach": {
"daily_impressions": 150000,
"daily_uniques": 45000
},
"relevance_score": 0.95
},
{
"id": "prod-tech-video-001",
"name": "Tech Audience - Video Pre-Roll",
"description": "Video pre-roll ads served on tech tutorial and review video content. High completion rates with an engaged developer audience.",
"channels": ["video"],
"pricing_options": [
{
"id": "price-003",
"model": "cpm",
"rate": 25.00,
"currency": "USD",
"minimum_spend": 2500
}
],
"targeting": {
"contextual": ["technology", "tutorials"],
"geographic": ["US"]
},
"estimated_reach": {
"daily_impressions": 50000,
"daily_uniques": 30000
},
"relevance_score": 0.78
},
{
"id": "prod-ron-display-001",
"name": "Run of Network - Display",
"description": "Broad reach display advertising across all network properties. Cost-effective for awareness campaigns.",
"channels": ["display"],
"pricing_options": [
{
"id": "price-004",
"model": "cpm",
"rate": 4.00,
"currency": "USD",
"minimum_spend": 500
}
],
"targeting": {
"geographic": ["US"]
},
"estimated_reach": {
"daily_impressions": 500000,
"daily_uniques": 200000
},
"relevance_score": 0.52
}
],
"total": 3,
"ranking_model": "gemini-2.0-flash"
}
The AI ranked “Tech Audience - Premium Display” highest (0.95 relevance score). It offers CPM pricing at $12.50, targets technology and programming contexts in the US, and reaches 150K daily impressions. This is a strong match for our campaign.
With a $5,000 budget at $12.50 CPM, we can expect approximately 400,000 impressions over the campaign flight.
Before uploading creatives, call list_creative_formats to understand the exact specifications for display ads.
result = await client.call_tool("list_creative_formats")
{
"formats": [
{
"id": "fmt-display-300x250",
"name": "Medium Rectangle",
"channel": "display",
"mime_types": ["image/png", "image/jpeg", "image/gif"],
"dimensions": {
"width": 300,
"height": 250
},
"max_file_size_bytes": 150000,
"max_animation_duration_seconds": 30,
"requirements": "Static or animated. Max 3 animation loops. Must include a visible border."
},
{
"id": "fmt-display-728x90",
"name": "Leaderboard",
"channel": "display",
"mime_types": ["image/png", "image/jpeg", "image/gif"],
"dimensions": {
"width": 728,
"height": 90
},
"max_file_size_bytes": 150000,
"max_animation_duration_seconds": 30,
"requirements": "Static or animated. Max 3 animation loops. Must include a visible border."
},
{
"id": "fmt-display-160x600",
"name": "Wide Skyscraper",
"channel": "display",
"mime_types": ["image/png", "image/jpeg", "image/gif"],
"dimensions": {
"width": 160,
"height": 600
},
"max_file_size_bytes": 150000,
"max_animation_duration_seconds": 30,
"requirements": "Static or animated. Max 3 animation loops."
},
{
"id": "fmt-video-preroll",
"name": "Video Pre-Roll",
"channel": "video",
"mime_types": ["video/mp4"],
"dimensions": {
"width": 1920,
"height": 1080
},
"max_file_size_bytes": 50000000,
"max_duration_seconds": 30,
"requirements": "MP4 H.264 codec. Audio required. Max 30 seconds."
}
]
}
For our display campaign, we need:
Call list_authorized_properties to see the publisher’s domains and sites where ads will appear.
result = await client.call_tool("list_authorized_properties")
{
"properties": [
{
"id": "prop-001",
"name": "TechDaily.com",
"domain": "techdaily.com",
"channels": ["display", "video"],
"categories": ["technology", "software", "programming"],
"monthly_pageviews": 12000000,
"description": "Leading technology news and analysis"
},
{
"id": "prop-002",
"name": "DevHub Blog Network",
"domain": "devhub.io",
"channels": ["display"],
"categories": ["programming", "devops", "cloud"],
"monthly_pageviews": 8000000,
"description": "Developer-focused blog network covering modern software development"
},
{
"id": "prop-003",
"name": "GadgetReview",
"domain": "gadgetreview.com",
"channels": ["display", "video"],
"categories": ["technology", "consumer_electronics", "reviews"],
"monthly_pageviews": 5000000,
"description": "In-depth technology product reviews and comparisons"
}
],
"portfolio_description": "A premium digital media network reaching 50M monthly unique visitors across technology, business, and lifestyle verticals.",
"advertising_policies": {
"prohibited_categories": ["gambling", "tobacco", "weapons", "adult"],
"creative_requirements": "All creatives must include brand name. No misleading claims. Landing page must match advertised product.",
"approval_sla_hours": 24
}
}
The publisher operates three technology-focused properties with a combined 25M monthly pageviews. Our tech-focused campaign will appear across these sites. Note the advertising policies – our creatives must include the TechCorp brand name and the landing page must match our product.
Now we have all the information needed to create the media buy. We will use the “Tech Audience - Premium Display” product with CPM pricing.
result = await client.call_tool("create_media_buy", {
"name": "TechCorp Developer Tool Launch - Q2 2026",
"buyer_ref": "TC-2026-Q2-001",
"packages": [
{
"product_id": "prod-tech-display-001",
"pricing_option_id": "price-001",
"name": "Tech Display - Medium Rectangle",
"budget": 3000,
"start_date": "2026-04-01",
"end_date": "2026-04-30",
"targeting": {
"geographic": {"countries": ["US"]},
"contextual": {"categories": ["technology", "programming"]},
"device": {"device_types": ["desktop", "tablet"]}
},
"creative_format_ids": ["fmt-display-300x250"]
},
{
"product_id": "prod-tech-display-001",
"pricing_option_id": "price-001",
"name": "Tech Display - Leaderboard",
"budget": 2000,
"start_date": "2026-04-01",
"end_date": "2026-04-30",
"targeting": {
"geographic": {"countries": ["US"]},
"contextual": {"categories": ["technology", "software"]},
"device": {"device_types": ["desktop"]}
},
"creative_format_ids": ["fmt-display-728x90"]
}
],
"total_budget": 5000,
"currency": "USD"
})
{
"media_buy": {
"id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"name": "TechCorp Developer Tool Launch - Q2 2026",
"buyer_ref": "TC-2026-Q2-001",
"status": "pending_activation",
"total_budget": 5000.00,
"currency": "USD",
"packages": [
{
"id": "pkg-001",
"name": "Tech Display - Medium Rectangle",
"product_id": "prod-tech-display-001",
"budget": 3000.00,
"start_date": "2026-04-01",
"end_date": "2026-04-30",
"pricing_model": "cpm",
"rate": 12.50,
"estimated_impressions": 240000,
"creative_format_ids": ["fmt-display-300x250"],
"targeting": {
"geographic": {"countries": ["US"]},
"contextual": {"categories": ["technology", "programming"]},
"device": {"device_types": ["desktop", "tablet"]}
}
},
{
"id": "pkg-002",
"name": "Tech Display - Leaderboard",
"product_id": "prod-tech-display-001",
"budget": 2000.00,
"start_date": "2026-04-01",
"end_date": "2026-04-30",
"pricing_model": "cpm",
"rate": 12.50,
"estimated_impressions": 160000,
"creative_format_ids": ["fmt-display-728x90"],
"targeting": {
"geographic": {"countries": ["US"]},
"contextual": {"categories": ["technology", "software"]},
"device": {"device_types": ["desktop"]}
}
}
],
"created_at": "2026-03-09T14:30:00Z",
"workflow_tasks": [
{
"id": "task-review-001",
"type": "publisher_review",
"status": "pending",
"description": "Publisher review required before campaign activation"
}
]
}
}
The media buy was created successfully with ID mb-a1b2c3d4-5678-9012-3456-789012345678. Key observations:
pending_activation – the campaign needs publisher approval before it goes live.task-review-001) was created for publisher review. We will need to handle this in Step 7.Upload the display ad creatives for both packages using sync_creatives. The creatives are submitted with their format, click-through URL, and base64-encoded asset data.
result = await client.call_tool("sync_creatives", {
"media_buy_id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"creatives": [
{
"name": "TechCorp DevTool - 300x250",
"format_id": "fmt-display-300x250",
"click_url": "https://techcorp.example.com/devtool?utm_source=salesagent&utm_medium=display&utm_campaign=q2-launch",
"assets": [
{
"role": "primary",
"mime_type": "image/png",
"width": 300,
"height": 250,
"file_size_bytes": 87500,
"url": "https://cdn.techcorp.example.com/ads/devtool-300x250.png"
}
],
"alt_text": "TechCorp DevTool - Build faster with AI-powered development",
"provenance": {
"tool": "Adobe Creative Suite",
"created_by": "TechCorp Design Team"
}
},
{
"name": "TechCorp DevTool - 728x90",
"format_id": "fmt-display-728x90",
"click_url": "https://techcorp.example.com/devtool?utm_source=salesagent&utm_medium=display&utm_campaign=q2-launch",
"assets": [
{
"role": "primary",
"mime_type": "image/png",
"width": 728,
"height": 90,
"file_size_bytes": 62000,
"url": "https://cdn.techcorp.example.com/ads/devtool-728x90.png"
}
],
"alt_text": "TechCorp DevTool - Ship code 10x faster",
"provenance": {
"tool": "Adobe Creative Suite",
"created_by": "TechCorp Design Team"
}
}
]
})
{
"creatives": [
{
"id": "cr-001-300x250",
"name": "TechCorp DevTool - 300x250",
"format_id": "fmt-display-300x250",
"status": "pending_review",
"validation": {
"passed": true,
"checks": [
{"check": "dimensions", "status": "passed", "detail": "300x250 matches format spec"},
{"check": "file_size", "status": "passed", "detail": "87500 bytes < 150000 max"},
{"check": "mime_type", "status": "passed", "detail": "image/png is accepted"}
]
},
"created_at": "2026-03-09T14:35:00Z"
},
{
"id": "cr-002-728x90",
"name": "TechCorp DevTool - 728x90",
"format_id": "fmt-display-728x90",
"status": "pending_review",
"validation": {
"passed": true,
"checks": [
{"check": "dimensions", "status": "passed", "detail": "728x90 matches format spec"},
{"check": "file_size", "status": "passed", "detail": "62000 bytes < 150000 max"},
{"check": "mime_type", "status": "passed", "detail": "image/png is accepted"}
]
},
"created_at": "2026-03-09T14:35:00Z"
}
],
"summary": {
"total": 2,
"pending_review": 2,
"approved": 0,
"rejected": 0
}
}
Both creatives passed format validation (dimensions, file size, MIME type) and are now in pending_review status. The publisher’s creative review process (which may involve an AI review agent) will evaluate them for policy compliance. The creative lifecycle is:
processing --> pending_review --> approved
--> rejected
The media buy created a workflow task for publisher review, and the creatives are pending review. Workflow approvals are handled by the publisher through the Admin UI (or via Slack notifications if hitl_webhook_url is configured). The buying agent polls for status changes.
In a production environment, a publisher ad ops team member reviews the campaign in the Admin UI under Workflow Tasks and approves or rejects it. For testing, use the X-Auto-Advance: true header to bypass manual approval.
# Poll until the media buy is approved
import asyncio
while True:
result = await client.call_tool("get_media_buys", {
"media_buy_ids": [media_buy_id]
})
status = result["media_buys"][0]["status"]
if status in ("approved", "active", "delivering"):
print(f"Media buy approved! Status: {status}")
break
elif status in ("rejected", "canceled"):
print(f"Media buy {status}.")
break
print(f"Current status: {status} — waiting for publisher review...")
await asyncio.sleep(30)
The human-in-the-loop workflow is a core feature of the Sales Agent. It ensures that publishers maintain control over what runs on their properties. The workflow system:
X-Auto-Advance: true header.After approvals are complete, check the media buy status to confirm it has transitioned to active.
result = await client.call_tool("get_media_buys", {
"media_buy_ids": ["mb-a1b2c3d4-5678-9012-3456-789012345678"]
})
{
"media_buys": [
{
"id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"name": "TechCorp Developer Tool Launch - Q2 2026",
"buyer_ref": "TC-2026-Q2-001",
"status": "active",
"total_budget": 5000.00,
"currency": "USD",
"packages": [
{
"id": "pkg-001",
"name": "Tech Display - Medium Rectangle",
"status": "active",
"budget": 3000.00,
"start_date": "2026-04-01",
"end_date": "2026-04-30"
},
{
"id": "pkg-002",
"name": "Tech Display - Leaderboard",
"status": "active",
"budget": 2000.00,
"start_date": "2026-04-01",
"end_date": "2026-04-30"
}
],
"creatives": [
{"id": "cr-001-300x250", "status": "approved"},
{"id": "cr-002-728x90", "status": "approved"}
],
"created_at": "2026-03-09T14:30:00Z",
"activated_at": "2026-03-09T14:42:00Z"
}
]
}
The media buy has transitioned through the status lifecycle:
pending_activation --> active --> paused --> completed
Once the campaign is active and delivering, use get_media_buy_delivery to monitor performance. This returns impressions, spend, click-through rate, and pacing data.
result = await client.call_tool("get_media_buy_delivery", {
"media_buy_ids": ["mb-a1b2c3d4-5678-9012-3456-789012345678"]
})
{
"media_buy_id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"status": "active",
"delivery": {
"impressions": 125000,
"clicks": 1875,
"ctr": 0.015,
"spend": 1562.50,
"budget_remaining": 3437.50,
"budget_utilization": 0.3125
},
"pacing": {
"expected_spend_to_date": 1666.67,
"actual_spend_to_date": 1562.50,
"pacing_ratio": 0.9375,
"pacing_status": "slightly_behind",
"projected_end_spend": 4687.50,
"projected_end_date": "2026-04-30"
},
"by_package": [
{
"package_id": "pkg-001",
"name": "Tech Display - Medium Rectangle",
"impressions": 78000,
"clicks": 1248,
"ctr": 0.016,
"spend": 975.00,
"budget_remaining": 2025.00,
"pacing_ratio": 0.9500
},
{
"package_id": "pkg-002",
"name": "Tech Display - Leaderboard",
"impressions": 47000,
"clicks": 627,
"ctr": 0.0133,
"spend": 587.50,
"budget_remaining": 1412.50,
"pacing_ratio": 0.9167
}
],
"period": {
"start": "2026-04-01",
"end": "2026-04-30",
"days_elapsed": 10,
"days_remaining": 20
}
}
Key metrics to understand:
| Metric | Value | Interpretation |
|---|---|---|
| Impressions | 125,000 | 31.25% of estimated 400K total – on track for day 10 of 30 |
| CTR | 1.5% | Strong click-through rate for display ads |
| Spend | $1,562.50 | 31.25% of $5,000 budget consumed |
| Pacing Ratio | 0.9375 | Slightly behind pace (93.75% of expected) |
| Projected End Spend | $4,687.50 | On current trajectory, will underspend by $312.50 |
The by_package breakdown shows that the 300x250 Medium Rectangle is performing better (1.6% CTR) than the 728x90 Leaderboard (1.33% CTR). The 300x250 is also pacing better at 95% vs 91.67%.
Based on the delivery data, we can optimize the campaign. The 300x250 format is performing better, so let us shift $500 from the leaderboard package to the medium rectangle and extend the flight by one week.
result = await client.call_tool("update_media_buy", {
"media_buy_id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"packages": [
{
"package_id": "pkg-001",
"budget": 3500,
"end_date": "2026-05-07"
},
{
"package_id": "pkg-002",
"budget": 1500,
"end_date": "2026-05-07"
}
]
})
{
"media_buy": {
"id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"status": "active",
"total_budget": 5000.00,
"packages": [
{
"id": "pkg-001",
"name": "Tech Display - Medium Rectangle",
"budget": 3500.00,
"start_date": "2026-04-01",
"end_date": "2026-05-07",
"previous_budget": 3000.00
},
{
"id": "pkg-002",
"name": "Tech Display - Leaderboard",
"budget": 1500.00,
"start_date": "2026-04-01",
"end_date": "2026-05-07",
"previous_budget": 2000.00
}
],
"updated_at": "2026-04-11T10:15:00Z"
}
}
Use update_performance_index to feed back performance signals that help the Sales Agent optimize future product recommendations:
result = await client.call_tool("update_performance_index", {
"media_buy_id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"feedback": {
"overall_satisfaction": 4,
"audience_quality": 5,
"delivery_consistency": 3,
"notes": "Strong CTR on 300x250 format. Leaderboard underperforming on pacing. Audience targeting is excellent for developer demographic."
}
})
{
"performance_index": {
"media_buy_id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"overall_satisfaction": 4,
"audience_quality": 5,
"delivery_consistency": 3,
"updated_at": "2026-04-11T10:20:00Z"
}
}
When the flight ends (or the budget is exhausted), the campaign status transitions to completed. Check the final delivery report:
result = await client.call_tool("get_media_buys", {
"media_buy_ids": ["mb-a1b2c3d4-5678-9012-3456-789012345678"]
})
{
"media_buys": [
{
"id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"name": "TechCorp Developer Tool Launch - Q2 2026",
"status": "completed",
"total_budget": 5000.00,
"completed_at": "2026-05-07T23:59:59Z"
}
]
}
result = await client.call_tool("get_media_buy_delivery", {
"media_buy_ids": ["mb-a1b2c3d4-5678-9012-3456-789012345678"]
})
{
"media_buy_id": "mb-a1b2c3d4-5678-9012-3456-789012345678",
"status": "completed",
"delivery": {
"impressions": 398500,
"clicks": 6176,
"ctr": 0.0155,
"spend": 4981.25,
"budget_remaining": 18.75,
"budget_utilization": 0.9963
},
"pacing": {
"pacing_status": "completed",
"final_delivery_ratio": 0.9963
},
"by_package": [
{
"package_id": "pkg-001",
"name": "Tech Display - Medium Rectangle",
"impressions": 278000,
"clicks": 4587,
"ctr": 0.0165,
"spend": 3475.00,
"budget_utilization": 0.9929
},
{
"package_id": "pkg-002",
"name": "Tech Display - Leaderboard",
"impressions": 120500,
"clicks": 1589,
"ctr": 0.0132,
"spend": 1506.25,
"budget_utilization": 1.0042
}
],
"period": {
"start": "2026-04-01",
"end": "2026-05-07",
"total_days": 37
}
}
| Metric | Target | Actual | Result |
|---|---|---|---|
| Budget | $5,000 | $4,981.25 | 99.6% utilized |
| Impressions | 400,000 (est.) | 398,500 | 99.6% of estimate |
| CTR | – | 1.55% | Strong performance |
| Flight | Apr 1 – Apr 30 | Apr 1 – May 7 | Extended by 1 week (optimization) |
The campaign delivered 398,500 impressions at 99.6% budget utilization, with a strong 1.55% CTR. The mid-flight optimization (shifting budget to 300x250 and extending the flight) helped maximize delivery.
Here is the complete working script that executes all steps in the campaign lifecycle. This can be run directly against a local Sales Agent instance using the mock adapter.
"""
Complete Campaign Lifecycle Example
Runs a full advertising campaign through the Prebid Sales Agent.
Requirements:
- Sales Agent running locally (docker compose up -d)
- Mock adapter configured (default in development)
- Python 3.12+ with fastmcp installed (uv pip install fastmcp)
Usage:
uv run python campaign_lifecycle.py
"""
import asyncio
import json
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
SALES_AGENT_URL = "http://localhost:8000/mcp/"
AUTH_TOKEN = "test-token"
def print_step(step_num: int, title: str) -> None:
print(f"\n{'='*60}")
print(f"Step {step_num}: {title}")
print(f"{'='*60}")
def print_result(result: object) -> None:
if hasattr(result, "content"):
for block in result.content:
if hasattr(block, "text"):
parsed = json.loads(block.text)
print(json.dumps(parsed, indent=2))
else:
print(json.dumps(result, indent=2, default=str))
async def run_campaign_lifecycle() -> None:
transport = StreamableHttpTransport(
SALES_AGENT_URL,
headers={"x-adcp-auth": AUTH_TOKEN},
)
async with Client(transport=transport) as client:
# Step 1: Discover capabilities
print_step(1, "Discover Capabilities")
result = await client.call_tool("get_adcp_capabilities")
print_result(result)
# Step 2: Search for products
print_step(2, "Search for Products")
result = await client.call_tool("get_products", {
"brief": "display ads targeting US tech enthusiasts, "
"developers, $5000 budget"
})
print_result(result)
# Step 3: Check creative formats
print_step(3, "Check Creative Formats")
result = await client.call_tool("list_creative_formats")
print_result(result)
# Step 4: View publisher properties
print_step(4, "View Publisher Properties")
result = await client.call_tool("list_authorized_properties")
print_result(result)
# Step 5: Create the media buy
print_step(5, "Create Media Buy")
result = await client.call_tool("create_media_buy", {
"name": "TechCorp Developer Tool Launch - Q2 2026",
"buyer_ref": "TC-2026-Q2-001",
"packages": [
{
"product_id": "prod-tech-display-001",
"pricing_option_id": "price-001",
"name": "Tech Display - Medium Rectangle",
"budget": 3000,
"start_date": "2026-04-01",
"end_date": "2026-04-30",
"targeting": {
"geographic": {"countries": ["US"]},
"contextual": {
"categories": ["technology", "programming"]
},
"device": {"device_types": ["desktop", "tablet"]},
},
"creative_format_ids": ["fmt-display-300x250"],
},
{
"product_id": "prod-tech-display-001",
"pricing_option_id": "price-001",
"name": "Tech Display - Leaderboard",
"budget": 2000,
"start_date": "2026-04-01",
"end_date": "2026-04-30",
"targeting": {
"geographic": {"countries": ["US"]},
"contextual": {
"categories": ["technology", "software"]
},
"device": {"device_types": ["desktop"]},
},
"creative_format_ids": ["fmt-display-728x90"],
},
],
"total_budget": 5000,
"currency": "USD",
})
print_result(result)
# Extract media_buy_id from response for subsequent calls
media_buy_id = "mb-a1b2c3d4-5678-9012-3456-789012345678"
# Step 6: Upload creatives
print_step(6, "Upload Creatives")
result = await client.call_tool("sync_creatives", {
"media_buy_id": media_buy_id,
"creatives": [
{
"name": "TechCorp DevTool - 300x250",
"format_id": "fmt-display-300x250",
"click_url": "https://techcorp.example.com/devtool"
"?utm_source=salesagent"
"&utm_medium=display"
"&utm_campaign=q2-launch",
"assets": [
{
"role": "primary",
"mime_type": "image/png",
"width": 300,
"height": 250,
"file_size_bytes": 87500,
"url": "https://cdn.techcorp.example.com"
"/ads/devtool-300x250.png",
}
],
"alt_text": "TechCorp DevTool - Build faster "
"with AI-powered development",
},
{
"name": "TechCorp DevTool - 728x90",
"format_id": "fmt-display-728x90",
"click_url": "https://techcorp.example.com/devtool"
"?utm_source=salesagent"
"&utm_medium=display"
"&utm_campaign=q2-launch",
"assets": [
{
"role": "primary",
"mime_type": "image/png",
"width": 728,
"height": 90,
"file_size_bytes": 62000,
"url": "https://cdn.techcorp.example.com"
"/ads/devtool-728x90.png",
}
],
"alt_text": "TechCorp DevTool - Ship code 10x faster",
},
],
})
print_result(result)
# Step 7: Wait for approval workflows
# In production, publisher approves via Admin UI.
# In testing, use X-Auto-Advance: true header to skip.
print_step(7, "Wait for Approval Workflows")
print("\nPolling media buy status...")
import asyncio
while True:
result = await client.call_tool("get_media_buys", {
"media_buy_ids": [media_buy_id],
})
status = result["media_buys"][0]["status"]
if status in ("approved", "active", "delivering"):
print(f"Media buy approved! Status: {status}")
break
elif status in ("rejected", "canceled"):
print(f"Media buy {status}.")
break
print(f"Status: {status} — waiting...")
await asyncio.sleep(10)
# Step 8: Monitor campaign status
print_step(8, "Monitor Campaign Status")
result = await client.call_tool("get_media_buys", {
"media_buy_ids": [media_buy_id],
})
print_result(result)
# Step 9: Track delivery
print_step(9, "Track Delivery")
result = await client.call_tool("get_media_buy_delivery", {
"media_buy_ids": [media_buy_id],
})
print_result(result)
# Step 10: Optimize
print_step(10, "Optimize Campaign")
result = await client.call_tool("update_media_buy", {
"media_buy_id": media_buy_id,
"packages": [
{
"package_id": "pkg-001",
"budget": 3500,
"end_date": "2026-05-07",
},
{
"package_id": "pkg-002",
"budget": 1500,
"end_date": "2026-05-07",
},
],
})
print_result(result)
# Submit performance feedback
print("\nSubmitting performance feedback...")
result = await client.call_tool("update_performance_index", {
"media_buy_id": media_buy_id,
"feedback": {
"overall_satisfaction": 4,
"audience_quality": 5,
"delivery_consistency": 3,
"notes": "Strong CTR on 300x250. Leaderboard "
"underperforming on pacing.",
},
})
print_result(result)
# Step 11: Final delivery report
print_step(11, "Campaign Completion")
result = await client.call_tool("get_media_buy_delivery", {
"media_buy_ids": [media_buy_id],
})
print_result(result)
print("\n" + "=" * 60)
print("Campaign lifecycle complete!")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(run_campaign_lifecycle())
media_buy_id and task_id values from each response rather than using hardcoded IDs. The hardcoded IDs in this script correspond to the mock adapter's deterministic responses.