Skip to main content
Modify an existing media buy using PATCH semantics. Supports campaign-level and package-level updates. Response Time: Instant to days (status: completed, working < 120s, or submitted for manual review) PATCH Semantics: Only specified fields are updated; omitted fields remain unchanged. Request Schema: /schemas/v2/media-buy/update-media-buy-request.json Response Schema: /schemas/v2/media-buy/update-media-buy-response.json

Quick Start

Create a media buy, then pause it:
import { testAgent } from '@adcp/client/testing';
import { CreateMediaBuyResponseSchema, UpdateMediaBuyResponseSchema } from '@adcp/client';

// First, create a media buy to update
const uniqueRef = `test_campaign_${Date.now()}`;

// Use dates in the future
const startDate = new Date();
startDate.setDate(startDate.getDate() + 7); // Start 1 week from now
const endDate = new Date();
endDate.setDate(endDate.getDate() + 37); // End 5 weeks from now

const createResult = await testAgent.createMediaBuy({
  buyer_ref: uniqueRef,
  brand_manifest: {
    name: 'Nike',
    url: 'https://nike.com'
  },
  packages: [{
    buyer_ref: 'display_pkg',
    product_id: 'prod_d979b543',
    pricing_option_id: 'cpm_usd_auction',
    format_ids: [{
      agent_url: 'https://creative.adcontextprotocol.org',
      id: 'display_300x250_image'
    }],
    budget: 10000,
    bid_price: 5.00
  }],
  start_time: startDate.toISOString(),
  end_time: endDate.toISOString()
});

if (!createResult.success) {
  throw new Error(`Create failed: ${createResult.error}`);
}

const created = CreateMediaBuyResponseSchema.parse(createResult.data);
if ('errors' in created && created.errors) {
  throw new Error(`Create failed: ${JSON.stringify(created.errors)}`);
}

console.log(`Created media buy ${created.media_buy_id}`);

// Now update it - pause the campaign
const updateResult = await testAgent.updateMediaBuy({
  buyer_ref: uniqueRef,
  paused: true
});

if (!updateResult.success) {
  throw new Error(`Update failed: ${updateResult.error}`);
}

const updated = UpdateMediaBuyResponseSchema.parse(updateResult.data);
if ('errors' in updated && updated.errors) {
  throw new Error(`Update failed: ${JSON.stringify(updated.errors)}`);
}

console.log(`Campaign ${updated.media_buy_id} paused`);

Request Parameters

ParameterTypeRequiredDescription
media_buy_idstringYes*Publisher’s media buy identifier to update
buyer_refstringYes*Your reference for the media buy to update
start_timestringNoUpdated campaign start time
end_timestringNoUpdated campaign end time
pausedbooleanNoPause/resume entire media buy (true = paused, false = active)
packagesPackageUpdate[]NoPackage-level updates (see below)
creativesCreativeAsset[]NoUpload and assign new creative assets inline
creative_assignmentsCreativeAssignment[]NoUpdate creative rotation weights and placement targeting
*Either media_buy_id OR buyer_ref is required (not both)

Package Update Object

ParameterTypeDescription
package_idstringPublisher’s package identifier to update
buyer_refstringYour reference for the package to update
pausedbooleanPause/resume specific package (true = paused, false = active)
budgetnumberUpdated budget allocation
pacingstringUpdated pacing strategy
bid_pricenumberUpdated bid price (auction products only)
targeting_overlayTargetingOverlayUpdated targeting restrictions
creative_idsstring[]Replace assigned creatives
*Either package_id OR buyer_ref is required for each package update

Response

Success Response

FieldDescription
media_buy_idMedia buy identifier
buyer_refYour reference identifier
implementation_dateISO 8601 timestamp when changes take effect (null if pending approval)
affected_packagesArray of full Package objects showing complete state after update

Error Response

FieldDescription
errorsArray of error objects explaining failure
Note: Responses use discriminated unions - you get either success fields OR errors, never both. Always check for errors before accessing success fields.

