> ## Documentation Index
> Fetch the complete documentation index at: https://docs.interchange.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Campaign

> The unit of media planning — flight dates, budget, products, audiences, creatives, and pacing

## Overview

A **Campaign** is a single coherent media plan owned by an [Advertiser](/v2/object-guides/advertiser). Every campaign carries a `mode`: `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 connected-account 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 sales agent. Tracked connected-account campaigns do not execute:
subscription projects one existing upstream campaign into one campaign shell
and one media buy.

An external AdCP client calling an Interchange-hosted storefront creates a directed
buyer campaign when that alpha is enabled: the task addresses one storefront and the
storefront manages execution. Interchange 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 Interchange uses AdCP](/v2/concepts/adcp-on-the-buy-side#external-buyer-to-interchange-to-seller-alpha).

<Note>
  **`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.
</Note>

## Choosing a mode for a named storefront

Naming one storefront or ad platform does not determine campaign mode by
itself. If the request is simply “buy TikTok” or “buy Meta,” first clarify which
system should remain the campaign source of truth:

* A connected-account projection returns `mode: "directed"` when the seller or
  ad platform remains authoritative for a single one-to-one media buy. It is
  tracked and read-only; create the upstream campaign in the seller platform.
* Choose `discovery` when Interchange manages product selection from a buyer
  brief. A discovery search may be scoped to one named storefront and still
  remain platform-managed. After selection and confirmation, launch it with
  `execute_campaign`.
* Choose `performance` when Interchange allocates across sellers toward an
  objective. `performanceConfig` is required for this mode and is not accepted
  in discovery mode.

Campaign mode is fixed for normal updates. Adding `performanceConfig` to an
existing discovery campaign is rejected rather than converting it to
performance mode. To repair or retire an existing performance configuration,
send `performanceConfig: null`; clearing it returns the campaign to discovery
mode.

[Campaigns on connected seller accounts](/v2/buyer/campaigns/directed-campaigns)
documents the read-only subscribe, mirror, refresh, and delivery lifecycle.

## Key fields

