Skip to main content
tools/list is authoritative for the active account. A principal with both Buyer and Seller access receives their deduplicated union at connection time, while calls remain authorized against the selected account. Enrollment, permissions, feature rollout, and host support can narrow the catalog. This page explains the complete registered families and intended use.
The Identity and brands guide covers current identity and provenance, domain correction, public brand lookup, confirmation and human proof follow-up in text-only clients. These operations use get_status, get, save_buyer_operator, save_advertiser and save_seller. Select identityContract: "confirmed-v1" for the preview and confirmation workflow; existing save payloads keep their earlier contract. switch_account works for a signed-in user on both session-based and reconnecting MCP clients. After a successful switch, call get_status to read the active account and its available tools; the selected account remains active when the client reconnects.

Catalogs

Buyer accounts can search and read their advertiser-scoped catalogs with search(kind: "catalog") and get(kind: "catalog", id, advertiserId: "12345"). Use save_catalog to declare one AdCP Catalog or archive it. Item reads are a bounded get include and never expose feed credentials.

Open Media Buys

open_media_buys_page opens the Seller Media Buys Page. It accepts an optional positive-integer-string accountRelationshipId and optional view of media_buys, creatives, or delivery. With no relationship ID, it opens the Seller’s unfiltered Media Buys Page. When a relationship ID is supplied, the tool first proves that the relationship belongs to the authenticated Seller Account. An unknown or another seller’s relationship fails closed: it returns no Page directive and opens nothing. The Page then shows only that buyer relationship’s activity with the Seller — its media buys and synced creatives — never the buyer’s own campaigns.

Open Seller Dashboard

open_seller_dashboard opens Seller Dashboard. It takes no arguments and is listed only for Seller Accounts; a call from another account returns the switch_account call that reaches a Seller Account and opens nothing. The Page chooses its own view. While the Seller Account holds an active, unexpired Demo Storefront lease, it opens the Demo Storefront evaluation view of practice runs, labelled as synthetic evaluation data. Opening it does not make the Demo Storefront live. Every other Seller Account sees its seller analytics. The Page loads its data through its own app-only reads. Every search call must provide query, kind, or both. An empty call is invalid. Use query for a match term or documentation question and kind to list account objects.

Campaign filters

search(kind: "campaign") pages the complete filtered campaign collection. Pass the returned nextCursor with the same filters to continue; total is the filtered total and truncated is true only when another page remains. A Buyer can browse the selected advertiser’s visible products across eligible Sellers before creating a campaign:
Each result includes the exact opaque productId, sellerId, and Seller name needed for a subsequent creative_format search. Pagination traverses each qualified product once, even when multiple seller proposals reference it. Pass nextCursor back with the same advertiser and limit. When resultsComplete is false, replace the provisional page after polling; when partial is true, do not treat an absent product as unavailable. persistenceIncomplete identifies a terminal partial result where otherwise visible products were withheld because a complete, buyer-safe identity could not be persisted; restart without a cursor to retry them. This read may use live or cached catalogue results and does not create a proposal, campaign, or media buy. To see whether each product can be bought on the advertiser’s connected ad account, add include: ["executionReadiness"]:
Each row then carries executionReadiness when its seller reports readiness (currently Meta, TikTok, Snap and LinkedIn): a summary of whether the product can be bought on that account and what’s missing.
status is ready_for_execution, setup_required, input_required, selection_required or inventory_unavailable. Each slot names a resource the product needs (such as a Facebook Page or a pixel), the decision for it, an optional reason, the option already chosen in selected (when the seller picked one), and total, the number of options available. The options themselves aren’t listed in this search. Parts of a seller’s readiness that don’t match this shape are left out individually, so a row keeps the rest of its summary. Rows from sellers that don’t report readiness have no executionReadiness field. Keep the same include when you pass nextCursor back. The include works only for wholesale_product on a buyer account. A Creative search must name exactly one owner. Use filter.advertiserId to browse the advertiser’s visible Creative summaries, or filter.campaignId to list summaries attached to one campaign:
Advertiser scope supports formatKind, assetType, role, source, and promoted. Omitting promoted returns all visible Creatives; true selects the promoted reusable shelf, and false selects non-promoted Creatives. Campaign scope supports the top-level query only. Each successful response repeats the effective owner scope and filters in both text and structured output. Results are plain Creative summary rows; this search does not open or return a Creative Library Page. Pass nextCursor back only with the same selected account, owner scope, effective filters, and limit. A Seller asking what it currently offers uses:

Conversation history

Buyer and Seller members can explicitly search their prior Murph rooms with kind: "conversation":
The result includes only the member’s own rooms and explicitly shared rooms in the active account. Matches include bounded, display-safe matchedText evidence from the matching title, participant, or message, even when a matching message is older than the recent transcript tail. Follow a returned id with get({"kind":"conversation","id":"..."}) to read up to 20 recent, display-safe user/assistant messages. That bounded detail read does not limit the history searched. Tool output and system context are never returned. Service credentials without an end-user identity fail closed, and conversation search runs only when explicitly requested; it is not included in an unqualified cross-kind search.

Retained Sessions

Retained Session reads are available to signed-in, non-impersonated platform operators in seller accounts. An ordinary seller can directly search or get only an explicitly source-qualified storefront_rfp_v1 Session. Buyer Session access is not yet available because no source currently opts in to buyer relationship checks. Catalogue visibility is unchanged from the current release; buyer Session search requires a seller account and direct Session reads are denied. When a source does opt in, buyers will see no result for revoked relationships, other buyers’ exchanges, or unrelated sellers’ exchanges, and results will exclude private events, seller private notes, and coaching. save_session remains seller-only and can add an idempotent private coaching note. See the Retained Session timeline for access, cursor, import-limit, and unsupported-operation boundaries.