Common Scenarios

Update Package Budget

Increase budget for a specific package:
import { testAgent } from '@adcp/client/testing';
import { UpdateMediaBuyResponseSchema } from '@adcp/client';

const result = await testAgent.updateMediaBuy({
  media_buy_id: 'mb_12345',
  packages: [{
    buyer_ref: 'ctv_package',
    budget: 50000  // Increased from 30000
  }]
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = UpdateMediaBuyResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Update failed: ${JSON.stringify(validated.errors)}`);
}

const pkg = validated.affected_packages?.find(p => p.buyer_ref === 'ctv_package');
if (pkg) {
  console.log(`Package budget updated to ${pkg.budget}`);
}

Change Campaign Dates

Extend campaign end date:
import { testAgent } from '@adcp/client/testing';
import { UpdateMediaBuyResponseSchema } from '@adcp/client';

const result = await testAgent.updateMediaBuy({
  buyer_ref: 'summer_campaign_2025',
  end_time: '2025-09-30T23:59:59Z'
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = UpdateMediaBuyResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Update failed: ${JSON.stringify(validated.errors)}`);
}

console.log('Campaign end date extended');
console.log(`Effective: ${validated.implementation_date}`);

Update Targeting

Add or modify geographic restrictions:
import { testAgent } from '@adcp/client/testing';
import { UpdateMediaBuyResponseSchema } from '@adcp/client';

const result = await testAgent.updateMediaBuy({
  media_buy_id: 'mb_12345',
  packages: [{
    buyer_ref: 'ctv_package',
    targeting_overlay: {
      geo_country_any_of: ['US', 'CA'],
      geo_region_any_of: ['CA', 'NY', 'TX', 'ON', 'QC']
    }
  }]
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = UpdateMediaBuyResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Update failed: ${JSON.stringify(validated.errors)}`);
}

console.log('Targeting updated successfully');

Replace Creatives

Swap out creative assets for a package:
import { testAgent } from '@adcp/client/testing';
import { UpdateMediaBuyResponseSchema } from '@adcp/client';

const result = await testAgent.updateMediaBuy({
  media_buy_id: 'mb_12345',
  packages: [{
    buyer_ref: 'ctv_package',
    creative_ids: ['creative_video_v2', 'creative_display_v2']
  }]
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = UpdateMediaBuyResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Update failed: ${JSON.stringify(validated.errors)}`);
}

const pkg = validated.affected_packages?.find(p => p.buyer_ref === 'ctv_package');
const assignmentCount = pkg?.creative_assignments?.length || 0;
console.log(`Assigned ${assignmentCount} creatives`);

Multiple Package Updates

Update multiple packages in one call:
import { testAgent } from '@adcp/client/testing';
import { UpdateMediaBuyResponseSchema } from '@adcp/client';

const result = await testAgent.updateMediaBuy({
  media_buy_id: 'mb_12345',
  packages: [
    {
      buyer_ref: 'ctv_package',
      budget: 50000,
      pacing: 'front_loaded'
    },
    {
      buyer_ref: 'audio_package',
      budget: 30000,
      paused: true
    }
  ]
});

if (!result.success) {
  throw new Error(`Request failed: ${result.error}`);
}

const validated = UpdateMediaBuyResponseSchema.parse(result.data);

if ('errors' in validated && validated.errors) {
  throw new Error(`Update failed: ${JSON.stringify(validated.errors)}`);
}

console.log(`Updated ${validated.affected_packages?.length || 0} packages`);

What Can Be Updated

Campaign-Level Updates

Can update:
  • Start/end times (subject to seller approval)
  • Campaign status (active/paused)
Cannot update:
  • Media buy ID
  • Buyer reference
  • Brand manifest
  • Original package product IDs

Package-Level Updates

Can update:
  • Budget allocation
  • Pacing strategy
  • Bid prices (auction products)
  • Targeting overlays
  • Creative assignments
  • Package status (active/paused)