| Field                   | Type               | Notes                                                                                                                                                                                                     |
| ----------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `campaignId`            | string             | Stable campaign identifier                                                                                                                                                                                |
| `mode`                  | enum               | `discovery`, `performance`, or `directed` (the `directed` value is a deprecated compatibility value — read `management` for what the platform does with the campaign)                                     |
| `management`            | enum               | `tracked` (mirrored from a connected seller account the platform did not set up; read-only shell) or `managed` (uses the canonical Interchange buyer lifecycle)                                           |
| `advertiserId`          | string             | Owning advertiser                                                                                                                                                                                         |
| `name`                  | string             | Campaign name (max 255)                                                                                                                                                                                   |
| `status`                | enum               | `DRAFT`, `ACTIVE`, `PAUSED`, `COMPLETED`, `CANCELED`, `ARCHIVED`                                                                                                                                          |
| `flightDates`           | object             | `{ startDate, endDate }` ISO dates                                                                                                                                                                        |
| `budget`                | object             | `{ total, currency, dailyCap?, pacing? }`. Gross — fee-inclusive, like every buyer budget. See [Budgets and fees](/v2/concepts/budgets-and-fees)                                                          |
| `allocatedBudget`       | number             | Sum of active media buy budgets, plus delivered spend (all-in, at each buy's locked fee terms) on ended or archived media buys — gross, the same denomination as `budget.total`                           |
| `unallocatedBudget`     | number             | `budget.total - allocatedBudget` — room left for new media buys. Can go negative when delivered spend on ended buys exceeds `budget.total`                                                                |
| `pacingPeriods`         | object             | Optional time-windowed pacing schedule with weight or budget mode                                                                                                                                         |
| `constraints`           | object             | Channel filter plus AdCP-shaped targeting overlay (geo, language, device). Targeting fields flow into every media-buy package: include lists intersect with package-level targeting, exclude lists union. |
| `performanceConfig`     | object             | Optimization goals (event-based or metric-based)                                                                                                                                                          |
| `optimizationApplyMode` | `AUTO` \| `MANUAL` | Inherits advertiser default if unset                                                                                                                                                                      |
| `audiences`             | array              | Target and suppress audiences                                                                                                                                                                             |
| `mediaBuys`             | array              | Spawned at execution for platform-managed campaigns; exactly one mirrored upstream buy for a directed campaign                                                                                            |
| `creativeFormats`       | object             | `{ required, covered, missing }` — surfaces formats still needing creatives                                                                                                                               |
| `discoveryId`           | string             | Discovery session feeding product selection (DRAFT only)                                                                                                                                                  |
| `productCount`          | number             | Selected products (DRAFT only — after execute, products are inside media buys)                                                                                                                            |
| `frequencyCaps`         | array              | Buyer-side caps for this campaign                                                                                                                                                                         |

## Platform-managed lifecycle

The steps below apply to managed `discovery` and `performance` campaigns. A
tracked connected-account campaign has no DRAFT planning or execute step. Its
mirror can be refreshed read-only. See
[Campaigns on connected seller accounts (alpha)](/v2/buyer/campaigns/directed-campaigns)
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. Campaign-spine update and cancellation support are not yet part
of that alpha.

<Steps>
  <Step title="DRAFT — plan and configure">
    Create the campaign with `flightDates`, `budget`, and optional `brief`/`constraints`. Attach a discovery session, select products, attach audiences, configure pacing periods.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Execute → ACTIVE">
    `POST /api/v2/buyer/campaigns/:id/execute` launches a `DRAFT` (or `COMPLETED`) campaign — it creates one media buy per sales agent for each selected product, packages them per pacing period, and submits 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.
  </Step>

  <Step title="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`.
  </Step>

  <Step title="COMPLETED">
    Reaching `flightDates.endDate` or full delivery transitions the campaign to COMPLETED.
  </Step>
</Steps>

## Common operations

### Create a campaign

This operation creates a managed campaign. To track a seller-owned campaign,
create it in the connected platform and subscribe that account's read-only
mirror.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.interchange.io/api/v2/buyer/campaigns \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "advertiserId": 12345,
      "name": "Q2 2026 — Tech Launch",
      "flightDates": { "startDate": "2026-05-15T00:00:00Z", "endDate": "2026-07-15T23:59:59Z" },
      "budget": { "total": 100000, "currency": "USD", "pacing": "even" },
      "brief": "Premium video for tech-savvy professionals",
      "constraints": {
        "channels": ["ctv", "video"],
        "geo_countries": ["US", "CA"],
        "geo_metros": [{ "system": "nielsen_dma", "values": ["501", "803"] }],
        "language": ["en"]
      }
    }'
  ```

  ```json With pacing periods theme={null}
  {
    "advertiserId": 12345,
    "name": "Holiday Heavy-Up",
    "flightDates": { "startDate": "2026-11-01T00:00:00Z", "endDate": "2026-12-31T23:59:59Z" },
    "budget": { "total": 250000, "currency": "USD" },
    "pacingPeriods": {
      "mode": "weight",
      "periods": [
        { "label": "Pre-Black Friday", "start": "2026-11-01T00:00:00Z", "end": "2026-11-26T23:59:59Z", "weight": 1.0 },
        { "label": "Black Friday / Cyber Monday", "start": "2026-11-27T00:00:00Z", "end": "2026-12-02T23:59:59Z", "weight": 3.0 },
        { "label": "Holiday push", "start": "2026-12-03T00:00:00Z", "end": "2026-12-31T23:59:59Z", "weight": 1.5 }
      ]
    }
  }
  ```
</CodeGroup>

### List / get campaigns

```bash theme={null}
curl "https://api.interchange.io/api/v2/buyer/campaigns?advertiserId=12345&status=ACTIVE" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"

curl https://api.interchange.io/api/v2/buyer/campaigns/cmp_987654321 \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

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 connected-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 Scope3 pick a balanced product set from eligible inventory:

```bash theme={null}
curl -X POST https://api.interchange.io/api/v2/buyer/campaigns/cmp_987654321/auto-select-products \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "maxProducts": 8, "minBudgetPerProduct": 2500 }'
```

Iterate with ADCP-style refinement:

```json theme={null}
{
  "refine": [
    { "scope": "request", "ask": "more video, less display" },
    { "scope": "product", "id": "prod_xyz789", "action": "more_like_this" },
    { "scope": "product", "id": "prod_xyz790", "action": "omit" }
  ]
}
```

### Execute / pause / reactivate

```bash theme={null}
# Launch a DRAFT (or COMPLETED) campaign — turns selected products into media buys and submits to ADCP
curl -X POST https://api.interchange.io/api/v2/buyer/campaigns/cmp_987654321/execute \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "debug": false }'

# Pause every media buy on the campaign
curl -X POST https://api.interchange.io/api/v2/buyer/campaigns/cmp_987654321/pause \
  -H "Authorization: Bearer $SCOPE3_API_KEY"

# Resume a PAUSED campaign
curl -X POST https://api.interchange.io/api/v2/buyer/campaigns/cmp_987654321/reactivate \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

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.

<Note>
  `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`.
</Note>

### 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:

```json theme={null}
{
  "mediaBuys": [
    {
      "action": "update",
      "mediaBuyId": "mb_abc123",
      "packages": [{ "packageId": "pkg_1", "budget": 15000, "pacing": "asap" }],
      "optimizationGoals": [
        { "kind": "metric", "metric": "completed_views", "target": { "kind": "cost_per", "value": 0.08 } }
      ]
    },
    { "action": "cancel", "mediaBuyId": "mb_def456", "reason": "underperforming" }
  ]
}
```

<Warning>
  Always confirm `optimizationGoals` with the buyer before changing them — silent goal changes break optimization continuity.
</Warning>

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](/v2/guides/pacing-periods) for the response shape, append-only rules, and unsupported-agent fallback.

### Live ADCP status

```bash theme={null}
curl https://api.interchange.io/api/v2/buyer/campaigns/cmp_987654321/media-buy-status \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

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 are not a directly-creatable resource — they're spawned by the platform when a campaign executes. Each media buy represents one ADCP transaction with one sales agent. You read their status and tune them via the campaign update endpoint.

