Skip to main content

Overview

A Campaign is a single coherent media plan owned by an Advertiser. The read-only mode field projects the campaign’s configuration as discovery, performance, or directed. Managed campaigns define flight dates, budget, optimization goals, audiences, creative requirements, and (after execution) the resulting media buys with publishers. A tracked campaign is instead a read-only projection of one storefront-managed media buy. Its directed mode is a deprecated compatibility value; use management: "tracked" to identify the projection. In v2, the campaign is the focal object. For platform-managed campaigns, discovery sessions, products, creatives, audiences, frequency caps, and pacing schedules attach to it, and execution turns selected products into one or more media buys per channel group, sales agent, and settlement currency. Tracked campaigns do not execute: subscription projects one existing upstream campaign into one campaign shell and one media buy. An external AdCP client calling an Apostra-hosted storefront creates a directed buyer campaign when the Seller’s Listing + Distribution package is active: the task addresses one storefront and the storefront manages execution. Apostra anchors the contract, governance, durable journal, and dual-key media-buy identity before dispatch, then records accepted commitments and delivery on its ledger. The commercial envelope does not add a cross-storefront management layer. See How Apostra uses AdCP.

Campaign history and compounding intelligence

Every campaign contributes to its advertiser’s history from the moment it is created. The durable record includes the brief, selected products and media buys, decisions made through Interchange, and the delivery or performance results received for the campaign. An authorized agent can use that advertiser-scoped history when planning later campaigns. A campaign does not need conversion or other external measurement data to add to this history. Its brief and decisions still remain available. Reporting and measurement results join the same record as they arrive; Interchange does not invent outcome metrics before a source reports them.
routingType is legacy billing compatibility metadata, not a campaign or storefront model. It is a lossy observation derived from BillingParty for old rate-card and ledger consumers. You do not set it. Campaign mode, bring-your-own buyer agent, protocol endpoint, execution path, optimization, credentials, targeting, and settlement are independent concerns and must not be inferred from this field.

Choosing a composition path

Composition ownership — who picks the inventory — is controlled by autonomy.inventorySelection (manual | propose | automatic), not by optimization goals. A campaign with optimization goals (performanceConfig) can still be manually composed.
  • manual: the buyer picks sellers and products directly. This is the default.
  • propose: the agent drafts a selection and waits for approval.
  • automatic: the platform optimizer (auto_select_products) owns product selection. Manual create_media_buys calls are rejected on these campaigns.
The read-only mode field reflects the campaign’s configuration as a v2 read projection: discovery (no goals, no tracked buy), performance (optimization goals present), or directed (tracked buy). Mode is not a write-side gate. Setting optimization goals on an existing campaign is allowed regardless of its current mode projection. After the backfill (AI-8988), mode: 'performance' means “this campaign has goals”; it no longer means “the platform composes this campaign” — composition is controlled by autonomy.inventorySelection. Naming one storefront or ad platform does not determine composition path. If the request is “buy TikTok” or “buy Meta,” first clarify which system should remain the campaign source of truth:
  • A tracked projection returns mode: “directed” when the provider remains authoritative for a single one-to-one media buy. It is tracked and read-only; create the upstream campaign in the connected provider account.
  • Use autonomy.inventorySelection: “manual” (the default) when the buyer explicitly selects products. A discovery search may be scoped to one named storefront. After selection and confirmation, launch it with execute_campaign.
  • Use autonomy.inventorySelection: “automatic” when Apostra should allocate across sellers toward an objective. Set performanceConfig to provide optimization goals for the optimizer.
To retire an existing performance configuration, send performanceConfig: null.

Goal configuration in V3

In V3, save_campaign.optimizationGoals is opt-in. Send an array only when the buyer explicitly states the goal; a metric word in a campaign name or brief is a question to ask, not a goal to set. On an update, omit optimizationGoals to keep the current goals, or send optimizationGoals: null to clear them and return the campaign to discovery. Campaigns on connected provider accounts documents the read-only subscribe, mirror, refresh, and delivery lifecycle.

Key fields

labels is the Buyer Account’s optional dimension-keyed classification for the campaign. It is managed through the V3 MCP save_campaign tool and read through V3 get and search; see Dimensions and labels.

Tracker inheritance