Cannot update:
  • Package ID
  • Product ID
  • Pricing option ID
  • Format IDs (creatives must match existing formats)

Error Handling

Common errors and resolutions:
Error CodeDescriptionResolution
MEDIA_BUY_NOT_FOUNDMedia buy doesn’t existVerify media_buy_id or buyer_ref
PACKAGE_NOT_FOUNDPackage doesn’t existVerify package_id or buyer_ref
UPDATE_NOT_ALLOWEDField cannot be changedSee “What Can Be Updated” above
BUDGET_INSUFFICIENTNew budget below minimumIncrease budget amount
POLICY_VIOLATIONUpdate violates content policyReview policy requirements
INVALID_STATEOperation not allowed in current stateCheck campaign status
Example error response:
{
  "errors": [{
    "code": "UPDATE_NOT_ALLOWED",
    "message": "Cannot change product_id for existing package",
    "field": "packages[0].product_id",
    "suggestion": "Create a new package with the desired product instead"
  }]
}

Update Approval

Some updates require seller approval and return pending status:
  • Significant budget increases (threshold varies by seller)
  • Date range changes affecting inventory availability
  • Targeting changes that alter campaign scope
  • Creative changes requiring policy review
When approval is needed, implementation_date will be null:
{
  "media_buy_id": "mb_12345",
  "buyer_ref": "summer_campaign_2025",
  "implementation_date": null,
  "affected_packages": []
}

PATCH Semantics

Only specified fields are updated - omitted fields remain unchanged:
{
  "buyer_ref": "summer_campaign_2025",
  "packages": [{
    "buyer_ref": "ctv_package",
    "budget": 50000
  }]
}
Array replacement: When updating arrays (like creative_ids), provide the complete new array:
{
  "packages": [{
    "buyer_ref": "ctv_package",
    "creative_ids": ["creative_video_v2", "creative_display_v2"]
  }]
}

Asynchronous Operations

Updates may be asynchronous, especially with seller approval.

Response Patterns

Synchronous (completed immediately):
{
  "media_buy_id": "mb_12345",
  "buyer_ref": "summer_campaign_2025",
  "implementation_date": "2025-06-15T10:00:00Z",
  "affected_packages": []
}
Asynchronous (processing):
{
  "status": "working",
  "message": "Processing update..."
}
Poll for completion or use webhooks/streaming. Manual Approval Required:
{
  "status": "submitted",
  "message": "Update requires seller approval (2-4 hours)"
}
Will take hours to days.

Protocol-Specific Handling

AdCP tasks work across multiple protocols (MCP, A2A, REST). Each protocol handles async operations differently:
  • Status checking: Polling, webhooks, or streaming
  • Updates: Protocol-specific mechanisms
  • Long-running tasks: Different timeout and notification patterns
See Task Management for protocol-specific async patterns and examples.

Best Practices

1. Use Precise Updates Update only what needs to change - don’t resend unchanged values. 2. Budget Increases Small incremental increases are more likely to be auto-approved than large jumps. 3. Pause Before Major Changes Pause campaigns before making significant targeting or creative changes to avoid delivery issues. 4. Test with Small Changes Test update workflows with minor changes before critical campaign modifications. 5. Monitor Status Always check response status and implementation_date for approval requirements. 6. Validate Package State Check affected_packages in response to confirm changes were applied correctly.

Usage Notes

  • Updates are atomic - either all changes apply or none do
  • Both media buys and packages can be referenced by buyer_ref or publisher IDs
  • Pending states (working, submitted) are normal, not errors
  • Orchestrators MUST handle pending states as part of normal workflow
  • implementation_date indicates when changes take effect (null if pending approval)

Next Steps

After updating a media buy:
  1. Verify Changes: Use get_media_buy_delivery to confirm updates
  2. Upload New Creatives: Use sync_creatives if creative_ids changed
  3. Monitor Performance: Track impact of changes on campaign metrics
  4. Optimize Further: Use provide_performance_feedback for ongoing optimization

Learn More