save_session: record a private note

save_session accepts only action: "record_note". Use the exact sessionId returned by Session search or read, a clientRequestId, and the private note. throughEventId is optional; when supplied, it anchors the note to one accessible retained event in that Session. sessionId, clientRequestId, and throughEventId are opaque strings. They are trimmed, must not be empty, and can contain at most 512 characters. The private note is also trimmed and required, with a maximum of 8,000 characters.
The MCP structuredContent response is limited to the write result:
It does not return the full Session or event, and it does not say whether a note was created or replayed. Re-read the Session with get({"kind":"session","id":"SESSION_ID"}) to inspect the retained note. The same scoped request key with the same content replays the saved note. Reusing that key with changed content returns a conflict. The tool never sends an outbound message. A retry is denied when current access no longer permits the note, and every action other than record_note is unsupported. This tool is listed only for a signed-in platform operator in an active seller account. It requires no user impersonation and the current native-room permission check. The seller catalog entry does not expose it to customers; the tool also rechecks the operator, end-user, and seller boundaries at call time. See the Retained Session timeline for the complete access and retry rules.

Seller and product filters

For search({"kind":"wholesale_product"}), add sourceId to narrow the list to one ad-server or modular source, or filter.productStatus to select draft, active, or archived products. Each product carries origin: ad_server for a product you author on an ad-server source, and modular_source for one a modular source offers from its avails. A modular source’s products are the ones its storefront catalogue serves to buyers and proposals. You change them at the source, not with save_wholesale_product. Each product also carries price, the price a proposal quotes for it, and a Demo Storefront product carries synthetic. For Buyer seller reads, filter.ids accepts up to 50 exact Storefront ids in one call, preserves requested order, and reports unavailable ids as objects.missingIds. As an alternative Buyer read scoped to one Seller, call get with kind: "seller", that exact Seller id, the current account’s advertiserId, and include: ["products"]. Preserve the returned productQueryId and catalog revision when polling the same request to a terminal result; do not reuse a Product from an older catalog. Buyer search(kind:"seller") also accepts these filter fields to narrow results: seller and connection are intentionally separate buyer nouns. A seller is one exact Storefront commercial counterparty and can exist before authorization. A connection is one authorization grant, identified by its typed target. Seller grants retain sellerId. Enrolled buyers can also connect a Creative Engine using target: {kind: "creative_engine", id: "..."} and search its grants with filter.targetKind: "creative_engine". Omit that filter to list seller grants. One seller can have zero, one, or multiple connections, and each connection can contain multiple discovered provider accounts. Use search(kind:"connection") for questions such as “who am I connected to?” and join the returned sellerId to the seller catalog when names or marketplace policy are needed. Each connection reports how many advertisers are mapped to its active provider accounts, and which ones, so a single search(kind:"connection") call answers “is this advertiser mapped here?” without reading each connection. A get(kind:"seller") call for a single seller also returns connectionState.directedCampaignSubscription when an active directed campaign subscription exists between the buyer and that seller. The field is absent on search results and null when no subscription exists.

Internal Marketplace administration

Directly authenticated Apostra SuperAdmins may also receive two internal tools: These tools are omitted from every non-SuperAdmin catalog and re-check staff authorization at execution. Apply performs a whole-batch stale-state preflight, then commits each Storefront independently. Its response labels this behavior BEST_EFFORT_WITH_BATCH_PREFLIGHT; callers must inspect every item before retrying failures.

Plan and billing

Check and complete Terms setup in text-only chat

Call get with { "kind": "billing" }. This read is available before Terms acceptance, takes no ID or account override, and uses the selected account’s organization. Check object.organization.customerId before proposing a write. The read uses the same billing facts and next-action order as Plan & Billing. Both response channels contain the organization, readiness, Terms, payment status and next action, so this flow works in hosts that only expose text. The response omits card details, billing contact details, processor identifiers and browser credentials. When nextAction.canCompleteInChat is true and the person has reviewed the Terms and explicitly authorizes acceptance, call save_billing with the exact terms.requiredVersion from that read. This JavaScript example uses the structured response; a text-only host gets the same version in the required version line:
save_billing accepts exactly one intent per call: terms, paymentTerms, or paymentAuthority. Acceptance requires an authenticated user who is a direct organization admin, within the credential’s organization boundary. A child account admin is insufficient without direct authority on the parent. A stale version is rejected; read again and obtain authorization for the new Terms. Use the subsequent read to verify the same organization.customerId, the accepted version and any remaining action. A successful acceptance returns action: "terms_accepted" with terms.acceptedVersion, the field the read reports.

Choose payment terms for consolidated billing

When get_status reports a required payment_terms next action, an organization administrator must choose the terms that sellers are asked for before the buyer can use consolidated billing. The next action names save_billing with paymentTerms: "net_60", which is the default choice; use that value when the buyer wants the default. Otherwise, confirm the buyer’s choice and send one of net_15, net_30, net_45, net_60, or net_90:
This capability is available only while the buyer-payment-terms rollout is enabled for the account. Before then, save_billing refuses paymentTerms with a validation error. Do not send null to restore a default; null is refused. Send net_60 instead. The successful response has action: "payment_terms_saved" and echoes both paymentTerms and paymentTermsChosen: true. The recorded choice belongs to the billing organization, including when its administrator works in a child Buyer Account context. Organization (parent-container) sessions can read billing readiness, but their save_billing calls remain blocked by the existing tool policy. For these sessions, terms.canAccept describes the user’s authority and nextAction.canCompleteInChat is false. An organization admin completes Terms and payout setup in the Plan & Billing Page instead: its accept_plan_billing_tos and set_plan_billing_payout_payee actions run in the Organization session. A payout payee set there is the organization’s default for every Seller Account without its own payee. The organization must have at least one Seller Account. If the account is already governed by a custom contract, no standard Terms acceptance is recorded. The tool returns action: "terms_not_required" with terms.accepted: false and terms.governedByExistingContract: true so the agent does not report a legal acceptance that did not occur.

