These workflows require an enrolled Buyer account or the integrated
own-supply sandbox capability on a Media Company. Call
get_status first.
An integrated Media Company stays in the same account and never uses
switch_account for campaign work.Buyer workflow at a glance
- Confirm buyer operator readiness.
- Create or select an advertiser.
- Create a campaign with its brief, flight, and budget.
- Request proposals from ready sellers.
- Accept a quoted proposal or stage returned products.
- Inspect the staged media buys and resolve any draft issues.
- Launch the campaign explicitly.
- Query bounded campaign delivery.
0. Confirm buyer operator readiness
Callget_status before creating buying work. When
operatorIdentity.usableForBuying is false, new discovery and buying calls are
blocked with BUYER_SETUP_REQUIRED. An account administrator must call
save_buyer_operator with the buyer’s real non-platform domain and choose
whether this account represents the whole_operator or a specific_unit with
a stable operatorUnitId.
scopeStatus is unclassified, and locked is
false, reuse that domain and choose its scope before new AdCP 3.2 provisioning.
Existing buying remains available during that scope migration. If the identity
is locked, follow the support action from get_status instead.
Confirming the commercial operator does not add users, change membership, or
change the login organization. See
Buyer setup and go-live for scope selection and
identity-locking rules.
1. Create an advertiser
Creating requiresname and brand. primaryCurrency defaults to 'USD'
when omitted; pass any ISO 4217 code to override, or update it later while the
advertiser is unlocked. When name or brand is missing the tool returns a
neutral needs_input result naming the missing fields. A needs_input result
is a question, not a failure; nothing is saved until the tool is called again
with the answer.
The currency stays editable until the first campaign or seller binding locks
it. To change it, send advertiserId with the new primaryCurrency as an
ordinary update; you never need to archive and recreate the advertiser.
sandbox is immutable after creation.
advertiserId. To update, send it with only the fields to
change. Do not send sandbox on an update.
2. Create a campaign
Creation requiresadvertiserId, name, flight, budget, and an
idempotencyKey. Write the brief from the buyer’s stated goal, audience, and
what is being promoted.
campaignId and revision.
Target Nielsen DMA areas
You can target Nielsen Designated Market Areas (DMAs) while saving a campaign or staging a media buy. Onsave_campaign, use targeting.geoMetros. On
save_media_buy, use products[].targetingOverlay.geo_metros to include DMAs
or geo_metros_exclude to exclude them. Each field contains at least one
metro entry, and each entry contains at least one value.
Send a numeric DMA code or a familiar DMA name such as LA DMA. The service
resolves accepted names to numeric codes before it saves the campaign or media
buy.
3. Request proposals from eligible sellers
get_status reports how many destinations are currently ready and includes a
bounded readyDestinations sample for explanation. A fresh
request_proposals call rechecks the complete marketplace server-side and
contacts every active seller whose buyer-specific readiness has canBuy: true.
The caller does not select or cap the seller cohort.
Before a seller-visible brief is recorded or sent, each seller must also pass
the campaign’s structured market and channel constraints. Campaign seller IDs
remain planning scope for ordinary marketplace rounds; use expectedSellerId
only as the documented authority guard, not as a fan-out selector.
For an active sponsored buyer using a sandbox advertiser, the server instead
confines the cohort to that buyer’s sponsoring storefront and applies the
sandbox transaction path. The sponsoring storefront does not need to be open
to the public marketplace for this no-spend workflow.
expectedSellerId is a Storefront ID, not an internal customer ID. It is an
optional fail-closed precondition for automation that must remain confined to
one seller. A fresh round fails if the buyer’s current
server-side authority does not resolve exclusively to that seller. The value
can narrow an already-authorized scope; it cannot authorize a seller or reduce
a normal marketplace buyer’s complete eligible cohort. Omit it when broad
marketplace discovery is intentional.
For a fresh round, the call durably schedules complete eligibility enumeration
and returns running; the frozen seller count may therefore be zero on the
first response while discovery is pending. Each background seller attempt has a
30-second bound. Retry the exact same idempotency key until the result becomes
complete, partial, or failed. The execution
stores the resolved seller cohort, so retries never silently add, remove, or
duplicate sellers. Each seller may return:
quotedwith qualified Proposal IDs;productswith aproductQueryId; orfailedwith a bounded error.
page.nextCursor. perSeller
contains at most 50 outcomes on the current page, while product-heavy outcomes
may continue for the same seller on the next cursor. Product details are
bounded for transport; detailsTruncated: true marks a bounded field projection.
The productId remains the selection key. Meanwhile,
summary.sellersRequested always counts the full frozen cohort.
4. Stage a media buy
Accept a quoted proposal
idempotencyKey on every call, although proposal
acceptance derives retry safety from the qualified proposal version and does
not consume the supplied key.
Stage returned products
For a seller that returned products without a Proposal, preserve every returned identity field and use that seller’sproductQueryId as the idempotency key:
inventorySourceId,
salesAgentId, and pricingOptionId distinguish the exact Product route and
price selected from the returned catalog.
When a returned Product advertises signal_targeting_options, select an
eligible Signal through that Product’s targetingOverlay. Preserve its
signal_ref, value type, bounds or values, activation handle, and pricing
identity exactly as returned; the Seller validates eligibility at launch.
save_media_buy accepts flight when staging from returned products or
accepting a proposal. It accepts top-level budget for one returned product or
a one-allocation proposal. For multiple products or allocations, keep the
returned allocation or set products[].budget for each selected product.
Accepting a proposal creates one media buy, so every allocation must use the
same settlement currency and seller route. If a proposal spans currencies or
seller routes, save_media_buy rejects it before claiming the proposal or
creating a draft; request separate proposals by currency or seller route.
5. Inspect staged work
List media buys under the campaign:limit up to 200; pass back nextCursor for the next page):
campaignId (when the buy belongs to a campaign), phase,
pause state, flight, the seller (as
sellerId plus sellerName), and the buy’s gross budget. sellerId is always
a Storefront ID, never an internal customer ID. It does not retain
Proposal evidence after the acceptance response, so preserve the
proposalSource fields returned by save_media_buy when that audit link
matters. When both filters are present, campaignId wins. An advertiser that
is not in your account returns NOT_FOUND, not an empty list.
Read one buy’s execution tree with get:
campaignId, sellerId, sellerName, budget,
flight, and the why-visibility fields (pendingReason, errorCode,
forwardedAt, buyerReference). Includes add:
Any other include is echoed in
unavailableIncludes with the reason. The
content[0].text of every read mirrors these facts — seller names, budgets,
line items, packages, and format labels — so an agent reading only text sees
the same buy a structured-first host does. Proposal reads and
request_proposals outcomes name their sellers the same way.
Archive is a visibility flag, not a lifecycle phase. A read or
search(kind: "media_buy", filter: { isArchived: true }) reports an archived
buy with isArchived: true and its preserved phase. This lets you distinguish
an archived draft from a completed or canceled buy.
Older archived buys may report phase: "completed" because the previous
archive lifecycle overwrote their status with ARCHIVED and did not retain the
earlier phase. completed is a compatibility stand-in for those rows, not a
record of their pre-archive phase.
At this step, inspect each draft, apply any supported correction with
save_media_buy({ mediaBuyId: ... }), and continue only when the staged set is
the one you intend to launch. Supported corrections on a draft:
flight— move the start or end date.budget— a new total, on a draft with exactly one line item.products[]— per line item, changebudget,bidPrice, orpricingOptionId, or setremove: trueto drop the line item. A product that is not already on the buy cannot be added; stage a new media buy for it.isArchived: true— drop an unwanted draft altogether. The campaign is unchanged. Only a draft can be dropped this way; a dispatched buy is cancelled through the campaign or the v2 update contract.
save_media_buy with its mediaBuyId, isPaused: true, and an
idempotencyKey. To resume that same buy, call it again with isPaused: false.
Send isPaused on its own: do not combine it with a flight, budget, product,
or archive change. Only an ACTIVE buy can be paused and only a PAUSED buy
can be resumed.
Anything the draft cannot absorb (a new product, a different inventory source
or seller route, changed Signal targeting, restoring an archived buy) is
refused with NOT_IMPLEMENTED naming the field, never silently ignored.
5a. Review the draft before going live
When the host renders MCP Apps,open_campaign_receipt opens Review & go
live for one draft campaign: its budget and flight, the staged media buys
with their budget split, why each buy is not live yet, and the readiness
blockers still standing between the draft and launch (a creative that is not
ready, or no media buys staged). The tool returns the shared MCP App directive,
a compact text summary of the same facts, and the projected receipt in
structuredContent.receipt. It reads only: going live remains the explicit
save_campaign step below.
open_campaigns_page with the same campaignId opens its record
instead. The headless equivalent is
get({kind: "campaign", id, include: ["mediaBuys"]}), whose mode: "review"
workspace carries the same readiness.blockers.
6. Launch explicitly
Launch is a two-call update to an existing campaign, not part of campaign creation. First preview — this call launches nothing:action: "pending_confirmation" with campaign.revision and,
under launch, the media buys that would go live and their combined budget.
Show it to the buyer. Only after they say yes, confirm with the previewed
revision:
7. Query campaign delivery
Useget_delivery with report: "campaign_delivery". Supply either an explicit
UTC date range of at most 90 inclusive days or range: { "lifetime": true }
for everything from the campaign’s earliest reported delivery to today, and
choose only the metrics and dimensions needed by the caller:
period and always carries
totals: the same rollup as the rows, over every matched row rather than the
page (totals.rowsIncluded equals page.total). When matched rows span more
than one currency, totals.currency is null and money metrics are
unavailable with reason: "currency_unavailable"; counts and unitless rates
still total. Nothing is FX-converted.
seller is the storefront the buy was placed with and sales_agent the AdCP
sales agent behind it, both resolved from the products on the buy. A row whose
seller cannot be attributed keeps seller: null, groups with its peers, and
still counts in totals; it is never folded into another seller or dropped.
Filter by advertiserId, campaignId, channelGroupId, mediaBuyId, or
packageId. channelGroupId is applied before the report is bounded, so rows
from other Campaign groups cannot displace matching rows.
packageId requires a bounded date range (startDate and endDate, at most
90 days) and cannot be combined with range: { "lifetime": true } — the
reporting operation fetches all campaign data before filtering by package, so
lifetime package filtering is rejected before dispatch. An integrated Media
Company must name at least an advertiser, campaign, or media buy; the server
re-proves that scope against its sandbox advertiser and exact own Storefront
before querying. Advertiser-wide integrated queries also fail closed if any
current buy under that advertiser has wider supply.
Rows preserve the Buyer reporting denomination: spend, ecpm, cpc, and
cpa are gross and fee-inclusive where the underlying buy has pinned terms.
The response names its currency and reports numeric zero as available. A null
derived rate remains unavailable when its denominator or conversion signal is
absent.
This projection is seller-reported delivery viewed through the Buyer hierarchy;
it is not Buyer measurement. The V2 compatibility source does not expose
ordered revision evidence, so V3 does not infer SNAPSHOT or OFFICIAL
finality or billing eligibility. Follow nextCursor without changing the
query when page.truncated is true.
8. Save uploaded assets as a creative
Select the owning advertiser before preparing the upload. Only a finalized JPEG or PNGscope3-asset:// reference bound to that advertiser can be passed
verbatim as sourceAssetRef to the existing save_creative tool, together with
the matching advertiserId or campaignId. If no advertiser was selected
before preparation, keep using the reference through the existing
account-scoped delivery flow.
For an eligible reference, use the existing creative noun; there is no separate
adoption tool:
sourceAssetRef
cannot be combined with assets; its JPEG or PNG media supplies the canonical
image format identity.
The private source is temporary. Saving copies its verified bytes into the
governed creative store, so the Creative remains usable after the upload
source expires. The stored manifest contains the governed asset and a one-way
fingerprint only—not the source reference, private path, or a signed URL.
When you include campaignId, the server creates the advertiser-scoped
Creative first and then applies the existing idempotent campaign membership.
Retry with the same reference and name if a response is interrupted; the retry
returns the same Creative and finishes a missing attachment. Use
advertiserId instead to save it without campaign membership.
For an advertiser Creative with multiple uploaded assets or a promoted MP4,
first search creative_format with the advertiser and selected product. Pass
the returned opaque format id as creativeFormatId, then bind each durable
asset assetId to its declared slot in sourceAssets. This keeps the Creative
in the advertiser Library without requiring a campaign or contacting a
provider. Ordinary assets are unbound and cannot satisfy those format slots:
formatKind or formatOptionRef is also supplied, it
must match the selected format. The MP4 must have reached promoted, and all
sources must resolve to the same customer, principal, and advertiser. A
successful save confirms the canonical advertiser Creative. For a new Creative
that should be attached immediately, use campaignId, formatKind, and the
campaign product’s exact formatOptionRef instead of advertiserId and
creativeFormatId. The save returns providerContacted: false; video
destination delivery remains deferred. Do not claim delivery until later sync
and exact provider readback prove it.
9. Author a social creative
A social creative is authored copy, not an uploaded file.save_creative
accepts a social block and stores each field in the standard AdCP text and
URL slots, so the creative round-trips through save, get, and search
without a preview or transcode step.
Platforms name the same authored field differently (TikTok uses
display_name
and ad_text, Meta uses primary_text). When a creative is pinned to a
platform format, the format declaration decides which slot ids are valid: the
save is refused with the list of declared slots if a slot is not declared, and
per-slot length limits apply. Use components to write those platform-native
slots. Page or profile identity (a Facebook Page, an Instagram account) is not a
save_creative field; a format that declares it as a slot takes it through
components.
Social copy is content but carries no format identity, so a new creative still
needs formatKind (for example image or video_hosted) or a media asset.
On update (creativeId + campaignId), each slot named in social replaces
the existing value on that slot and unnamed slots are left unchanged.
Read a creative in full
get with kind: "creative", sourceId (the campaign ID), and id returns
the complete record:
Format identity is canonical only: a creative read never carries a legacy
agent_url. A creative without a canonical formatKind reports
requiresUpgrade: true and cannot be assigned to a new media buy until it is
upgraded through the v2 API.
Filter creative search
search with kind: "creative" scopes by filter.campaignId or
filter.advertiserId. An advertiser-scoped search narrows with formatKind,
assetType (media kind, for example IMAGE or VIDEO), role (evergreen
or reference), source (uploaded, generated, connected), and
promoted. A campaign-scoped search supports query only, and the narrowing
filters are refused under campaign scope rather than silently ignored. Each row reports
formatKind, mediaKind, assetCount, and requiresUpgrade; the text block
repeats them for text-only hosts. Pagination is the opaque cursor from the
previous page. Filters by readiness state, archive state, or media-buy
assignment are not available: archived creatives are not listable, and an
isArchived: true filter is refused rather than returning the active list.
Archive a creative
save_creative with isArchived: true, creativeId, and advertiserId
removes the creative from every campaign and frees its name, so a later create
under the same name is a new creative rather than a dedupe hit. Archiving is
permanent: isArchived: false is refused, an archived creative reads as
NOT_FOUND, and there is no allowArchived read for creatives.
Interactive buyer Pages
Three buyer Pages have fixed v3 owners. Each owner binds one MCP App resource in its tool descriptor, so a host that renders MCP Apps opens the same Page from Murph, Claude, or ChatGPT; a host that does not render them receives the text summary. The Pages self-fetch their data, so none of these launchers puts the list into model context — usesearch and get for text answers.
The compatibility
open_page enum never lists these Pages; the owner tool is
their portable contract. Task pages:
Open Advertisers,
Open Campaigns, and
Open Review & go live.
Lifecycle operations
isPaused: truepauses an active campaign;falsereactivates it.isArchived: truearchives it from default lists without changing its phase, pause state, media buys, or budget commitment. It is refused while any executable or unsettled media buy remains; cancel or settle each named buy first, then archive the campaign.isArchived: falserestores an archived campaign with the same phase and pause state it had when archived. Send it alone, then re-read the campaign before making further changes. Legacy rows whose status was overwritten toARCHIVEDhave no recoverable earlier phase: they read as archived withphase: "completed", including in an archived completed-phase search, and cannot be restored; create a new campaign instead.desiredPhase: "canceled"terminally cancels the campaign without cancelling its media buys. Send it alone with the currentexpectedRevision. If executable or unsettled buys remain, the request is rejected before any write and names every blocker; explicitly cancel or settle those buys first. If the response is lost, re-read the campaign and retry with its committed revision. An already cancelled campaign then returns its terminal state. This cancellation request is separate from archive and does not archive the campaign.save_creativewithisArchived: truearchives a creative permanently and frees its name; creative unarchive does not exist, soisArchived: falseis refused (see Author a social creative).- A tracked campaign is read-only until it is adopted or duplicated through the existing v2 workflow.
autonomyfields are accepted for forward compatibility but are not persisted yet.