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.

create_media_buy request — 22 fields, 5 required
FieldTypeRequiredDescription
adcp_versionstringRelease-precision AdCP version (VERSION.RELEASE, e.g.
adcp_major_versionintegerDEPRECATED in favor of adcp_version (release-precision string).
idempotency_keystringrequiredClient-generated unique key for this request.
plan_idstringCampaign governance plan identifier.
accountaccount-refrequiredAccount to bill for this media buy.
proposal_idstringID of a committed proposal from get_products to execute.
total_budgetobjectTotal budget for the media buy when executing a proposal.
packagespackage-request[]Array of package configurations.
brandbrand-refrequiredBrand reference for this media buy.
advertiser_industryadvertiser-industryIndustry 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_recipientbusiness-entityOverride the account's default billing entity for this specific buy.
io_acceptanceobjectAcceptance of an insertion order from a committed proposal.
po_numberstringPurchase order number for tracking
agency_estimate_numberstringAgency estimate or authorization number.
start_timestart-timingrequiredCampaign start timing: 'asap' or ISO 8601 date-time
end_timestringrequiredCampaign end date/time in ISO 8601 format
pausedbooleanCreate the media buy in a paused delivery state.
push_notification_configpush-notification-configOptional webhook configuration for async task status notifications.
reporting_webhookreporting-webhookOptional webhook configuration for automated reporting delivery
artifact_webhookobjectOptional webhook configuration for content artifact delivery.
contextcontextOpaque correlation data that is echoed unchanged in responses.
extextExtension 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.

create_media_buy response — 13 fields, 1 required
FieldTypeRequiredDescription
adcp_versionstringRelease-precision AdCP version (VERSION.RELEASE, e.g.
adcp_major_versionintegerDEPRECATED in favor of adcp_version (release-precision string).
context_idstringSession/conversation identifier for tracking related operations across multiple task invocations.
contextcontextPer-request opaque caller-supplied correlation object echoed unchanged in the response.
task_idstringUnique identifier for tracking asynchronous operations.
statustask-statusrequiredCurrent task execution state. One of: submitted, working, input-required, completed, canceled, failed, rejected, auth-required, unknown.
messagestringHuman-readable summary of the task result.
timestampstringISO 8601 timestamp when the response was generated.
replayedbooleanSet to true when this response was returned from the idempotency cache rather than from a fresh execution.
adcp_errorerrorTransport-envelope error signal for fatal task failures.
push_notification_configpush-notification-configPush notification configuration for async task updates (A2A and REST protocols).
governance_contextstringGovernance context token issued by the account's governance agent during check_governance.
payloadobjectConceptual 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.

Example (javascript) — javascript SDK or CLI call — source
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}`);
}
Example (python) — python SDK or CLI call — source
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())
Example (bash) — bash SDK or CLI call — source
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

Related