Payment status and the remaining handoff gap

The billing read does not add a payment-method write or a secure one-time cardholder handoff. It returns paymentAuthority.secureHandoff.available: false. The reason is secure_handoff_unavailable, or card_collection_not_enabled for an organization outside the card-collection rollout. This field describes the missing one-time ceremony, not availability of the existing capture-link tool. Existing save_billing integrations keep the same request, confirm and status actions, response shapes and permissions. request returns confirmation_required with a confirmation token; confirm consumes that token and returns human_action_required with the cardholder URL. That legacy URL can be reopened until verification or expiry. It does not provide a one-time browser exchange with fresh human proof, and is not the secure handoff required to complete the new text-only setup flow. Do not describe that missing capability as complete or ask for card details in chat. For current payment readiness, poll get with { "kind": "billing" } every 15–30 seconds. Its paymentAuthority fields distinguish current verification from setup history. status and expiresAt describe the latest capture link, at the same path as the save_billing status poll: Existing save_billing calls with { "paymentAuthority": { "action": "status" } } keep their capture-history response: paymentAuthority contains method: "capture_link", status and expiresAt, and the action is payment_authority_pending, payment_authority_opened, payment_authority_verified or payment_authority_expired. Existing card-collection permissions still apply; no capture record returns NOT_FOUND. These responses do not report the current payment method or submitted form state. Use the new billing read for those facts. After verification, read billing readiness again. A verified payment method does not imply every billing action is complete. Use the canonical next-action explanation to distinguish good standing from a pending decision. This read does not grant purchasing or product access. If the next action cannot be completed in chat, explain that limitation and use its documentation link. The new text-only readiness flow stops before card setup. A versioned one-time handoff with fresh cardholder confirmation is still needed. The existing capture-link contract continues to serve deployed clients. Legal acceptance and payout changes remain authorization-sensitive. The three named Plan & Billing tools in this table are private Page capabilities and may be omitted from model context. Follow the tool response and Page requirements; never pass card or bank details through free text.

Buyer tools

Buyer search and get kinds include the account’s advertisers, campaigns, creatives, creative formats, creative collections, wholesale products, proposals, media buys, sellers, asks, and organization-owned Agents, plus other kinds advertised by the live schema. Follow the Buyer workflows page for sequencing.

Set package performance standards

Pass performanceStandards on a save_media_buy product when the buyer needs a package-level commitment. It is a non-empty list. Each entry has one metric (viewability, ivt, completion_rate, brand_safety, or attention_score) and a threshold from 0 through 1. Viewability also requires standard: "MRC" or standard: "GroupM"; do not send standard for the other metrics. IVT is a maximum. Every other metric is a minimum. You may name a measurement vendor with vendor: { "domain": "vendor.example", "brand_id": "optional-id" }. The vendor must be a brand reference: domain is required and brand_id is optional. Only send these terms for a product or seller that declared performance-standard support in discovery. At launch, Interchange forwards the exact standards when that support is present. Otherwise it refuses the launch with SELLER_PERFORMANCE_STANDARDS_UNSUPPORTED; it never omits the terms silently. Use get(kind: "media_buy", include: ["products"]) to read the accepted standards from each package.

Sync a first-party audience

Use save_audience to send a batch of first-party CRM audience changes for an advertiser that the active Buyer Account can write. Each item has a buyer-owned audienceId and can add members, remove members by external_id, or delete the audience entirely. Deleting an audience is destructive. Members are AdCP AudienceMember objects. Provide each added member’s external_id and either a SHA-256 hashed_email, a SHA-256 hashed_phone, or an email that the service normalizes and hashes before forwarding. Hashes are lowercase hex. If both email and hashed_email are present, they must represent the same normalized email. Do not put raw PII in external_id.
A successful save returns action: "synced", an operationId, the advertiserId, and audienceCount. The operation is asynchronous. Read the advertiser’s current audience status and uploaded and matched counts with get({ kind: "audience", advertiserId: "ADVERTISER_ID" }); there is no separate audience-status tool. Each audience reads back under the names save_audience takes (audienceId, name, consentBasis), with status, uploadedCount, matchedCount, lastOperationStatus, createdAt, and updatedAt beside them. The V3 audience workflow does not support the V2 missing-audience sweep or push-notification callback. To remove an audience, send that audience’s audienceId with delete: true.

Save event sources

