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

# Package

> One product per pacing period beneath a media buy — carries its own budget, pacing strategy, bid price, and optimization goals

## Overview

A **Package** is the delivery unit beneath a [Media Buy](/v2/buyer/campaigns/media-buys): one package per product, multiplied by the number of pacing periods. Packages are created when a media buy executes — like media buys, they are not directly creatable. Each package carries its own budget, pacing strategy, bid price, and optimization goals, and reports its own delivery metrics.

<Note>
  Packages are spawned at execution. A product selected on a `DRAFT` campaign becomes one package per pacing period once the campaign executes and the media buy submits to ADCP.
</Note>

### Where a package sits in the hierarchy

```
Campaign
└── Media Buy        (one per sales agent)
    └── Package      (one per product per pacing period)
        └── Delivery (impressions, spend, clicks)
```

## Per-package fields

| Field | Type | Notes |
| - | - | - |
| `packageId` | string | Stable for the life of the package. Pass it when updating or pausing a single package. Opaque: see [Package identity and the id numbering scheme](#package-identity-and-the-id-numbering-scheme) before parsing it for anything |
| `budget` | number | Budget allocated to this package — gross (fee-inclusive), like every buyer budget. See [Budgets and fees](/v2/concepts/budgets-and-fees) |
| `pacing` | enum | Pacing strategy: `even`, `asap`, `front_loaded` |
| `bid price` | number | Per-package bid |
| `optimizationGoals` | array | Inherited from the media buy at execution (event- or metric-based) |

## Meta campaign budget allocation

Set `budgetAllocation.mode` to `seller_optimized` only when creating a new Meta media buy from one or more explicit qualified Meta product selections through `create_media_buys` or `save_media_buy`. All selected products must be on the same Meta storefront and resolve to the same effective channel group. An omitted `channelGroupId` resolves to the campaign's only group. Products in different channel groups need separate media buys. It requires an existing campaign with a saved budget total and no existing cart selections or draft media buy for that campaign. The Meta storefront must not be paused or require seller approval. Proposals, mixed storefronts, and non-Meta selections are refused before a draft changes. You cannot add or change `budgetAllocation` on an existing media buy.

When using `save_media_buy` to create a Meta buy from selected products, choose `budgetAllocation.mode` explicitly. Use `fixed` for a separate budget on each ad set, with a positive `products[].budget` on every selection. Use `seller_optimized` for one shared campaign budget, with an `optimization_goals` entry supported by every selected product. If the request does not say which budget level the buyer wants, ask before creating the buy. A `seller_optimized` `save_media_buy` request must omit both `products[].budget` and the media buy's top-level `budget`: Meta uses the campaign's saved total. Returned packages show no invented share of that total. A single Meta campaign can contain multiple budget-less ad sets; Meta distributes the total across them.

The v2 `create_media_buys` operation still accepts an omitted `budgetAllocation` for its existing per-package behaviour, including funding a package without a stated budget from the campaign remainder. A `seller_optimized` request on either operation must not include a package budget.

`seller_optimized` allocation snapshots the saved campaign total when the new Meta buy is created and sends that total through the canonical Meta route. A later campaign-budget edit does not change an existing buy. Existing media buys keep their original allocation behaviour. Storefronts that require seller approval are refused.

## Package identity and the id numbering scheme

Every package carries a `packageId` that is stable for the life of the package, and it is what you pass in `packageIds` or `packages[]` when you cancel or update one package (see [Cancel a single package](#cancel-a-single-package) below). Storefront-minted ids follow this shape:

```
sf_pkg_<storefrontMediaBuyId>_<N>
```

`N` is a **one-based position across the whole dispatch**, not a period number, and it is not stable across media buys. On a buy with 2 products over 3 pacing periods, `_1` through `_6` cover both products: the first three belong to one product, the next three to the other.

<Warning>
  Treat `packageId` as opaque. Never parse the trailing number to find a package's pacing period, and never assume the same suffix means the same period across two different media buys. Read `pacingPeriod.index` and `pacingPeriod.label` on the package itself instead.

  On the first product of a buy the suffix happens to line up with the period index, which makes a parsing shortcut look correct when you spot-check it. It is a coincidence of the ordering: on the second product the same suffix is off by the number of periods.
</Warning>

### Going from a description to a packageId

A buyer or their agent typically has a description, not an id: "the display package ending 2026-08-11." [Get media buy packages](/v2/buyer/campaigns/tasks/get-media-buy-packages) resolves that directly, no seller-side export needed. Match on `productName` plus `endTime` (or `pacingPeriod.label`), then take `packageId`:

```bash theme={null}
curl https://api.apostra.com/api/v2/buyer/media-buys/mb_ETBn4gJ9Wu/packages \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

```json theme={null}
{
  "mediaBuyId": "mb_ETBn4gJ9Wu",
  "isPaced": true,
  "packageCount": 2,
  "packages": [
    {
      "packageId": "sf_pkg_sf_mb_1783618937177_43ycbp7b_1",
      "productId": "prod_display_300x250",
      "productName": "Display Run of Site",
      "status": "active",
      "pacingPeriod": { "index": 1, "label": "Week 1" },
      "startTime": "2026-08-01T00:00:00Z",
      "endTime": "2026-08-07T23:59:59Z",
      "budget": 5000,
      "budgetCurrency": "USD",
      "pacing": "even"
    },
    {
      "packageId": "sf_pkg_sf_mb_1783618937177_43ycbp7b_2",
      "productId": "prod_display_300x250",
      "productName": "Display Run of Site",
      "status": "active",
      "pacingPeriod": { "index": 2, "label": "Week 2" },
      "startTime": "2026-08-08T00:00:00Z",
      "endTime": "2026-08-11T23:59:59Z",
      "budget": 5000,
      "budgetCurrency": "USD",
      "pacing": "even"
    }
  ]
}
```

Both packages are the same product, so `productName` alone does not pick one. The `endTime` does: the second entry ends `2026-08-11T23:59:59Z`, so its `packageId` (`sf_pkg_sf_mb_1783618937177_43ycbp7b_2`) is the one to send. Match on the field, not on the trailing number.

### Packages without a recorded flight window

`startTime`, `endTime`, and `pacingPeriod` are absent on packages created before the platform started retaining this identity, and on any media buy that was never split across periods. Expect this to be the common case on buys that are already live: no package created before the change carries a period, and most carry no window either. The platform now keeps the flight window it requested even when the seller's response omits it, but it cannot reconstruct one for a package already stored without it, so the gap is permanent for those packages.

The periods themselves are still readable on the campaign, including each period's label and dates. What is missing is only the link from a package to its period.

For those packages, `productName` plus `budget` sometimes narrows it to one. Often it will not: a schedule that gives several periods the same budget gives their packages identical budgets, so same-product siblings come back indistinguishable, and nothing this API returns separates them. Those packages cannot be addressed individually, and the seller is the only remaining route. Check for the presence of `startTime`/`endTime` before matching on them, and treat two identical entries as unresolved rather than picking one: pausing the wrong period is worse than asking.

### Pacing strategies

| Strategy | Behavior |
| - | - |
| `even` | Spread spend evenly across the package window |
| `asap` | Spend as fast as inventory allows |
| `front_loaded` | Weight spend toward the start of the window |

### Which pacing a package gets

Every package is sent to the seller with one pacing value, chosen in this order:

1. The pacing you set on the product line (`products[].pacing` on the media buy), when you set one.
2. Otherwise, the campaign's `budget.pacing`. `EVEN` is sent as `even`, `ASAP` as `asap`, and `FRONTLOADED` as `front_loaded`.
3. Otherwise, `even`, the default that AdCP defines for a budgeted buy.

A campaign created with `"budget": { "pacing": "EVEN" }` therefore sends `even` on every package unless a product line overrides it. A product line that sets `front_loaded` or `asap` sends that value to the seller unchanged.

The package's `pacing` field records the pacing the seller applies: the value sent, or `even` when the field was left off. If the seller returns a different pacing, the package shows the seller's value. It is absent when there is no record of what the seller was sent, for example on some packages created before this behavior.

**When the pacing field is left off.** AdCP treats a missing pacing as `even`, so leaving the field off never drops an `even` instruction. Apostra leaves it off in two cases:

* **Meta ad accounts.** The Meta integration does not accept a pacing field yet, so an `even` pacing is not sent to Meta, whether it came from the product line, the campaign or the default. Meta's standard delivery spreads spend evenly.
* **A seller Apostra cannot identify.** When a package is added to a live media buy and the seller behind it cannot be identified, `even` is left off in case that seller rejects the field. An `asap` or `front_loaded` pacing is still sent.

**Meta and an `ASAP` or `FRONTLOADED` campaign.** Meta cannot take these either. The media buy still launches and runs on Meta's standard even delivery; it is not refused. The execute response carries a `pacing_not_applied` warning for that media buy, naming each product, the pacing you asked for (`requestedPacing`), the pacing Meta applies (`appliedPacing: even`) and why. The package records `even`. If you set `asap` or `front_loaded` on a Meta product line yourself, Meta refuses the media buy with an unsupported-feature error instead, because that is an explicit instruction for that package.

Changing the campaign's `budget.pacing` after a media buy is live does not resend it to the seller. To change a live package, send its `pacing` in `packages[]` through [Update media buy](/v2/buyer/campaigns/tasks/update-media-buy).

## Delivery metrics

Delivery metrics roll up per package:

* `impressions`
* `spend` — gross (fee-inclusive), stated at the fee terms locked on the parent media buy, so it compares directly against the package's budget. Packages under a legacy media buy created before fee terms were locked report spend net, as the seller reported it
* `clicks`

## Meta property coverage

Meta Audience Network can deliver outside properties owned by Meta, and Meta
does not provide a complete publisher-property roster during product
discovery. Standard AdCP 3.1 Meta products therefore use an explicit, closed
set of the advertised Facebook and Instagram placements rather than allowing
Meta to expand delivery into undisclosed properties.

Eligible Meta products can retain Advantage+ Placements. Those products declare
`property_coverage.disclosure: partial`,
set `property_targeting_allowed: false`, and name the known Facebook and
Instagram properties without claiming they are exhaustive. The same trusted
contract is required again when the package is created; a candidate product ID
cannot be submitted through a legacy call. Explicit Facebook or Instagram
placement selections remain complete and support placement-level delivery
reporting.

Meta's integrated Advantage+ automation is available for website sales products
(conversion, conversion-value, and catalog) and every executable lead
destination (Instant Form, website, Messenger, Instagram Direct, WhatsApp,
and calls). Meta's current Ads Manager no longer offers manual and Advantage+
as separate setups for either family — Advantage+ is now the automation state
of one product. `ext.scope3_meta_automation` discloses `seller_optimized`
campaign-budget and bidding allocation and Advantage+ audience expansion from
the buyer's included provider-account and buyer-synced audiences. Included
audiences are optimization suggestions to Meta, not a hard delivery boundary —
Meta may expand delivery to reach people outside them. Country targeting and
custom-audience exclusions remain hard controls that Meta does not expand past.
Meta products without this automation, and all legacy or manual products, keep
audience expansion disabled by default.

A Meta package read on AdCP 3.1 carries no `budget` when Meta holds the
budget on the campaign (Advantage campaign budget) or on the ad set as a daily
budget. In that case the package's `ext.scope3_meta_budget` states where the
budget is held and any daily amount, spend cap, or minimum spend target. See
[Reading campaign budgets and daily budgets](/v2/guides/connecting-ad-platforms#reading-campaign-budgets-and-daily-budgets).

## How pacing periods create packages

Each product becomes one package per pacing period. The campaign's `pacingPeriods` defines time-windowed spend intensity within the flight, in one of two modes:

* **`weight`** — relative weights (e.g. `3.0` = 3x normal); budget is distributed proportionally across periods.
* **`budget`** — an explicit dollar amount per period.

On execute, each product is split into one package per period, with budget set proportionally (weight mode) or explicitly (budget mode). **Gaps between periods are treated as pauses** — no spend, and no package covers the gap.

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

### Example: three periods → three packages per product

```json theme={null}
{
  "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 }
    ]
  }
}
```

With this configuration, every selected product produces three packages — one per period — each weighted for proportional budget.

## Cancel a single package

To cancel one package without touching the rest of its media buy, pass `packageIds` on a `mediaBuys[]` entry in `PUT /api/v2/buyer/campaigns/:id`. Resolve the `packageId` first with [Get media buy packages](/v2/buyer/campaigns/tasks/get-media-buy-packages):

```json theme={null}
{
  "mediaBuys": [
    {
      "action": "cancel",
      "mediaBuyId": "mb_abc123",
      "packageIds": ["sf_pkg_sf_mb_1783618937177_43ycbp7b_2"],
      "reason": "format not supported on this device mix"
    }
  ]
}
```

You can also update a package's budget and pacing in place by passing it in the `packages[]` array with `action: "update"`:

```json theme={null}
{
  "mediaBuys": [
    {
      "action": "update",
      "mediaBuyId": "mb_abc123",
      "packages": [
        { "packageId": "sf_pkg_sf_mb_1783618937177_43ycbp7b_1", "budget": 15000, "pacing": "even" }
      ]
    }
  ]
}
```

## Related concepts

<CardGroup cols={2}>
  <Card title="Get media buy packages" href="/v2/buyer/campaigns/tasks/get-media-buy-packages" icon="boxes-stacked">
    Resolve a description ("the display package ending 2026-08-11") to a packageId
  </Card>

  <Card title="Media Buy" href="/v2/buyer/campaigns/media-buys" icon="receipt">
    The parent ADCP transaction that spawns packages
  </Card>

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

  <Card title="Campaign" href="/v2/object-guides/campaign" icon="rocket">
    The parent media plan
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.