Campaign tracking configuration is part of the campaign rather than a separate top-level resource. Read the resolved configuration with GET /api/v2/buyer/campaigns/:campaignId/tracking-config and replace the campaign-local portion with PUT on the same path. Each campaign resolves the advertiser’s current tracker defaults, then applies its own custom_tracker_urls and tracker_overrides. The response exposes the deterministic effective_trackers list, including each tracker’s source_scope, inherited state, effective enabled state, and status reason. It also returns advertiser_revision, campaign_revision, and an effective_revision spanning both inputs. Advertiser trackers are referenced by stable tracker_id; they are not copied into every campaign. To avoid overwriting a concurrent edit, send the last-read campaign_revision as expected_revision on campaign updates. A stale value returns 409 CONFLICT. Disabling an inherited tracker creates a campaign override and does not modify the advertiser default. If that advertiser tracker is later removed, its override remains visible in orphaned_override_ids rather than attaching to another tracker. Tracker imports preserve both the vendor’s exact raw_url and the canonical AdCP-macro url. See Tracker defaults and campaign inheritance for macro mapping, publishability, and V3 tracking field behavior.

Platform-managed lifecycle

The steps below apply to managed discovery and performance campaigns. A tracked campaign has no DRAFT planning or execute step. Its mirror can be refreshed read-only. See Campaigns on connected provider accounts for its connect → map → subscribe → mirror lifecycle. An inbound external-AdCP campaign is also platform-managed, but the external client performs the AdCP media-buy lifecycle directly; it does not separately call the buyer API’s execute_campaign. Its alpha create is anchored before seller dispatch. Supported update and cancellation operations use the same governed campaign spine.
1

DRAFT — plan and configure

Create the campaign with flightDates, budget, and optional brief/constraints/channelGroups. Attach a discovery session, select products, attach audiences, configure pacing periods. When channel groups are defined, assign every selected product to one group.
2

Upload creatives

Use GET /api/v2/buyer/campaigns/:campaignId/creatives/templates to see required formats, then upload manifest-based creatives via POST /api/v2/buyer/campaigns/:campaignId/creatives/create (multipart). Listing manifests is GET /api/v2/buyer/campaigns/:campaignId/creativeManifest. Inspect creativeFormats.missing to confirm coverage.
3

Execute → ACTIVE

POST /api/v2/buyer/campaigns/:id/execute launches a DRAFT (or COMPLETED) campaign — it groups selected products by channel group, sales agent, and settlement currency, packages them per pacing period, and submits the resulting media buys to AdCP. The campaign transitions DRAFT → ACTIVE. If submission fails and a media buy remains DRAFT, retrying execute submits that same buy without rebuilding it from discovery, so corrected per-buy settings remain intact.
4

PAUSE / resume

POST /api/v2/buyer/campaigns/:id/pause halts spend across all media buys. To resume a PAUSED campaign, call POST /api/v2/buyer/campaigns/:id/reactivate — this is a separate endpoint from execute and is the only way to bring a paused campaign back to ACTIVE.
5

COMPLETED

Reaching flightDates.endDate or full delivery transitions the campaign to COMPLETED.

Common operations

Create a campaign

This operation creates a managed campaign. To track an upstream campaign, create it in the connected provider account and subscribe to that account’s read-only mirror.

List / get campaigns

Add mode=discovery, mode=performance, or mode=directed to filter the shared campaign list without splitting reporting into separate resources. The list defaults to the live working set — every non-terminal status (ACTIVE, DRAFT, PAUSED) across both management states, so anything that could still spend shows up by default while terminal history does not. Pass explicit statuses or status=ALL to reach completed, canceled, or archived campaigns; add management=managed or management=tracked to narrow by who operates the campaign. Tracked scale belongs to the provider-account relationship rollup, not the ambient list: a subscribed account may mirror thousands of historical campaigns. LIST returns each campaign in a compact summary shape (identity, mode, status, flight dates, flattened budget, and product count). Directed summaries also carry provider and mirror freshness. Use GET /campaigns/{id} for the full mode-specific resource. Platform-managed details include brief, audiences, creative format coverage, budget allocation, pacing, performance config, and frequency caps; directed details project the single upstream media buy and its packages.

Auto-select products

With a discovery session, let Apostra pick a balanced product set from eligible inventory:
Iterate with ADCP-style refinement:

Execute / pause / reactivate

The execute response reports success, previousStatus, newStatus, and structured errors[] when individual media buy submissions fail. Pass debug: true to surface the raw ADCP request/response payloads in error details.
execute and reactivate are distinct endpoints. execute launches a DRAFT or COMPLETED campaign for the first time (or relaunches it). reactivate is the only way to bring a PAUSED campaign back to ACTIVE.