Use save_event_source to register the conversion event sources (pixels, server feeds, CRM or warehouse syncs) that an advertiser sends events from. One call covers one advertiser and up to 50 sources. Each entry in eventSources is keyed by your own eventSourceId: a new id creates the source, an existing id updates it. name is required when an entry creates a source; Interchange never fills it in for you.
To say which pixel a source uses on a seller account, add sellerAccounts to its entry: up to 20 pairs of accountId, the seller account’s accounts[].id from get({ kind: "connection", id: "CONNECTION_ID" }), and sellerId, the id of the pixel on that account (for example a Meta dataset or a Snap Pixel). Name the pixel when the account has more than one.
Saving records the request; it does not contact the seller or check the pixel. Interchange checks the pixel when the source syncs to the account, which happens when you create a media buy on that account whose optimization goal names the source, while the Conversion events switch is on for that seller. This is rolling out account by account; see how a source reaches each seller account. Saving again with a different sellerId changes the pixel for that account. If the account already uses a confirmed pixel, it keeps using that pixel until Interchange confirms the new one. If the account’s sync failed, expired, or was removed, naming a pixel again returns it to pending so it can be retried. A save that changes only an event source’s seller accounts reports updated. Accounts you leave out of a later save keep their binding. An accountId that is not an active, connected account of yours fails the entry with REFERENCE_NOT_FOUND, and the source is not saved. Send isArchived: true to archive a source, and isArchived: false to undo the archive with its configuration kept. An entry that sets isArchived carries no other change. Campaign optimization goals that reference an archived source stop resolving it until the archive is undone. The response has one entry per source you sent, in the same order, each with its own action:
  • created, updated, or unchanged: the full event source, plus setup, which links the guide for sending events. Undoing an archive reports updated.
  • archived: the eventSourceId only.
  • failed: the eventSourceId and errors in the AdCP error shape. Each error has a code, a message, a recovery hint, and, where it applies, the field at fault:
    • VALIDATION_ERROR (correctable): the entry is invalid, for example a new source sent without a name.
    • REFERENCE_NOT_FOUND (correctable): an entry that sets isArchived named an eventSourceId the advertiser does not have, or a sellerAccounts entry named an accountId that is not connected.
    • SERVICE_UNAVAILABLE (transient): the entry could not be saved; send it again with a new idempotencyKey.
One failed entry does not stop the others. The idempotencyKey covers the whole call: repeating the same call with the same key returns the first result, including any failures, with replayed: true. To retry a failed entry, send it again with a new key. Each event source reports health in the AdCP EventSourceHealth shape. health.status is the AdCP grade (insufficient, minimum, good, or excellent), health.last_event_at is when Interchange last received an event from the source, and health.issues lists anything that needs attention, such as no events received yet or a rejected event. Registering a source does not prove that events arrive: read it with get({ kind: "event_source", id: "EVENT_SOURCE_ID", advertiserId: "ADVERTISER_ID" }) before relying on it. sellerAccounts lists the seller accounts the source is bound to. It is empty until you name a seller account. Each entry has the account’s storefront (id and name), its accountId, sellerId, status, lastSyncedAt, health, and, while the account cannot use the source yet, a blocker with a code and message. sellerId is the pixel Interchange has confirmed on the account. It stays null, and status stays pending, until a sync confirms the pixel, even after you name one. A pending account carries the SELLER_OBJECT_SYNC_PENDING blocker. The pixel you named is not returned. When you change the pixel on an account that already has a confirmed one, the account keeps using the confirmed pixel, which stays in sellerId; it reads status: "pending" with the SELLER_OBJECT_SYNC_PENDING blocker until Interchange confirms the new one. sellerAccounts[].health is the seller’s own assessment of the confirmed pixel, in the AdCP EventSourceHealth shape, as the seller reported it when Interchange confirmed the pixel. It is null until a pixel is confirmed, and null when the seller gives no assessment. A pixel that is set up but has not received events yet reads events_received_24h: 0 with no last_event_at; send events to it before relying on it for optimization. Interchange returns it as the seller reported it. The source’s top-level health is Interchange’s view of the events it received; sellerAccounts[].health is the seller’s view of its pixel. A seller account that cannot take event sources at all reads status: "failed" with the SELLER_OBJECT_TYPE_UNSUPPORTED blocker. These states never block saving or launching a campaign; a campaign’s optimizationGoals name event sources by eventSourceId, and their state on each seller account is read here. On every buyer read, a sellerId identifies a Storefront, with one exception: on an event source, sellerAccounts[].sellerId follows AdCP’s seller_id and is the seller’s own ID for that event source, such as a Meta dataset or Snap Pixel ID. It is not an internal customer ID. For one compatibility release, a seller’s deprecated connection.customerId remains available for existing integrations. Use sellerId or storefrontId instead; connection.customerId will be removed in a follow-up release after this transition period. For a saved Creative, add include: ["preview"] to get. The read returns a 15-minute render URL bound to the exact current Creative state revision, or a reason when the format has no first-party renderable asset. The URL runs only on the isolated Apostra render origin. A changed Creative invalidates the old URL, and the response never includes storage URLs, provider preview HTML, or measurement trackers. Direct image, video, and audio source assets are renderable; active HTML, VAST, DAAST, and ZIP documents return a reason instead. Before assembling uploaded assets, search the selected advertiser’s visible canonical format declarations. This read does not require a campaign and does not make a provider call or setup request. Seller-owned product declarations are available without a provider connection; direct-adapter products keep their existing active-connection visibility rules:
productId is required to keep the catalogue read bounded. Add sellerId or formatKind when the selected product supplies that scope. Each result returns an opaque id, the seller’s display name and id, the exact formatOptionRef, its canonical formatKind, any declared frame and size, and its slots. Every slot includes its stable slotId, required flag, minimum and maximum cardinality, accepted asset kind, and the exact authored constraint fields supplied by the product. Use the opaque result id with get({kind: "creative_format", id, advertiserId}) for stable readback. The read includes only currently buyer-visible product and publisher declarations; it does not add entries from the legacy global format catalogue. Pass the returned id as creativeFormatId when calling advertiser-scoped save_creative. The server revalidates the selected advertiser, seller, product route, option, and declaration before reading an asset or writing a Creative. If formatKind or formatOptionRef is also supplied, it must match the selected format. Search creative collections with exactly one campaign or advertiser scope. You can create an advertiser collection without attaching it to a campaign. To read one, use get with kind: 'creative_collection', its id, and advertiserId. To change an advertiser collection, use save_creative_collection with advertiserId, collectionId, and the returned updatedAt as expectedUpdatedAt. Updates allow name, description (description: null clears it), or parentId (parentId: null clears it); member changes accept saved creative ids only. Results include parentId and descendantMemberCount, which counts distinct members across the collection and its active descendants. A parent must belong to the same advertiser, cannot be the collection or one of its descendants, and cannot make the hierarchy deeper than 16 levels. Archive with isArchived: true only after archiving child collections. Restore with isArchived: false and the archive response’s expectedUpdatedAt; a collection with a parent can be restored only while that parent is active and belongs to the same advertiser. A stale revision does not change the collection, and expectedUpdatedAt is refused on campaign collections, which carry no revision fence. Advertiser collections remain non-executable: they do not attach campaigns or set roles, sync policy, or arbitrary metadata.

