AdCP 3.1.13 · Media Buy
create_media_buy
create_media_buy is an operation in the Media Buy area of AdCP 3.1.13. create_media_buy task — create advertising campaigns in AdCP from discovered products.
create_media_buy request fields
The required column reflects the top-level required array only. 5 of the 22 request fields carry a top-level required flag: idempotency_key, account, brand, start_time, end_time. A top-level if, anyOf, oneOf or dependencies block can make an unmarked field mandatory in the mode being called, and a flat table cannot render
a rule that fires in one mode only. Those branches are visible in the request schema itself.
| Field | Type | Required | Description |
|---|---|---|---|
adcp_version | string | Release-precision AdCP version (VERSION.RELEASE, e.g. | |
adcp_major_version | integer | DEPRECATED in favor of adcp_version (release-precision string). | |
idempotency_key | string | required | Client-generated unique key for this request. |
plan_id | string | Campaign governance plan identifier. | |
account | account-ref | required | Account to bill for this media buy. |
proposal_id | string | ID of a committed proposal from get_products to execute. | |
total_budget | object | Total budget for the media buy when executing a proposal. | |
packages | package-request[] | Array of package configurations. | |
brand | brand-ref | required | Brand reference for this media buy. |
advertiser_industry | advertiser-industry | Industry classification for this specific campaign. One of: automotive, automotive.electric_vehicles, automotive.parts_accessories, automotive.luxury, beauty_cosmetics, beauty_cosmetics.skincare, beauty_cosmetics.fragrance, beauty_cosmetics.haircare, cannabis, cpg, cpg.personal_care, cpg.household, dating, education, education.higher_education, education.online_learning, education.k12, energy_utilities, energy_utilities.renewable, fashion_apparel, fashion_apparel.luxury, fashion_apparel.sportswear, finance, finance.banking. | |
invoice_recipient | business-entity | Override the account's default billing entity for this specific buy. | |
io_acceptance | object | Acceptance of an insertion order from a committed proposal. | |
po_number | string | Purchase order number for tracking | |
agency_estimate_number | string | Agency estimate or authorization number. | |
start_time | start-timing | required | Campaign start timing: 'asap' or ISO 8601 date-time |
end_time | string | required | Campaign end date/time in ISO 8601 format |
paused | boolean | Create the media buy in a paused delivery state. | |
push_notification_config | push-notification-config | Optional webhook configuration for async task status notifications. | |
reporting_webhook | reporting-webhook | Optional webhook configuration for automated reporting delivery | |
artifact_webhook | object | Optional webhook configuration for content artifact delivery. | |
context | context | Opaque correlation data that is echoed unchanged in responses. | |
ext | ext | Extension object for platform-specific, vendor-namespaced parameters. |
create_media_buy writes state. Its request schema is one of the 30 in the 64-entry registry flagged x-mutates-state: true. A repeated call is deduplicated only by idempotency_key, which this request requires. AdCP's release notes describe x-mutates-state as a non-normative tooling
hint that agents do not validate, so it is the registry's classification of the operation rather than
a contract, and it is the only machine-readable answer in the registry to whether an operation is
safe to retry.
create_media_buy response fields
The response schema for create_media_buy declares the envelope and keeps its result
in a top-level oneOf, anyOf or if, which a table of
flat rows cannot represent. The branch names are in the response schema. 29
of the 64 operations are shaped that way.
The first 13 rows are the shared protocol envelope from core/version-envelope.json and core/protocol-envelope.json. Not one of the 13 rows below belongs to create_media_buy itself. One field in the whole response is marked required: status.
| Field | Type | Required | Description |
|---|---|---|---|
adcp_version | string | Release-precision AdCP version (VERSION.RELEASE, e.g. | |
adcp_major_version | integer | DEPRECATED in favor of adcp_version (release-precision string). | |
context_id | string | Session/conversation identifier for tracking related operations across multiple task invocations. | |
context | context | Per-request opaque caller-supplied correlation object echoed unchanged in the response. | |
task_id | string | Unique identifier for tracking asynchronous operations. | |
status | task-status | required | Current task execution state. One of: submitted, working, input-required, completed, canceled, failed, rejected, auth-required, unknown. |
message | string | Human-readable summary of the task result. | |
timestamp | string | ISO 8601 timestamp when the response was generated. | |
replayed | boolean | Set to true when this response was returned from the idempotency cache rather than from a fresh execution. | |
adcp_error | error | Transport-envelope error signal for fatal task failures. | |
push_notification_config | push-notification-config | Push notification configuration for async task updates (A2A and REST protocols). | |
governance_context | string | Governance context token issued by the account's governance agent during check_governance. | |
payload | object | Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). |
The shared protocol envelope
62 of AdCP's 64 registered operations carry every one of the 13 envelope fields. report_plan_outcome and check_governance compose core/version-envelope.json only, so they carry the version pair and none of the task fields.
status resolves to enums/task-status.json, which admits nine values:
submitted, working, input-required, completed, canceled, failed, rejected, auth-required,
unknown. The envelope marks it required on every response, and a synchronous call emits completed rather than omitting it. When it returns submitted or working instead, task_id is the
polling handle and
push_notification_config is the webhook the agent echoes back to confirm the callback.
Webhooks are A2A and REST only: MCP sends progress notifications and defines no webhook.
payload appears in the response field table typed object. core/protocol-envelope.json describes it as a documentary construct and states that it is not a required wire field. Body fields
are siblings of the envelope fields: on MCP at the root of the tool response, on A2A in task.artifacts[0].parts[].DataPart, on REST at the root of the JSON body. An accessor that reads response.payload finds
nothing there.
Field lists are not published over the wire
AdCP's integration guide, docs/protocol/calling-an-agent.mdx, states that its MCP
servers no longer publish per-tool parameter schemas: a tools/list call returns an empty
properties object for every tool, and the guide directs clients not to infer shape from it. A client
author reads the schema registry instead of the tool list.
Example create_media_buy calls
The field tables above come from the 3.1.13 schema release and these blocks come from the documentation tree, which is versioned separately, so the two can disagree.
import { testAgent } from '@adcp/sdk/testing';
import { CreateMediaBuyResponseSchema } from '@adcp/sdk';
// Calculate dates dynamically - start tomorrow, end in 90 days
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(0, 0, 0, 0);
const endDate = new Date(tomorrow);
endDate.setDate(endDate.getDate() + 90);
const result = await testAgent.createMediaBuy({
brand: {
domain: 'acmecorp.com'
},
packages: [
{
product_id: 'prod_d979b543',
pricing_option_id: 'cpm_usd_auction',
format_option_refs: [{ scope: 'product', format_option_id: 'display_300x250_image' }],
budget: 2500,
bidding: { bid_amount: 5.00 }
},
{
product_id: 'prod_e8fd6012',
pricing_option_id: 'cpm_usd_auction',
format_option_refs: [{ scope: 'product', format_option_id: 'display_300x250_html' }],
budget: 2500,
bidding: { bid_amount: 4.50 }
}
],
start_time: tomorrow.toISOString(),
end_time: endDate.toISOString()
});
if (!result.success) {
throw new Error(`Request failed: ${result.error}`);
}
// Validate response against schema
const validated = CreateMediaBuyResponseSchema.parse(result.data);
// Check for errors (discriminated union response)
if ('errors' in validated && validated.errors) {
throw new Error(`Failed to create media buy: ${JSON.stringify(validated.errors)}`);
}
if ('media_buy_id' in validated) {
console.log(`Created media buy ${validated.media_buy_id}`);
console.log(`Upload creatives by: ${validated.creative_deadline}`);
console.log(`Packages created: ${validated.packages.length}`);
} import asyncio
import time
from datetime import datetime, timedelta, timezone
from adcp.testing import test_agent
async def create_campaign():
# Calculate dates dynamically - start tomorrow, end in 90 days
tomorrow = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
end_date = tomorrow + timedelta(days=90)
result = await test_agent.simple.create_media_buy(
brand={
'domain': 'acmecorp.com'
},
packages=[
{
'product_id': 'prod_d979b543',
'pricing_option_id': 'cpm_usd_auction',
'format_kind': 'image',
'params': {'width': 300, 'height': 250},
'budget': 2500,
'bidding': {'bid_amount': 5.00}
},
{
'product_id': 'prod_e8fd6012',
'pricing_option_id': 'cpm_usd_auction',
'format_kind': 'html5',
'params': {'width': 300, 'height': 250},
'budget': 2500,
'bidding': {'bid_amount': 4.50}
}
],
start_time=tomorrow.isoformat().replace('+00:00', 'Z'),
end_time=end_date.isoformat().replace('+00:00', 'Z')
)
# Check for errors (discriminated union response)
if hasattr(result, 'errors') and result.errors:
raise Exception(f"Failed to create media buy: {result.errors}")
print(f"Created media buy {result.media_buy_id}")
print(f"Upload creatives by: {result.creative_deadline}")
print(f"Packages created: {len(result.packages)}")
asyncio.run(create_campaign()) npx @adcp/sdk@latest \
https://test-agent.adcontextprotocol.org/sales/mcp \
create_media_buy \
'{"brand":{"domain":"acmecorp.com"},"packages":[{"product_id":"prod_d979b543","pricing_option_id":"cpm_usd_auction","format_option_refs":[{"scope":"product","format_option_id":"display_300x250_image"}],"budget":30000,"bidding":{"bid_amount":5.00}},{"product_id":"prod_e8fd6012","pricing_option_id":"cpm_usd_auction","format_option_refs":[{"scope":"product","format_option_id":"display_300x250_html"}],"budget":20000,"bidding":{"bid_amount":4.50}}],"start_time":"2025-06-01T00:00:00Z","end_time":"2025-08-31T23:59:59Z"}' \
--auth $ADCP_AUTH_TOKEN Frequently asked
- What does create_media_buy do?
- create_media_buy task — create advertising campaigns in AdCP from discovered products.
- Which fields does a create_media_buy request require?
- 5 of the 22 request fields carry a top-level required flag: idempotency_key, account, brand, start_time, end_time. AdCP also constrains requests with top-level if/then, anyOf, oneOf and dependencies blocks that no required array captures, so an unmarked field can still be mandatory in the mode being called.
- Which AdCP version do these create_media_buy field tables describe?
- AdCP 3.1.13, the current published release, last updated 2026-08-11. The documentation tree is versioned separately from the schema tree in the same repository, so a doc page and a schema file can describe the same field differently.
Other operations in Media Buy
-
get_media_buy_delivery -
get_media_buys -
get_products -
list_creative_formats -
log_event -
provide_performance_feedback -
sync_audiences -
sync_catalogs -
sync_event_sources -
update_media_buy