Update a running campaign

PUT /api/v2/buyer/campaigns/:id accepts partial updates. The mediaBuys[] array on update lets you act on individual media buys in one call:
Always confirm optimizationGoals with the buyer before changing them — silent goal changes break optimization continuity.
When the update payload includes pacingPeriods, the response also contains a pacingCascadeResult block summarizing the per-media-buy outcome of pushing appended periods to live media buys. See the Pacing Periods guide for the response shape, append-only rules, and unsupported-agent fallback.

Live ADCP status

This polls the sales agents directly and persists any status changes. Useful right after execution while waiting on publisher approvals.

Media buys and packages

Media buys can be staged directly as DRAFT resources before campaign execution through the v3 save_media_buy tool. Campaign execution also reconciles the campaign cart and submits its drafts. Each media buy represents one AdCP transaction with one sales agent, one settlement currency, and, when configured, one channel group. You read their status and tune them via the campaign update endpoint. Hierarchy: Campaign → Media Buy (one per channel group × sales agent × settlement currency) → Package (one per product per pacing period) → Delivery.

Media buy status

status is one of DRAFT, PENDING_APPROVAL, INPUT_REQUIRED, ACTIVE, PAUSED, COMPLETED, CANCELED, FAILED, REJECTED, ARCHIVED. For multi-agent campaigns, the surfaced status is the most restrictive across agents — e.g. one ACTIVE and one PENDING_APPROVAL reports as PENDING_APPROVAL. INPUT_REQUIRED indicates a seller needs additional information before approval can proceed. Status updates flow via:
  • Webhooks from sales agents (preferred — near-real-time)
  • Polling via GET /api/v2/buyer/campaigns/:campaignId/media-buy-status, which queries each agent and persists changes

Optimization goals at the media-buy level

Goals are applied to every package in a media buy at execution time. Each goal is either event-based or metric-based:
Supported metrics: clicks, views, completed_views, viewed_seconds, attention_seconds, attention_score, engagements, follows, saves, profile_visits, reach. For a reach or CPM-style goal, target metric: "reach" with a required reach_unit (individuals, households, devices, accounts, cookies, or custom) and an optional target_frequency — see Optimization goals for the full shape.

Meta Instant Form conversion leads

When product discovery on a connected Meta account returns Meta Leads — Instant Form Conversion Leads, you can optimize for people who are more likely to become qualified leads. This matches Meta Ads Manager’s Maximize number of conversion leads performance goal. Select an authorized Facebook Page and one of its active Instant Forms. The product binds the event source meta:instant_forms and event type qualify_lead; a messaging, website, or other event source is not a compatible substitute. Create and update requests fail before changing the Meta campaign if the selected Page or exact Form is no longer eligible. Delivery reporting uses Meta’s conversion_leads result as ext.meta.qualified_leads. It is authoritative for this provider-qualified signal, including an explicitly reported zero; it does not populate the generic leads or conversions fields. Connecting CRM feedback through Meta’s Conversions API is recommended because it gives Meta more information about which leads become qualified, but Meta does not enable that integration automatically and it is not required to create the media buy. Existing campaign setup and optimization behavior, and the standard Instant Form leads product, are unchanged.

Packages

When a media buy executes, products turn into packages — one per product, multiplied by the number of pacing periods. Each package carries its own budget, pacing strategy (even, asap, front_loaded), bid price, flight start/end dates, and optimization goals. Delivery metrics roll up per-package: impressions, spend, clicks — these are reported rollups, not valid optimization_goals.metric targets.

Updating media buys via the campaign update endpoint

Use PUT /api/v2/buyer/campaigns/:id with the mediaBuys[] array. Allowed actions: update (default — modify), cancel (cancel running), delete (archive).

Campaign membership: which creatives run on the campaign

A creative that is a member of a campaign is delivered on every current and future media buy of that campaign whose products accept its format. Membership is one relationship with three ways to set it, all writing the same edge and running the same seller sync:
  • save_campaign with campaignId and creativeIds: the full set of creatives on the campaign. A creative missing from the list is detached, a new one attached. Omit the field to leave membership unchanged; it is not accepted on create.
  • save_creative with creativeId, advertiserId, and campaignIds: the same edge from the creative’s side.
  • The Creative Library picker under a creative’s Used in section, which shows the seller-acceptance check per campaign before you choose.