Replay an uncertain advertiser create

When creating an advertiser, you may supply an idempotencyKey of 16–255 letters, numbers, underscores, periods, colons, or hyphens. Use one key for one logical create and reuse that exact key with the unchanged create payload if the response is lost or uncertain. The replay returns the originally created advertiser; changing the payload with the same key returns a conflict. The key is create-only and optional, so a create without it retains ordinary create behavior. Keep correlationId unique for each transport attempt: it is a diagnostic handle, not the replay key.

Create and verify a campaign creative

Call save_creative with a campaignId, name, and optional assets to add a creative directly to a campaign. Each asset supplies exactly one public url or base64 dataUrl, plus its assetType. The direct tool accepts image, video, audio, HTML, VAST XML, and text media; ZIP bundles continue through the inspected HTML5 upload flow. Add clickUrl when the creative has a click-through destination. makePrimary is available for image, video, audio, HTML, and VAST assets; text can be attached only as a subsidiary asset. For a local JPEG, PNG, or MP4, select the owning advertiserId and call upload_creative_asset. A destination Storefront connection and provider account are deliberately not part of this V3 source identity. The Task hashes the file in the browser and sends the bytes directly to private storage. File bytes never enter model or chat context. The signed PUT URL is returned only to the embedded Task app; it is excluded from model-visible results, chat/activity, logs, and persistence. Configured buyer ingress accepts JPEG and PNG files of 1–30,000,000 bytes. When managed-video ingress and inspection are configured, authenticated Buyer Accounts also accept MP4 files of 1–2,000,000,000 bytes. Finalization verifies the declared MIME type, exact size, and digest. MP4 finalization also reports asynchronous media inspection state. An expired preparation must be started again; retrying a completed upload returns the same source identity. Pass a finalized JPEG/PNG source reference verbatim as sourceAssetRef to save_creative. The advertiser is the supplied advertiserId, or is derived from campaignId. For an eligible reference, use sourceAssetRef instead of assets:
The server accepts only the customer, authenticated principal, and advertiser that owned the finalized upload. It re-verifies the private bytes, copies them into the governed creative asset store, and saves only the durable asset URL plus a one-way source fingerprint. Private object paths, source references, and signed URLs are never persisted on the creative. With campaignId, the creative is created under that campaign’s advertiser first and then attached through normal campaign membership. Retry the same sourceAssetRef and name after an uncertain response: an existing adoption is returned and any interrupted campaign attachment is completed. A different name is refused rather than renaming or duplicating the adoption. Use advertiserId instead of campaignId to keep the creative in the advertiser library for later assignment. To assemble uploaded assets before a campaign exists, search the selected product with search({kind: "creative_format", filter: {advertiserId, productId}}), then pass the result’s opaque id as creativeFormatId. Bind each durable asset to a declared slot through sourceAssets[].assetId. The upload Task does not choose a format. save_creative derives the canonical formatKind and formatOptionRef from the signed selection and resolves only its exact seller and product route. Use advertiserId with creativeFormatId to keep the assembled Creative in the Library without a campaign. For a campaign-scoped create, use campaignId, formatKind, and the campaign product’s exact formatOptionRef instead; the save also attaches the new Creative to that campaign. V3 does not yet attach an existing advertiser-only Creative; use the V2 attach_creatives_to_campaign operation for that later step. Ordinary assets are unbound and cannot satisfy those slots in the same call. Repeatable groups use group[index].member: bind uploaded media through sourceAssets[].slot and text or URL members through social.components[].slot, using the same zero-based index for every required member in an instance. Indices must be contiguous from zero. For example, a hosted-video format can bind an MP4 to video and a JPEG or PNG to thumbnail:
To save an incomplete assembly, add "mode": "draft". A draft is scoped to the advertiser and can omit required media, copy, destination, or repeat-group slots. The response and every later Creative read return its state and whole-state stateRevision. The existing opaque revision remains the buyer-preview projection digest. Revise the same draft by sending its creativeId, current stateRevision as expectedRevision, the signed creativeFormatId, and the complete replacement sourceAssets and authored-slot state. The save replaces the prior submitted state; it never merges two revisions. A stale revision fails without changing the Creative. Use "mode": "complete" on that same replacement when all required slots are present. Completion runs the format’s current server-side validation and keeps the Creative in the advertiser Library without attaching it to a campaign or contacting a provider. Existing calls that omit mode keep complete-save behavior. The server re-proves every source against the advertiser. A video must have completed inspection and reached promoted. Saving under advertiserId creates the canonical Creative without a campaign or provider call. Saving under campaignId also attaches it. Video delivery remains destination_required until V2 attach_creatives_to_campaign resolves a compatible selected Meta destination. That governed attachment rechecks the durable Library source and current account authorization, then uses normal Meta review and readback. Other destinations remain unsupported for this bridge. Do not substitute the provider-bound V2 adoption operation for a buyer-owned V3 source. After the campaign executes, call get with kind: "creative", sourceId set to the campaign ID, and id set to the returned creative ID. When a legacy Sales Agent library receives the creative through sync_creatives, platformLinks rows show the downstream ad-platform sync status, platform ID, approval status, and last sync time. Inline package delivery does not create a platformLinks row. Each agent has one authoritative row: the most recently updated account lineage wins after a credential or account rotation. A platform ID confirms that the destination created its creative object; the status and approval fields still determine whether it is ready to deliver. Existing metadata-only save_creative calls require no changes. Start with get_status. Its operatorIdentity.usableForBuying field is false when the operator is missing or still uses a platform-owned legacy fallback. In that state, an account administrator must call save_buyer_operator with the buyer’s real non-platform domain and choose whole_operator or a stable specific_unit before new discovery or buying work. 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. If the identity is locked, follow the support action from get_status instead. This confirms the commercial operator identity used with sellers; it does not add users, change account membership, or change the login organization.