**Hierarchy**: Campaign → Media Buy (one per sales agent) → 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:

<CodeGroup>
  ```json Event goal — optimize for tracked conversions theme={null}
  {
    "kind": "event",
    "event_sources": [
      { "event_source_id": "website_pixel", "event_type": "purchase", "value_field": "value" }
    ],
    "target": { "kind": "cost_per", "value": 25.0 }
  }
  ```

  ```json Metric goal — optimize for seller-native delivery metrics theme={null}
  {
    "kind": "metric",
    "metric": "completed_views",
    "target": { "kind": "cost_per", "value": 0.08 }
  }
  ```
</CodeGroup>

| Target kind      | Meaning                                              |
| ---------------- | ---------------------------------------------------- |
| `cost_per`       | Target CPA (events) or CPM-like rate (metrics)       |
| `per_ad_spend`   | Target ROAS — value per dollar spent (events only)   |
| `maximize_value` | No target — maximize total event value within budget |
| `threshold_rate` | Maintain a minimum rate of the metric (metrics only) |

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](/v2/buyer/campaigns/media-buys#optimization-goals) for the full shape.

### 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).

<CodeGroup>
  ```json Update budget + optimization theme={null}
  {
    "mediaBuys": [
      {
        "action": "update",
        "mediaBuyId": "mb_abc123",
        "packages": [
          { "packageId": "pkg_1", "budget": 15000, "pacing": "even" }
        ],
        "optimizationGoals": [
          { "kind": "metric", "metric": "completed_views", "target": { "kind": "cost_per", "value": 0.08 } }
        ],
        "updated_reason": "Mid-flight optimization based on early performance"
      }
    ]
  }
  ```

  ```json Cancel one media buy theme={null}
  {
    "mediaBuys": [
      { "action": "cancel", "mediaBuyId": "mb_abc123", "reason": "underperforming vs threshold" }
    ]
  }
  ```

  ```json Cancel just one package theme={null}
  {
    "mediaBuys": [
      {
        "action": "cancel",
        "mediaBuyId": "mb_abc123",
        "packageIds": ["pkg_2"],
        "reason": "format not supported on this device mix"
      }
    ]
  }
  ```
</CodeGroup>

### 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.

```json Replace creatives for one media buy theme={null}
{
  "mediaBuys": [
    {
      "action": "update",
      "mediaBuyId": "mb_abc123",
      "creative_ids": ["cr_video_15s", "cr_video_30s"]
    }
  ]
}
```

* **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[]`:

```json theme={null}
{
  "success": false,
  "errors": [
    {
      "mediaBuyId": "mb_abc123",
      "salesAgentId": "agent_xyz",
      "message": "Sales agent returned 422: invalid format for product"
    }
  ]
}
```

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.

<Tip>
  Pacing periods can only be modified on DRAFT campaigns. After execution, the package split is locked in. See the [pacing periods workflow guide](/v2/guides/pacing-periods) for full mode-by-mode examples.
</Tip>

## Frequency caps

Buyer-side frequency caps configured on the campaign are enforced by Scope3 across **all** publishers in the campaign — distinct from publisher-side caps that live in package overlays.

```json theme={null}
"frequencyCaps": [
  { "max_impressions": 5, "window": { "interval": 7, "unit": "days" } }
]
```

When `frequencyCaps` is provided on update, it replaces all existing non-archived caps. See [Frequency caps](/v2/guides/frequency-caps) for the full configuration surface.

## 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.

## Related concepts

<CardGroup cols={2}>
  <Card title="Creative" href="/v2/object-guides/creative" icon="image">
    Manifest-based creatives uploaded under the campaign
  </Card>

  <Card title="Pacing periods" href="/v2/guides/pacing-periods" icon="calendar-week">
    Time-windowed spend intensity
  </Card>

  <Card title="Frequency caps" href="/v2/guides/frequency-caps" icon="gauge">
    Buyer-side cross-publisher caps
  </Card>
</CardGroup>
