Skip to main content
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.
When you request proposals through Murph, its runtime continues the same request for up to eight dispatches within 30 seconds. It preserves the campaign revision and idempotency key. An unfinished response is provisional, including when no products have arrived; it does not mean there are no matches. If the wait limit is reached or accumulated result pages exceed its response-size limit, Murph retains the request and reports incomplete results. It does not automatically start a replacement round or attach provisional products. Completed results still follow the normal product-selection and attachment checks. Reading full targeting capability pages preserves the original request and any confirmed attachments. If a first request is rejected before execution starts, you can correct it and retry in the same conversation. Errors during an existing execution or with an uncertain outcome keep that execution protected from replacement.

Buyer workflow at a glance

  1. Confirm buyer operator readiness.
  2. Create or select an advertiser.
  3. Create a campaign with its brief, flight, and budget.
  4. Request proposals from ready sellers.
  5. Accept a quoted proposal or stage returned products.
  6. Inspect the staged media buys and resolve any draft issues.
  7. Launch the campaign explicitly.
  8. Query bounded campaign delivery.

0. Confirm buyer operator readiness

Call get_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.
When the domain is usable, 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 requires name 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.
Retain the returned advertiserId. To update, send it with only the fields to change. Do not send sandbox on an update.

2. Create a campaign

Creation requires advertiserId, name, flight, budget, and an idempotencyKey. Write the brief from the buyer’s stated goal, audience, and what is being promoted.
Creation does not launch. Retain the returned campaignId and revision.

Target Nielsen DMA areas

You can target Nielsen Designated Market Areas (DMAs) while saving a campaign or staging a media buy. On save_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.
If a name matches more than one DMA, the save returns a validation error with the matching candidates. Show those candidates to the buyer and ask them to choose one. Do not guess which area they mean.

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:
  • quoted with qualified Proposal IDs;
  • products with a productQueryId; or
  • failed with a bounded error.
Repeating the same idempotency key returns the same proposal round. Use a new key only when intentionally asking sellers for a fresh round. Evaluation instructions are recorded but are not yet applied to ranking; review the returned results yourself. Only one proposal-request execution may run for a buyer at a time, across all campaigns. Poll the active execution to terminal before starting another. After the execution is terminal, follow every 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

The proposal version must still be current and belong to the campaign. The result is a draft media buy; accepting a proposal does not launch it. The current schema requires 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’s productQueryId as the idempotency key:
Do not reconstruct qualified IDs. When returned, 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:
Or list every current buy for an advertiser across its campaigns, paged (limit up to 200; pass back nextCursor for the next page):
Each row reports its 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:
The base object carries 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, change budget, bidPrice, or pricingOptionId, or set remove: true to 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.
To pause one running media buy without pausing its campaign or sibling media buys, call 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.
The receipt covers draft campaigns only. For a campaign that has already gone live, 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:
The response is 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:
See Launch a campaign with explicit confirmation for the full preview shape. Do not combine launch with pause or archive in the same call. A launch can partially write downstream execution state even when no media buy activates; read the structured error, fix the cause, re-read the campaign revision, and retry deliberately.

7. Query campaign delivery

Use get_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:
For the whole life of a campaign, grouped by the seller it ran with:
The response echoes the resolved window as 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 PNG scope3-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:
The upload and campaign must resolve to the same customer and advertiser, and the authenticated principal must own the upload. Prepared, expired, cross-customer, and cross-advertiser references are refused. 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:
The signed format is account- and advertiser-bound. The server revalidates its seller, product route, option, and declaration before reading an asset or writing the Creative. If 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. 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 — use search 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: true pauses an active campaign; false reactivates it.
  • isArchived: true archives 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: false restores 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 to ARCHIVED have no recoverable earlier phase: they read as archived with phase: "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 current expectedRevision. 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_creative with isArchived: true archives a creative permanently and frees its name; creative unarchive does not exist, so isArchived: false is refused (see Author a social creative).
  • A tracked campaign is read-only until it is adopted or duplicated through the existing v2 workflow.
  • autonomy fields are accepted for forward compatibility but are not persisted yet.
See Preview limitations before replacing a v2 buyer integration.