Launch a campaign with explicit confirmation

Launching with save_campaign is a two-call operation. First request a preview without changing the campaign:
This returns action: "pending_confirmation" — a successful, structured response, not an error — with campaign.revision, the campaign budget and flight, and under launch exactly what would go live: mediaBuys (each staged media buy’s id, name, phase, budget, and seller; the first 25, with mediaBuysTruncated: true if there are more), mediaBuyCount, and combinedBudget (the sum across those buys when they share a currency; null with a per-currency budgetsByCurrency list when they do not). nextStep carries the exact follow-up call, including a fresh idempotencyKey for the confirmation (the preview’s key must not be reused, or the preview is replayed). It does not launch the campaign. Do not combine this preview with campaign field changes; save those changes in a separate call before requesting the preview. After reviewing the preview, send that campaign.revision as expectedRevision with explicit confirmation:
confirmLaunch: true requires the expectedRevision returned by the preview. The server checks that revision again while holding the campaign mutation lock, before any provider dispatch. If the campaign changed after the preview, the call returns REVISION_CONFLICT; re-read the campaign, request a new preview, and confirm that new revision instead of reusing the earlier confirmation.

Cancel a campaign and its media buys

To cancel a campaign, send desiredPhase: "canceled" by itself with the campaign’s current expectedRevision:
This requests cancellation for every nonterminal media buy through its existing seller path. It is asynchronous: a requested cancellation can return per-buy canceled, cancel_pending, and cancel_failed outcomes; a buy already terminal retains its observed terminal phase. While any buy is pending or failed, the campaign reads as phase: "ending" with reason "canceled"; the response plainly says it is not canceled until every buy is confirmed. Pending exposure stays visible and releases only when that buy becomes terminal. The background status sync moves the parent to phase: "canceled" after the final child settles, without another buyer call. Retry with the latest expectedRevision to re-drive pending or failed buys; confirmed buys are not canceled again. Archive is separate: use isArchived: true only when you want to remove a campaign from default visibility.

Archive and restore

save_advertiser and save_campaign archive with isArchived: true and restore with isArchived: false. Archiving is a soft delete: the record moves to archived state and everything under it is retained and stays readable. Archiving an advertiser does not lock its campaigns, media buys, or creatives — search and get on those children keep working — and it frees the advertiser’s name for a new advertiser. Restoring an advertiser whose name is now held by an active advertiser returns CONFLICT; rename or archive the other advertiser first. To archive several advertisers in one confirmed action, pass advertiserIds (up to 50) with isArchived instead of advertiserId. Each advertiser is archived or restored independently and the response lists every outcome under results; partialWrite: true marks a mixed result. A restored campaign comes back as a draft: its media buys were retired when it was archived, so stage inventory again before launching. save_media_buy archives a draft media buy only (isArchived: true); a dispatched buy is cancelled through the campaign or the v2 update contract, because that cancellation may wait on seller or operator approval and must not be reported as done before it is. An archived media buy cannot be restored; stage a new one on the campaign.

Advertiser and campaign tracking