Every creative must belong to the campaign’s advertiser. When the campaign has media buys and no seller’s products accept a creative’s format, the save fails and names those sellers (the first five, with a count of the rest); a campaign with no media buys yet accepts any complete creative, while a media buy whose products declare no accepted formats cannot be checked, so the save fails closed and names it. A creative live on an active or paused media buy cannot be detached until that buy ends. Every way of changing membership, including the Library picker and the singular save_creative association, shares one lock protocol, so concurrent membership writes to the same creative or campaign do not overlap: a write that finds another one in progress, or whose target set changed while it was being prepared, returns CONFLICT and should be retried with the full set. Detaches run before attaches; if one edge fails, the response carries partialWrite: true with the attached and detached ids that already applied. Read membership back with get on the campaign including creatives, or membership.campaignIds on the creative. Per-buy selection is a separate, narrower control: mediaBuys[].creative_ids below overrides the campaign membership for one media buy.

Creative attachment to a media buy

Omitting creative_ids leaves a media buy’s existing creative assignments unchanged. Pass creative_ids on a mediaBuys[] entry only when you intend to replace or clear its assignments.
Replace creatives for one media buy
  • Omit creative_ids to leave existing assignments unchanged; no creative data is fetched, validated, synced, or forwarded.
  • "creative_ids": [] explicitly clears all creatives from the media buy.
  • Each ID must already be linked to the campaign and match a format accepted by the media buy’s products. Otherwise the update fails with a validation error — the field is not silently filtered.
  • creative_ids is only valid with action: "update". It is rejected with cancel or delete.

Cascade behavior

  • Campaign pause halts every media buy and package
  • Campaign reactivate brings them back to ACTIVE (post-flight if dates allow)
  • Creative manifest update re-syncs to every media buy that uses the format
  • Property list update propagates to packages without a full re-execute
  • Frequency caps on the campaign or advertiser are enforced across all media buys

Failures and debug

When a media buy fails to execute (publisher rejection, ADCP error), POST /api/v2/buyer/campaigns/:id/execute returns structured errors[]:
Pass debug: true on execute to include the full ADCP request, response, and A2A debug logs in each error entry.

Pacing periods

pacingPeriods defines time-windowed spend intensity within the campaign flight. Two modes:
  • weight — relative weights (e.g. 3.0 = 3x normal); budget is distributed proportionally
  • budget — explicit dollar amount per period
Gaps between periods are treated as pauses (no spend). On execute, each product becomes one package per period with proportional budget.
Pacing periods can only be modified on DRAFT campaigns. After execution, the package split is locked in. See the pacing periods workflow guide for full mode-by-mode examples.

Frequency caps

Buyer-side frequency caps configured on the campaign are enforced by Apostra across all publishers in the campaign — distinct from publisher-side caps that live in package overlays.
When frequencyCaps is provided on update, it replaces all existing non-archived caps. See Frequency caps for the full configuration surface.

Click-URL parameters

A campaign can add or override the query parameters Apostra appends to a creative’s click-through URL at seller sync. The field has two shapes:
  • Write (create and update request bodies): an object, params plus deleteMissing.
  • Read (the campaign resource): an array of resolved entries, each { paramKey, paramValue, source }.
Send the write shape on create or update:
deleteMissing: false (the default) adds or updates the given keys and keeps the campaign’s other keys; deleteMissing: true replaces the campaign’s whole set. A campaign carries at most 50 keys: a request that would leave more is rejected, so remove keys or replace the set. Reads return the resolved array, for example:
A campaign key wins over the advertiser key with the same name. V3 save_campaign writes the same rows through tracking.macroAdditions, where a null value suppresses an inherited key. The inheritance, macro, and no-defaults rules are in the advertiser guide’s Click-URL parameters.

Routing compatibility metadata

DECISIONED / ROUTED is a temporary, server-derived billing observation on a media buy. It exists only for legacy rate-card and ledger compatibility and is derived from the authoritative AdCP BillingParty fact. It is never a client input and there is no campaign-level or storefront-level routing type. Do not use it to infer execution path, optimization, credentials, product selection, campaign mode, BYOA, AdCP connectivity, targeting support, or eligibility. Use the explicit field for each concern: seller capabilities, authentication state, campaign configuration, and BillingParty. The deprecated supportedRoutingTypes v2 storefront field returns both legacy values as a behavior-free compatibility placeholder.

Creative

Manifest-based creatives uploaded under the campaign

Pacing periods

Time-windowed spend intensity

Frequency caps

Buyer-side cross-publisher caps