Tracking is a typed field on the existing advertiser and campaign nouns; it is not a separate V3 noun or tool family. V3 uses the same tracking services as the V2 REST compatibility surface. Set advertiser defaults with save_advertiser. This HappyDemics example keeps the raw vendor tag, identifies its documented dialect, and lets the server compile it to AdCP universal macros:
Read it back with:
The advertiser’s tracking.trackers[] returns a stable trackerId, the url as you saved it, the translated canonicalUrl, and the auditable mapping metadata. In the example, documented HappyDemics tokens canonicalize as {{USER_ID}} → {DEVICE_ID} and {{GDPR_CONSENT_STRING}} → {GDPR_CONSENT}. Other double-brace spellings are not inferred without their own vendor evidence. V3 returns bounded mapping, diagnostic, and documentation summaries with their total counts; the V2 tracking-config response retains the complete stored mapping artifact for non-model audit clients. Long URL values use bounded previews with their original length and a truncation flag, and effective campaign entries do not duplicate the source tracker’s mapping artifact. When updating an existing tracker, send its trackerId and omit url to preserve the complete stored URL; a returned truncated preview is also recognized and will not overwrite that stored value. Send the returned advertiser revision or campaign campaignRevision as tracking.expectedRevision to make any tracking update an atomic compare-and-set; URL-preserving updates add that guard automatically. Tracker URLs are limited to 8,192 characters and 64 macro tokens. For a custom event, set trackerType to custom and supply customEventName; that field is invalid for impression or click trackers. Stored free-form tracking values returned by V3 are bounded and fenced as untrusted external data. Tracker labels, custom event names, and custom-macro descriptions can be passed back unchanged on an update; the server restores their exact stored values. New or changed custom-macro descriptions are limited to 240 characters. Before saving or trafficking a tracker, call test_creative_macros. The tool does not fetch the URL or change a creative. Standard request auditing retains only statuses and counts, never tracker URLs or binding values. The tool returns four independently copyable stages—exact raw input, canonical AdCP form, recipient form, and final synthetic substitution—plus mapping evidence, bindings, and diagnostics.
For this example, the stages show {{USER_ID}} → {DEVICE_ID} → %%ADVERTISING_IDENTIFIER_PLAIN%%, while the final stage uses a visibly synthetic advertising identifier. For consent syntax inspection, gvlVendorId can show the corresponding GAM ${GDPR_CONSENT_<id>} form, but a caller-supplied number is not verification: the report remains failed and not preview-ready. Without the number, the consent parameter is removed and the report also fails closed. A production consent mapping needs separate evidence that the ID belongs to the measurement vendor; the tester never infers that relationship from a pixel URL. Available scenarios are device_id_present, device_id_unavailable, gdpr_applies_with_consent, gdpr_does_not_apply, and missing_required_value. The unavailable-device scenario deliberately shows an empty device value and labels it privacy_suppressed. Change scenarioKey to obtain a different deterministic cachebuster. Explicit values must be raw synthetic test values, never real user identifiers and never pre-encoded; a value that already contains percent-encoded octets is flagged as possible double encoding. Binding values and stage templates in the report are bounded by UTF-8 bytes, and projection reports any truncation or omitted detail. The whole Murph result stays within a 96 KiB budget. Inspect the substituted stage for placement and encoding. Explicitly required macros that are absent, missing required values, unknown or wrong-case tokens, ambiguous vendor syntax, unsafe nesting, and missing recipient mappings keep passed and readyForPreview false. Campaigns inherit advertiser trackers dynamically. Add a campaign-local tracker or disable one inherited default with save_campaign:
Request include: ["tracking"] when reading a campaign. Its tracking field contains the local trackers, inherited overrides, and effectiveTrackers with sourceScope, inherited, effectiveEnabled, and statusReason. Within tracking, each supplied array is the complete desired array for that scope; omit an array to leave that part unchanged.

Seller tools

Seller search and get kinds include the Seller singleton, inventory sources, material, RFPs and RFP turns, library requests, coverage, wholesale products, playbook, business rules, media buys, work items, signals, asks, organization-owned Agents, and other kinds advertised by the live schema. Follow the Seller workflows page for sequencing.

Inventory-source recorded health evidence

Read one source with get({"kind":"inventory_source","id":"SOURCE_ID", "include":["diagnostics"]}). Its compact health projection preserves the recorded lastErrorAt, lastSuccessAt, and lastCheckedAt values from source diagnostics, including null. These are recorded observations, not a new provider probe made by get: when the check timestamp is null, freshness is unavailable rather than assumed to be now. A later recorded success makes an earlier recorded error historical evidence; it does not erase the original evidence or prove a transaction succeeded. A successful health observation is not proof that a media buy was accepted or delivered. To request a new no-spend check, use the existing refresh_inventory_source_health flow. search({"kind":"library_request"}) and get({"kind":"library_request","id":"LIBRARY_REQUEST_ID"}) are gated by the sales-library rollout. Search accepts filter.status (open or closed) and filter.originRfpTurnId; get returns the request’s gap, status, origin turn, and closing Material evidence.

Organization-owned Agents

search({"kind":"agent"}) lists the software Agents the active account’s organization owns. Add query to match an Agent ID, Agent display name, or operator display name. This explicit-only kind does not appear in unrelated broad object searches and does not turn a buyer counterparty Storefront into an Agent. Read one result with get({"kind":"agent","id":"AGENT_ID"}). Add include:["certification"] for the complete customer-safe certification summary, include:["validationRuns"] for up to five server-bound run summaries, or include:["diagnostics"] for observed implementation health and the current production-revision label. A returned run’s diagnosticRead shows the exact follow-up: pass its validationRunId with include:["diagnostics"] to receive the bounded Activity graph. Sales Agent detail also names its immutable versioned public validationSkill. These reads intentionally omit credentials, private evidence, operator identifiers, relationship identities, and owner-only action URLs. Interactive seller Pages that run inside a host use the account-resolved v3 surface for these reads and writes. In Apostra’s hosted bridge, the nested /mcp/:toolName proxy accepts surface: "v3" and forwards the call to /mcp/v3; callers still use the tool’s canonical arguments, not REST paths. The Seller RFP lifecycle guide defines the save_rfp action union, polling states, complete response requirements, and clarification/failure behavior. The RFP performance guide defines aggregate metric formulas, valid dimensions, disclosure thresholds, and immutable pagination. Individual RFP facts remain on get({ kind: "rfp" }). Material is evidence and teaching input. It does not replace inventory sources, products, pricing, Playbook instructions, business rules, signals, formats, or Seller identity. Use get({ "kind": "material", "id": "MATERIAL_ID" }) to inspect the current source revision, ingestion state, diagnostics, typed candidates, confirmation receipts, bounded rendition summaries, and source artifact references. Explicit includes expose independently paginated rendition blocks, reusable visual assets, extraction diagnostics, composition receipts, and expiring source access; originals and derived binaries are never embedded in the response. Confirm a candidate by calling the exact proposedMutation.tool with its arguments. Material prose is cited directly and is not a candidate to accept, reject, or correct. Source records may carry supplied original and derived structure references such as page or slide order, geometry, table/image/chart assets, OCR/caption artifacts, digests, provenance, confidentiality, and reuse rights. Completeness is reported per extraction facet, and unsupported or degraded parsing is never presented as ready. See Seller workflows for upload finalization, selection, retention, and access behavior.

Material search filters

search({"kind":"material"}) can narrow Library browse rows before paging. The returned total, nextCursor, and hasMore describe that filtered result set, not the unfiltered Library.

MCP App and resource-bound tools

These tools support interactive Pages or resources. A compliant host may keep some of them out of model context and invoke them only through the associated app contract. The Product Marketing Page receives a session-bound capability from open_page. It uses the host-only teach_search_materials, teach_get_material, teach_save_material, and teach_renew_capability aliases for Material listing and exact reads, seller-private URL and file additions, upload finalization, Material-owned candidate decisions, and capability renewal. MCP clients continue to use the public search, get, and save_material tools. The bridge does not permit generic Material writes or return source-access or visual-preview URLs. Expiring upload credentials are returned only for the transient browser PUT step. An active, unexpired Demo can also run the fixed synthetic evaluation from the Product Marketing Page. These app-only tools require the same session-bound Teach capability and cannot be invoked as general chat actions: All results are rehearsal-only. Synthetic rate-card figures are planning examples, not live prices or buyer-quotable rates. Custom fulfilment requires a named human or Connect follow-up; the evaluation does not make it automated or available now. See Controlled synthetic evaluation for the full seller workflow. The Library Page uses the host-only library_* aliases the same way, including library_get_rfp_turn and library_open_proposal_pass for opening the authorised Proposal Pass of a response from the Usage view; see MCP Apps for the full list. The Buyer Page companion tools in this table are app-only and are not part of the model-visible buyer catalog. Current Media Partners Page bundles use public V3 search, get, save_connection, and save_ask; the two closed Media Partners Page facades remain only for a rolling deploy window. Current Campaigns bundles instead use the resource-bound app-only buyer_campaigns_page_search and buyer_campaigns_page_get tools, backed by canonical V3 projections; Advertisers uses its closed landing-projection read. None of these Pages receives the generic api_call surface. Except for download_discovery_printable_card, the Listing and ChatGPT destination tools above are shared between the model-visible seller catalog and the Listing page (see Seller Pages). They use the same account, administrator, and confirmation checks in both paths. The printable listing tool is app-only because it returns PDF bytes for a browser download; print fulfillment and provider orders remain outside the tool.

Core read examples

List objects:
Read one object with related detail:
Campaign proposals, recentActivity, deliverySummary, and sourceIdentity are accepted include names so the API can return a precise unavailableIncludes explanation, but they are not populated in the preview. Search documentation:
Read a selected documentation result by its returned document path:
Arbitrary web URLs and internal repository paths are not readable through the documentation search contract.

Read structured results

Every tool returns MCP content for a person or model to read. Integrations should make decisions from structuredContent. tools/list is authoritative for inputs and publishes an output schema where the current tool declares one; the patterns below cover the remaining preview tools. Do not parse IDs, revisions, actions, or recovery guidance out of rendered prose. A write may return action: "unchanged"; that is a successful idempotent outcome. On partial work, preserve partialWrite, warnings, errors, per-seller or per-source outcomes, and any returned current revision. request_proposals.status is normally running while durable background work contacts the cohort; retry that same key until it becomes complete, partial, or failed. A buyer can have only one running proposal execution across campaigns. Terminal result cursors can continue a product-heavy outcome for the same seller, and detailsTruncated marks byte-bounded product fields. Each terminal seller result has one safe outcome: products returned, no matching products, no inventory for the request, a skipped seller, or a failed seller. no_inventory_for_request is a completed seller choice, not a failure or retry instruction. skipped with reasonCode advertiser_activation means the seller is off for the campaign’s advertiser and was not contacted; the seller’s error.message says where to turn it on. When neither sellerIds nor a campaign seller list applies, request_proposals contacts every eligible seller, including ones that are off for the advertiser; each of those reports this outcome. propertyCoverage counts the property domains present in returned products. When a seller reports filter accounting or unfinished work, filterResult distinguishes no match, filtered-out inventory, and partial results without exposing seller operational detail. When a seller cannot execute an exact age range, its proposal remains available as a counter-pitch and its semantic document names the limitation and the seller’s supported ranges. A direct get_products response carries the same information in its additive limitations array: code, buyer-readable message, requested min/max/include_unknown, and supported_ranges.

AI-8599 bounded targeting continuation (cursor protocol v1)

Capability details can exceed a model-safe response page. V3 therefore exposes two signed, opaque continuation fields; they are discovery/readback only and do not contact a seller or start a new proposal round. Cursors are account- and snapshot-bound. A forged, stale, or wrong-scope cursor returns a validation error; a Proposal’s expiry remains explicit in its read-only state and never gives a cursor execution authority. Follow a continuation only while the response supplies its next cursor. A terminal page has complete: true and no next cursor. A capability marked unavailable or blocked is not complete actionable targeting: demographic selections remain rejected before seller dispatch while the application seller transport remains on AdCP 3.1. If a signed continuation cannot fit its approved field limit, V3 omits the token and marks the capability unavailable rather than truncating its scope or presenting omitted details as complete. For example, a successful advertiser creation returns the new identity under the noun rather than as an unlabelled string:
request_proposals returns enough state to continue each seller branch:
These examples show the continuation fields, not every property. Treat the live output schema as authoritative as the preview evolves. Follow every page.nextCursor before treating the review as complete.

Errors and result handling

Tool errors use bounded structured codes and explanatory text. Common classes include validation errors, access denial, not found, revision conflict, unsupported preview behavior, external failure, and partial results.
  • Treat isError: true as a failed tool result even when the MCP transport itself returned successfully.
  • Preserve structured IDs and recovery fields; do not parse prose to recover an identifier already present in structuredContent.
  • A partial result is not an empty result. Read its warnings, coverage, and per-source or per-seller outcomes.
  • Re-read after any response that reports a partial write.
For transport-wide behavior, see Errors, Rate Limits, and Preview limitations.