# One agent. Every seller. Every channel. Source: https://docs.apostra.com/v2/buyer-introduction A technical introduction to building buyer agents on Apostra — for the engineers wiring the next twenty years of media buying. > Coming from the [Apostra overview](/v2/introduction)? This is the buyer-side deep dive — what you integrate, in the order you'll integrate it. > **[Quickstart](/v2/quickstart)** > > First call in five minutes — sandbox advertiser, MCP connector, REST curl. > **[skill.md](/v2/skill)** > > The single URL your agent reads at startup to learn the entire API. > **[MCP connector](/v2/authentication)** > > OAuth into Claude or ChatGPT and drive Apostra from any agent. ## You've integrated this market before. If you've shipped media-buying software in the last decade, you've written the same code five times. One adapter for each DSP. A separate SDK for CTV. Another for retail media. A scraper for the platform that still doesn't have an API. Every channel speaks a different language, and your integration backlog grows linearly with the market. That's not a protocol problem. That's the absence of one. ## The shift. Programmatic exposes a **valuation API**: bid on this impression, yes or no, in 100ms. Agentic exposes an **allocation API**: here is a brief, here are constraints, here is a budget — discover sellers, negotiate terms, execute, report. You stop integrating vendors. You start shipping a buyer agent. > Programmatic asks: what is this impression worth? > Agentic asks: how should I allocate this budget? > One is a bidder. The other is a portfolio manager. They are different programs. ## What you actually integrate. You integrate the **Apostra**. Three surfaces, one product: * **`skill.md`** — a single URL your agent reads at startup to learn the entire API. No SDK. No client generation. The agent reads the manual. * **MCP** — four tools (`health`, `ask_about_capability`, `describe_operation`, `api_call`) drive the full Apostra from any MCP-speaking model. No per-domain tool sprawl. * **REST + OpenAPI** — for CLIs, servers, and integrations that don't speak MCP. OAuth for agent connectors, API keys for everything else. Apostra speaks **AdCP**, the open protocol for agentic media buying. That's how one integration reaches every seller — CTV, podcasts, social, retail media, OOH, anything that registers a storefront. A new channel doesn't mean a new integration on your side. The seller registers; you reach them. AdCP moves the transaction. Apostra handles the layers the protocol can't — contracts, billing, multi-agent aggregation, governance. ## Where this leaves you. One buyer agent. Every seller on Apostra. One auth surface. One reporting shape. The integration cost of adding a new channel approaches zero — because you don't add it. The seller does, by registering a storefront. That's the change. The rest of this site explains how to ship it. > **[Buyer onboarding](/v2/setup/buyer-onboarding)** > > Connect, create advertisers, and launch your first campaign. > **[Buyer API reference](/v2/buyer-api-reference)** > > Every REST endpoint with request/response schemas. > **[Philosophy primer](/v2/concepts/philosophy)** > > The conceptual model behind storefronts, signals, and agent-first design. > **[AdCP spec](https://github.com/adcontextprotocol/adcp)** > > The open protocol Apostra speaks. Apache 2.0. # Market readiness preview Source: https://docs.apostra.com/v2/buyer/account/market-readiness Check pilot availability and public-safe supply evidence by country and channel. Market readiness answers separate questions before buyer setup: **is the product/support path ready for this ISO country × channel cell, has market/legal availability been assessed, and what public-safe supply applies?** It does not admit an account. Buyer signup and every authenticated buyer API remain behind the existing `alpha-opt-in` gate. A storefront appearing in the supply read also never opens a pilot cell; opening and closing are explicit, audited human decisions. Buyer Setup is a separate account-level process: an account administrator proves the buyer's stable Organization/operator identity, chooses a plan, accepts Terms, and then connects seller and platform accounts. Billing details (payer name, street address, and country) are required only for consolidated-billing routes; no card or credit line is required. Market readiness never creates, confirms, or locks an operator identity. See [Buyer setup and go-live](/v2/buyer/account/setup). ## Request readiness Call `POST /api/v2/market-readiness` with one to 50 cells. This endpoint is public and rate-limited. Country codes use assigned ISO 3166-1 alpha-2 values. Send country codes in uppercase. Supported canonical channels are `audio`, `ctv`, `display`, `dooh`, `social`, `meta`, and `google`; channel values are lowercase. `meta` and `google` are globally governed platform overlays. ```json theme={null} { "cells": [ { "countryCode": "FR", "channel": "display" }, { "countryCode": "ID", "channel": "social" } ] } ``` Each result includes: * `pilotState`: the effective `open` or `closed` decision; * `pilotScope`: `domestic`, `global_overlay`, or `none`; * `domesticSupply` and `globalSupply`, kept separate; * `signupGuidance`, which keeps product readiness, currently unassessed market/legal availability, and supply readiness separate and offers scoped next steps; * up to 10 public names from human-reviewed listed storefronts (while `storefrontCount` remains the full usable count); and * `sourceHealth`, which is `healthy`, `degraded`, or `unknown`. Only active sources from human-reviewed `LISTED` storefronts contribute to public status, counts, names, or health. Hidden storefronts cannot be inferred from a count, and pending or disabled sources are not treated as available supply. Global supply is an overlay, not a made-up country. An open global social path can therefore apply to Indonesia while Indonesia display remains closed. ```json theme={null} { "data": { "cells": [ { "countryCode": "ID", "channel": "social", "pilotState": "open", "pilotScope": "global_overlay", "domesticSupply": { "scope": "domestic", "status": "none", "storefrontCount": 0, "namedStorefronts": [], "sourceHealth": "unknown" }, "globalSupply": { "scope": "global", "status": "available", "storefrontCount": 1, "namedStorefronts": [ { "name": "Global Social Platform", "domain": "social.example" } ], "sourceHealth": "healthy" }, "signupGuidance": { "route": "proceed", "productReadiness": "ready", "marketAvailability": "not_assessed", "supplyReadiness": "available", "options": ["continue"] } } ] }, "error": null } ``` ## Which cells a storefront counts toward Each storefront counts toward a cell by the markets it advertises on its Discovery Card, so readiness and discovery agree: * A storefront whose seller has set its markets counts for the channels and countries the seller listed. A storefront marked worldwide counts as global supply. * A pass-through storefront whose seller has not confirmed its markets counts for the channels and countries it stored plus the channels and countries its connected sales agents declare. The seller's first save replaces that assumption with the values the seller chooses. * A channel or country list with no values is open. An open channel list counts toward every channel. An open country list counts toward every country and appears as global supply, so storefronts that have not set their markets can raise the supply shown for a cell. Readiness does not claim the storefront chose worldwide acceptance. ## Unknown is not empty If the supply query or source-health projection fails, the endpoint returns `status: "unknown"`, `storefrontCount: null`, and `sourceHealth: "unknown"`. It never turns a failed read into a claim that the market has no supply. `status: "none"` is returned only after the supply read succeeds and finds no usable source for that scope. A supply gap does not close signup. Its guidance includes continuing setup, joining the exact cell's waitlist, bringing supply, and browsing other cells. Only `route: "waitlist_product"` stops the requested product cell, and it does not create an Account-wide waitlist. When the signup experience records a waitlist choice, it preserves the exact product, country, channel, desired path, notification preference, and readiness reason shown here. Before identity proof, the record is tied to a one-way principal reference rather than an Account. Organization linkage is added only after a valid claim, and the interest is never used as authorization. Email notifications also retain the accepted notice key, version, locale, rendered text, and server-stamped consent time. Public interest remains ineligible for email delivery until principal verification. Claimed interests can be changed only through an authenticated Organization surface. # Account Source: https://docs.apostra.com/v2/buyer/account/overview Your current account context, the accounts under your organization, membership access, and notification preferences An **account** is the entity the authenticated user is operating as. Every buyer request runs in the context of one account, identified by a numeric `id` and carrying a `role` (`MEMBER`, `ADMIN`, or `SUPER_ADMIN`) that gates what the caller can do. A user can belong to several accounts; the account API lets you read the current context, list the accounts you can access, and switch your integration between them. ## Account model Full user-context responses returned by account creation and switching separate the product an account uses from its access and approval state: | Field | Meaning | | - | - | | `nodeKind` | `ACCOUNT` for a product workspace or `CONTAINER` for an organization that governs accounts. | | `accountType` | `BUYER` or `SELLER` for accounts, or `DEVELOPER` for a Developer account, which owns agents and has no Buyer or Seller tools. Some compatibility responses use `PARTNER` to project the organization's sales-agent offering; it is not a separate account. An organization container has no core account type. | | `customerRole` | Compatibility field for Buyer and Seller integrations. Use `accountType` for new Buyer and Seller reads. The sales-agent offering has no `customerRole` equivalent, and a Developer account's `customerRole` is a placeholder: read `accountType` to tell it apart from a Buyer account. | | `enabled` | Whether people and API clients can enter the account. It does not grant Buyer live-spend eligibility or Partner registration, certification, or commercial approval. | | `buyerAccessPosture` | Deprecated and never operation authority; use Buyer Setup capability verdicts. `customer.buyerAccessPosture` is always `null`. A Buyer's `accountProducts.core.buyerAccessPosture` is derived from those verdicts on every read: `LIVE_ELIGIBLE` when the Buyer can spend live, otherwise `SANDBOX_ONLY`. | | `registrationStatus` / `commercialStatus` | Independent states on an organization's sales-agent offering. Registration is `AVAILABLE` or `REGISTERED`; commercial status is `INACTIVE`, `ACTIVE`, `SUSPENDED`, or `EXITED`. A newly enabled offering starts `AVAILABLE` / `INACTIVE`, and registration or certification does not grant commercial approval. | Each account has its own registered `customerDomain` and membership settings. The MCP `customer_list` response also includes `accountType`. Developer accounts report `DEVELOPER` even when the legacy `customerRole` is `BUYER`; organization containers report a null account type. This classification comes from the registered account type, not the account's name. If the type cannot be verified, the list returns an unavailable error instead of reporting a guessed type. When an enterprise organization manages more than one account, organization membership grants management access through that hierarchy but does not create direct account membership. Separately, each user manages their own **notification preferences** — the set of event types and channels (`email`, `in_app`) they opt into. ## Key concepts | Concept | Description | | - | - | | Current account | The account context the request authenticates as — `id`, `company`, `name`, `role`, `customerDomain` | | Organization | The top-level entity in the hierarchy; only accounts created under an organization can be deleted | | `customerDomain` | The account's registered domain; required before domain auto-join can be enabled | | Operator identity | The seller-facing AdCP key for this buyer account: its operator domain and the account's own operating-unit code, a stable `operator_unit.id` | | Membership | Per-account access settings, notably `allowDomainAutoJoin`; defaults on for top-level organizations and off for child accounts | | Notification preferences | Per-user opt-ins of `notificationType` × `channel` | | Role | `MEMBER`, `ADMIN`, or `SUPER_ADMIN`; admin-only operations require `ADMIN` on the target account | ## Manage child accounts in the app Organization administrators can open **Settings → Organization → Accounts** from the organization or one of its child accounts. The list shows active accounts first; use the status filter to view archived accounts. The pane links to each active account's members and provides **Add account**, **Archive**, and **Restore**. Administrators can see **Add account** even when creation is unavailable; the pane explains why it is disabled. Creation follows the organization's account-capacity rules. Archive shows any campaign or media-buy blockers before you confirm; archived accounts retain their history and still count toward capacity. To archive the buyer account you are working in, open **Settings → Danger zone**. Organization administrators get **Open organization accounts**, which switches to the organization and opens its account list, because an account cannot be archived while you are using it. Other members are asked to contact an organization administrator. Standalone buyer accounts and buyer organizations have no Danger zone: only an organization's child accounts can be archived. Organizations with no separate Organization group, such as standalone accounts, continue to use **Plan & Billing** for account usage, fees when available, and agreement history. ## Manage accounts with a V3 agent From an organization in the V3 MCP preview, use `search({kind: "account"})` to list its active and archived child accounts. Use `get({kind: "account", id: ""})` for one account. The detail includes its `ACTIVE` or `ARCHIVED` lifecycle status and the organization's commercial terms that the account inherits: the current Terms of Service URL, required and accepted versions, acceptance details, current contract posture, and up to 25 agreement-history entries. These terms belong to the organization; they are not separate child-account terms. Use `search({kind: "member", filter: {accountId: }})` to read an account roster, then `get({kind: "member", id: "", memberAccountId: })` for one membership and role. Member reads do not grant or change access. The organization administrator changes child accounts with `save_account`, working in the organization account. A call with no `id` creates a child. A call with `id` and `name` renames one, and `isArchived` archives (`true`) or restores (`false`) it. Each of those writes is recorded for website approval: the tool returns an `approval_required` result with a link, nothing changes until an administrator of that organization approves the exact request on the website, and the agent never approves anything itself. Repeat the same call with the same `idempotencyKey` to see the recorded state, and once it ran, its result. A rename, archive or restore also needs a `reason` of 10 to 300 characters, which the approver sees. `get` and `save_account` still accept the earlier `accountId` in place of `memberAccountId` and `id` while connected tool lists refresh. To create a child, send `accountType` (`buyer`, `seller` or `developer`), a `name`, a `reason` and an `idempotencyKey`. A seller also needs `operatingMode`, the Seller product, and any child may carry a `customerDomain`. A platform operator can instead send `accountType: "seller"` and `demoStorefront: true` (no `operatingMode`) to create a Demo Storefront, which uses no paid workspace. A standard Account needs contracted workspace capacity and plan coverage; the first Developer account is free and takes no slot. Setting `dryRun: true` runs every check the real request runs and records nothing, so the preview cannot differ from the request that follows. Send no `idempotencyKey` with a `dryRun`. A preview of an archive lists the campaigns and media buys that block it, and restore needs a child that was archived and a plan that still covers it. The organization account's `get_status` reports its total, active and inactive child account counts, its workspace capacity, and whether its Developer account exists. If it does not, `get_status` offers the `save_account` call that creates it. For one child, `get({kind: "account", id, include: ["lifecycle", "capacity"]})` adds the archive state, whether the child can be archived and the counts of campaigns and media buys that block it, and the organization's workspace capacity. The reads and every write check that you are an administrator of that exact organization. Send `organizationId` to act in an organization you are not connected to, for example from a child account you administer through its parent. The server checks that you directly administer that organization (or are a Scope3 platform operator) before it reads anything else. Without it, the call acts in the organization account you are connected to. The V3 agent surface does not choose the first owner or change members or roles. Member access stays in **Organization → Members**. A `settings.company` change is a display label, so it is saved at once and needs no approval. Delegated, machine, impersonated and switched-in sessions are refused, because the administrator check needs a direct session. Only a direct Scope3 platform operator can create a Demo Storefront, and the operator must name the customer's organization with `organizationId`. ## Create a Buyer child through MCP From an organization in the V3 MCP preview, an agent can create a Buyer child account under that exact organization. Nothing is created until an organization administrator approves it on the Apostra site: If the agent starts in a Buyer Account, call `get_status` to find the Organization account ID, then name it with `accountId` on each call. The agent can call `get_status` with that `accountId` to see the Organization's child accounts, and name a Buyer Account again on its next call; nothing is switched. An MCP host that keeps its initial tool list also lists the Organization tools when the signed-in user can reach the Organization. Each tool still checks the account the call names and the caller's permissions when it runs. See [Name the account on every call](/v3/name-the-account). 1. `review_buyer_child_account` with the organization's ID and the proposed name shows the contracted account capacity, who is asking, and what will change. It is read-only. 2. `request_buyer_child_account` with the same organization ID, the name and an `idempotencyKey` records the request and returns `approval_required` with a link to the approval page. Show the person the link. If the organization has no account capacity left, the request is refused and nothing is recorded. 3. An organization administrator opens the link, signed in in their own browser, and approves or rejects the request. It can be a different administrator from the one who asked. An unanswered request expires after 24 hours. See [Approving agent requests](/v2/concepts/web-approvals). 4. Call `request_buyer_child_account` again with the same key and name to get the outcome: the new account's ID once it is created, or the reason it could not be created (for example, no account capacity left). Creating the account sends no invitation, changes no feature flag and creates no media buy. Organization administrators inherit access to the new account. It does not make the account ready to buy: complete Buyer Setup, and any required terms, plan and payment steps, separately. This works in text-only MCP hosts. ## Task reference > **[Set up buyer identity](/v2/buyer/account/setup)** > > Confirm whole-operator or specific-unit scope before new AdCP 3.2 provisioning > **[Get current account](/v2/buyer/account/tasks/get-current-account)** > > `GET /accounts/current` — your current context > **[List accounts](/v2/buyer/account/tasks/list-customer-accounts)** > > `GET /accounts` — accounts you can access > **[Update account domain](/v2/buyer/account/tasks/update-customer-domain)** > > `PATCH /accounts/:customerId/domain` — set the registered domain > **[Get membership](/v2/buyer/account/tasks/get-membership)** > > `GET /accounts/:customerId/membership` — read access settings > **[Update membership](/v2/buyer/account/tasks/update-membership)** > > `PATCH /accounts/:customerId/membership` — toggle domain auto-join > **[Get notification preferences](/v2/buyer/account/tasks/get-notification-preferences)** > > `GET /notification-preferences` — your opt-ins > **[Update notification preferences](/v2/buyer/account/tasks/update-notification-preferences)** > > `PUT /notification-preferences` — replace your opt-ins # Refer a company Source: https://docs.apostra.com/v2/buyer/account/referrals Introduce another company to Apostra and earn Intelligence Units after a successful referral The customer referral program lets a signed-in Apostra customer introduce another company to Apostra. You can submit a referral from **Refer a company** in the account menu and follow its status on the same page. ## What to share Submit one person at the referred company with their: * name; * work email; * company name; and * optional context about why the company may be a good fit. Personal email addresses and referrals to your own company are not eligible. Apostra handles outreach and review; submitting the form does not itself earn a reward. ## When a referral is successful A referral becomes successful after Apostra links it to a new, active Apostra customer and confirms either: * the referred company has started a paid plan; or * the referred company is a buyer that has reached the agentic media-spend threshold shown on the referral page. A customer can qualify for one referral reward, and the referring and referred organizations must be different. When the referral succeeds, the offer shown on the referral page is added to the referring organization's shared Intelligence Unit wallet. Everyone in the organization sees the same reward; it is not a personal balance. The page shows the current IU amount and expiry period. Referral IUs have no cash value, do not roll over, and can be used for IU-rated Apostra activity available to your organization. Apostra reviews eligibility and may change or end the offer. ## Referral statuses * **Submitted** — Apostra received the introduction. * **Qualified** — the company appears to be a fit and review is progressing. * **Reward issued** — the company became an active customer and the IU credit was added to your organization's wallet. * **Not eligible** — the referral did not meet the program rules. # Buyer setup and go-live Source: https://docs.apostra.com/v2/buyer/account/setup Confirm your company once, then see what each seller or buying platform needs. Buyer Setup separates your organization’s platform readiness from the setup for each seller or buying platform. If Scope3 invites you to a new buyer account, the setup email is sent to your named address. Accept the invitation and verify that email to join. The invite opens the account setup path; your organization still completes its domain, plan, terms, and other readiness steps before live buying. Buyer and Seller Setup use the same activation pattern: a neutral status and progress header, one operator-domain editor, and a clear next action. They are separate Pages because the work after identity is different. Buyers configure destinations and advertiser mappings; storefronts configure inventory, publisher authorization, settlement, and payouts. ## Platform go-live You complete these steps once for the buyer account: * confirm the domain of the company that runs the account (the **Company identity** step); * choose the plan for this buyer account; * accept the current terms; * resolve any account hold; and * once choosing payment terms is available, choose the payment terms sellers are asked for before buying from a seller on consolidated billing (see [Your organization's payment terms](#your-organizations-payment-terms)). These facts unlock real non-spend work such as connections, proposals, and campaign setup. Your billing details are needed only when a transaction's seller bills through Apostra on consolidated billing. Apostra does not grant or manage a buyer credit line — sellers decide credit on their own terms, on their own media. The **Company identity** step asks for the domain of the company that runs this account. Sellers use it to recognize you. Sandbox advertisers don't need this step; live advertisers and real buying do. Once you have confirmed the domain and created your first advertiser, the domain and the account code become read-only, and changing them takes an identity migration through support. In AdCP terms the company is the *operator* and each buyer account is one *operating unit* of it. Every buyer account is its own operating unit. There is no "whole company or one team" question: an organization with one buyer account has one unit, and an organization with several accounts has several. Together, the domain and the account's unit code form its AdCP operator identity: | Part | What it is | | - | - | | Operator domain | The domain of the company that runs the account | | Operating unit `id` | A short code fixed when the account's identity is first saved | | Operating unit `name` | The buyer account name | Apostra generates the code from the account name, for example "DM - Restaurant Company Europe" becomes `dm-restaurant-company-europe`. If another account under the same operator domain already holds that code, Apostra adds a short numeric suffix. You can type a shorter code of your own, such as `rce`; prefer one your organization already uses, or a short, readable code that will stay meaningful. Do not use `default`, `global` or `primary` (no account is the default, global or primary one), a temporary display name, a seller's account ID, or an Apostra database ID. The code never changes when you rename the account. The buyer account name is mutable display metadata; the billing-company name is separate and does not identify the operating unit. An account administrator confirms the identity. Before confirming, review the other buyer accounts under the same operator domain. Buyer Setup lists the identities visible in your account hierarchy for the domain you enter. Apostra rejects an exact identity (domain and code) already claimed by another buyer account. You can change the operator domain or account code after setup and after connecting storefronts, until an advertiser account is created or bound under that identity. Buyer Setup then reports the identity as locked. In AdCP 3.2, the operator domain and the operating-unit ID are advertiser natural-key dimensions; pre-3.2 bindings still make the seller-visible operator domain immutable. Changing either requires an advertiser identity migration rather than an ordinary settings update. **Accounts under an organization that claims a domain.** When your buyer account sits under an organization whose company identity holds a domain, an account that uses that same domain takes its domain proof from the organization: * The account is verified when the organization's domain is approved or verified by DNS, and that approval covers every account under the organization that uses the domain. An approval for the account itself does not count, and staff ask for the approval on the organization instead. * An account that proved the domain before the organization claimed it keeps that proof. * Once any account using the organization's domain has an advertiser, the organization's domain is locked, because changing it would change every one of those accounts' identity at sellers. * An account that uses a different domain, or whose organization has not saved a company identity, confirms and proves its own domain as before. Apostra compares the operator domain with account membership and the verified organization domain. The Organization must prove control before real operations are available. AAO corporate metadata can suggest or classify the Organization, but a public AAO record is not ownership proof. A buyer operator does not publish `adagents.json`; that document belongs to seller authorization. Buyer setup also does not require `brand.json`. Buyer Setup always shows the current operator. A domain suggested from existing account or work-email information is prefilled, but it is not silently treated as your choice. The account code field is prefilled with a code generated from the account name, such as `dm-restaurant-company-europe`. Keep it or type your own, and the code you see is the code that is saved. Accounts created before every account had an operating unit keep working on their existing buying paths until their unit is assigned. Buyer Setup does not block them; a complete AdCP 3.2 account key needs the unit, so new AdCP 3.2 provisioning waits for it. ### Seller-history co-op The operator identity also holds whether the account joins the [seller-history co-op](/v2/concepts/seller-history-co-op), how an account takes part in the data co-op of Section 6.3 of the Terms of Service. It starts off. An account administrator changes it with `PATCH /api/v2/buyer/readiness/operator` and `sellerHistoryCoop`, or with `save_buyer_operator` on `/mcp/v3`. What a member shares and gets, and how leaving works, is on that page. ## Double-check your media partners Before you create your first advertiser, Buyer Setup asks one question: **Have you double-checked your media partners?** Media partners are managed for the whole company, not advertiser by advertiser. The **Review media partners** button opens [Media Partners](/v2/buyer/storefronts/overview). This is a reminder, not a go-live step. It never blocks creating an advertiser, and Apostra still starts account setup with media partners whether or not you open it. It shows as done once your company has accepted its media partners, which happens when it makes a choice about them on the Media Partners page. Buyer readiness returns this as a check with the id `media_partners`. It is `missing` until your company has accepted its media partners, then `complete`, and its `isBlocker` is always `false`, so it changes none of the readiness status, capability, or destination answers. On `/mcp/v3`, `get_status` lists it as a recommended next action that opens Media Partners. ## Launch your first campaign Once platform setup is complete, the next step is launching your first campaign. Your first campaign counts as launched once one of its media buys is placed. This is the step after setup, not a setup check: it never blocks anything, and it changes none of the readiness status, capability, or destination answers. Buyer readiness returns it as `firstCampaign`. `firstCampaign.launched` says whether the account has placed a media buy. Until it has, and once `platformReady` is `true`, `firstCampaign.action` is **Launch your first campaign**, which opens Campaigns (`page: "campaigns"`). While setup steps remain, or once a buy is placed, `action` is `null`. `firstCampaign` itself is `null` only when it could not be read for that request; try again. On `/mcp/v3`, `get_status` lists the same step as a recommended next action, `first_campaign`, that opens Campaigns. ## Your next step on the Advertisers page The Advertisers page shows one next step above your advertisers. While a setup step blocks buying, it names the first one and opens Buyer Setup. Once setup is complete and nothing has launched, it is **Launch your first campaign** and opens Campaigns. It is the same step `get_status` puts first. The page shows nothing when no step is left, or when the step could not be read in time. ## If an API call requires Buyer Setup On `/mcp/v3`, call `get_status` first. When `operatorIdentity.usableForBuying` is false, an account administrator can call `save_buyer_operator` with the real non-platform `operatorDomain`, and optionally a short AdCP `operator_unit` code (`{ "id": "rce" }`); without one, the account's unit code is generated from its name. When `locked` is true, follow the support action returned by `get_status` instead, unless the account has no unit yet (`operator_unit` is null), which the same call can still assign. To open the Buyer Setup page for a person, call `open_page` with `{ "page": "buyer_setup" }`; the opening call changes nothing. The v2 request, `PATCH /api/v2/buyer/readiness/operator`, takes the same `operatorDomain` and optional `operatorUnitId`. It still accepts `operatorScope: "specific_unit"` from older clients and generates the unit when you send no scope. It refuses `operatorScope: "whole_operator"` with a `400`: > Whole operator is retired; every buyer account is an operating unit. Send > operatorScope: "specific\_unit" with operatorUnitId, or omit operatorScope to use > the unit generated from the account name. Nothing is converted for you: a request that used to save a whole-operator identity now fails until you send a unit or omit the scope. This is a deliberate exception to the rule that v2 only grows. The decision, in full: "v2 buyer `PATCH /api/v2/buyer/readiness/operator` refuses `operatorScope: "whole_operator"` with a 400. Whole operator is retired. Brian O'Kelley, 2026-10-22." Reads are unchanged and return what is stored. The v2 readiness response keeps `operatorScope`, `scopeStatus` and `scopeConfirmedAt` as deprecated, read-only fields that report the stored scope: an identity saved as `whole_operator` keeps reading that way until it is converted to its own unit, and a saved or generated unit reads `specific_unit`. Nothing new is ever saved as `whole_operator`, so the update response reports `specific_unit` once the unit is saved. Readiness also returns `suggestedOperatorUnitId`, the code the account would get if you saved it now. The tool confirms commercial identity only; it does not grant user access or change the login organization. > [!NOTE] > > Before that identity is claimed, you can still create, update, or archive a > test-only advertiser with `sandbox: true`, through v3 `save_advertiser`, the v2 > advertiser API, or the **Create advertiser** form. This exception applies only > to the operator-identity check: plan selection, current Terms, and other > readiness blockers still apply. Live advertisers continue to require the > complete buyer operator identity. Buyer discovery and other new buying operations return HTTP `409` with error code `BUYER_SETUP_REQUIRED` when the account has no confirmed, non-platform operator identity. For a missing identity, the exact structured details are `reason: "missing_operator_identity"`, `readinessCheck: "operator_identity"`, `setupStep: 1`, and `setupPage: "buyer_setup"`. This is a correctable account-readiness task, not a seller outage: open **Buyer Setup**, confirm the operator domain, then retry the request. ```json theme={null} { "data": null, "error": { "code": "BUYER_SETUP_REQUIRED", "message": "Complete Buyer Setup step 1 by confirming your buyer operator identity before continuing.", "details": { "reason": "missing_operator_identity", "readinessCheck": "operator_identity", "setupStep": 1, "setupPage": "buyer_setup" } } } ``` Apostra never substitutes `interchange.io` for a missing buyer operator. That would combine unrelated buyers under one seller-side natural-key account. If a legacy profile explicitly contains that platform domain, the request uses the same `BUYER_SETUP_REQUIRED` contract with `details.reason: "platform_operator_not_allowed"`. Replace it with your organization’s operator domain in **Buyer Setup**. Changing or confirming the operator affects new discovery and provisioning, but does not rewrite existing transactions. A historical media buy continues to use the exact seller account persisted when the buy was created, including for status, creative re-sync, update, cancellation, and delivery operations. The same identity gates account linking. A production entry on `POST /api/v2/buyer/storefront-accounts/sync` (or the `sync_buyer_storefront_accounts` tool) must name your confirmed operator domain as `operator`; a different domain returns HTTP `403` with `ACCESS_DENIED` and `details.reason: "operator_identity_mismatch"`, and a missing identity returns the `BUYER_SETUP_REQUIRED` contract above. A sandbox entry (`sandbox: true`) is fenced off from production spend and may name any operator. ### Who decides your account request A seller decides your account request the same way whichever way it arrives: `sync_accounts` on the seller's AdCP endpoint, `POST /api/v2/buyer/storefront-accounts/sync` or the `sync_buyer_storefront_accounts` tool, or an account Apostra opens for you automatically. * **A seller that routes accounts to its own sales agent** (a Listing whose only source is that agent) forwards the request, including any `payment_terms`, to the agent, and the agent approves or rejects it. The seller does not review it in Apostra. The agent must accept the terms you requested or reject the account. Apostra records the terms you requested, and the account reports in `payment_terms` the terms the agent confirms. A new account the agent offers on other terms is treated as a refusal: the account is not activated and waits for you to review the seller's terms. An existing account the agent keeps on other terms stays usable, but it shows a warning naming both the terms you requested and the terms the agent kept, until the agent reports the terms you requested. If the agent cannot take account requests, the request fails and nothing is created: the buyer API returns HTTP `422` with `CAPABILITY_NOT_SUPPORTED` and a `details.reason` such as `account_resolution_authority_missing`, and the AdCP endpoint returns `CONFIGURATION_ERROR`. Retrying does not help until the seller fixes its agent. * **Every other seller** applies its account approval setting in Apostra: the request is approved automatically or waits for the seller to review it. A request that is still waiting comes back with `status: "pending_approval"`. Check it later with `GET /api/v2/buyer/storefront-accounts` or the `list_buyer_storefront_accounts` tool. On a storefront that has not moved to complete AccountKey mapping, an approved account belongs to your buyer organisation: any colleague or service token of the same buyer can use it, and nobody outside your organisation can. ### When a direct AdCP 3.0 or 3.1 storefront asks for account setup After a storefront moves to complete AccountKey mapping, a direct AdCP 3.0 or 3.1 `sync_accounts` call can still read an approved account that already belongs to its credential. It cannot create a new account on that older wire format. A new direct request returns `ACCOUNT_SETUP_REQUIRED` with `details.reason: "complete_key_account_setup_required"`. Create the account through your authenticated V2 buyer account, then retry the direct call after the seller has decided it. Use either the REST request `POST /api/v2/buyer/storefront-accounts/sync` or the buyer MCP tool `sync_buyer_storefront_accounts`. Both take the same account request; target the storefront with its V2 `storefrontId` and send the brand, your confirmed operator domain for a production request, and the billing party: ```json theme={null} { "target": { "storefrontId": 42 }, "accounts": [ { "brand": { "domain": "brand.example" }, "operator": "buyer.example", "billing": "agent" } ] } ``` This V2 request creates or returns the one organization-owned account request for that seller. It does not create a separate legacy per-credential approval. For production, use your confirmed buyer operator identity; use `sandbox: true` only for a test account that cannot be used for production spend. See [Payment terms](#payment-terms) for the optional per-seller request field and the billing requirements for `operator` and `advertiser`. ### Address your approved account from a direct AdCP 3.0 or 3.1 agent Once a seller has moved to complete AccountKey mapping, a direct agent can use two kinds of approved account. The `account_id` itself says which kind: * **Company accounts** have an `account_id` that starts `seller_account_v1_`. The seller approved them for your verified buyer organisation and an advertiser, and any human or service token currently authorised for that organisation and advertiser can use them. `sync_buyer_storefront_accounts` and `list_accounts` return them. * **Credential accounts** are the accounts that one credential of yours was approved for before the seller moved. Their `account_id` starts `sf_`. They stay available to that exact credential and to nobody else: another user or service token of the same company does not inherit them, and Apostra does not convert them into company accounts. Send the `account_id` alone. An AdCP `account` is one of two exclusive shapes, never both: ```json theme={null} { "account": { "account_id": "seller_account_v1_0123456789abcdef0123456789abcdef" } } ``` ```json theme={null} { "account": { "brand": { "domain": "brand.example" }, "operator": "buyer.example" } } ``` A request that sends `account_id` together with `brand` or `operator` inside `account` is rejected before any account lookup with `VALIDATION_ERROR`, `field: "account"`, and `details.issues[]` naming `/account` and the `additionalProperties` rule. It is a malformed request, not a missing account. To tell Apostra which brand you are buying for, send the top-level `brand` reference as AdCP defines it. Its domain must match the approved account's brand. On a seller in complete-key mode these calls resolve the same approved account, through the lane its `account_id` belongs to: `list_accounts`, `get_products`, `get_account_resources`, `sync_creatives`, `create_media_buy`, `get_media_buys`, `update_media_buy` (including cancellation), and `get_media_buy_delivery`. An id that starts `seller_account_v1_` is only ever looked up as a company account. If it is unknown, malformed, or not yours, the call fails; Apostra never retries it as a credential account, and never reads a `sf_` id as a company account. Each company-account call checks again, at that moment, that: * the account belongs to your verified buyer organisation and is approved by the seller; * the request selects the advertiser the account is scoped to, with the `x-scope3-seat-id` header (or an API key that is already bound to it); * the credential is a human user with Premium or Admin access to that organisation, or a write-capable service token for that organisation and advertiser, and has not been revoked; and * your buyer account is currently eligible. A credential account needs the grant for the calling credential to be active. `list_accounts` returns your company accounts first, then the calling credential's own credential accounts, behind one `pagination.cursor`. A credential with no selected advertiser or no human or service-token actor lists its credential accounts only. A caller that does present that authority and is refused gets the refusal, not a shorter list. `GET /api/v2/buyer/storefront-accounts` and the `list_buyer_storefront_accounts` tool list the same way on a seller in complete-key mode: company accounts first, then the calling credential's own credential accounts, behind one `cursor`. A `cursor` that did not come from this listing is rejected with a validation error. A new buy needs an account that is settled before anything is written. Send the `account_id`. A brand and operator is accepted only when it is exactly one credential account that the calling credential already holds (same brand, operator and sandbox flag, nothing more, and the grant is active); the buy is then written under that credential account. Any other brand and operator, and a missing account, returns `ACCOUNT_SETUP_REQUIRED` and writes nothing. A partial key is never completed into a company account. If a call fails closed, Apostra does not fall back to another account or to every account the customer owns. | Situation | Response | | - | - | | A company account that does not exist, belongs to another organisation, or is scoped to a different advertiser | `ACCOUNT_NOT_FOUND`, `details.reason: "account_not_authorized_for_buyer"` (the same answer for each, so an ID reveals nothing) | | A credential account that belongs to another credential | `ACCOUNT_NOT_FOUND`, `details.reason: "account_not_authorized_for_buyer"` | | A company account with no advertiser or no authenticated actor on the request | `AUTH_REQUIRED`, `details.reason: "complete_account_authority_required"` | | The actor or buyer lifecycle was revoked after the account was approved | `ACCOUNT_NOT_FOUND` on the next call | | A credential account whose grant is pending, rejected, suspended or closed | `AUTH_REQUIRED`, `details.reason: "account_grant_inactive"` | | Another account's media buy | `MEDIA_BUY_NOT_FOUND`, `details.reason: "unknown_media_buy"` | | A new brand or operator through direct 3.0/3.1 `sync_accounts`, or a buy with a brand and operator that is not your credential account | `ACCOUNT_SETUP_REQUIRED`, `details.reason: "complete_key_account_setup_required"` | | A company-account booking that the seller reviews manually | `INVALID_REQUEST`, `details.reason: "complete_key_account_approval_unsupported"` | Sellers that are not in complete-key mode keep the released per-credential account behaviour for every call above. An account grant says whether the seller will transact with you. It does not say whether Apostra uses that seller for a given advertiser. A `GET /api/v2/buyer/storefront-accounts` request (or the `list_buyer_storefront_accounts` tool) that names an advertiser with `advertiserId`, or selects one with the `x-scope3-seat-id` header, also returns `ext.interchange.advertiser_activation`: `seller_on` (whether product discovery and new media buys use this seller for that advertiser) and `reason` (the control that decided it). A named advertiser you cannot read is refused with `403`, and `503` means access could not be checked right now. A seller can have an `active` grant and still be off for the advertiser; turn it on with [Update advertiser Seller preference](/v2/buyer/storefronts/tasks/update-advertiser-activation-preference). The field is omitted when the request names and selects no advertiser, when the advertiser belongs to another account (a sibling account in your organization, or an organization that delegated it), or when Apostra cannot read the advertiser or the decision; the grant list still returns. ### Billing modes and the business entity payload Each `sync_accounts` entry names a `billing` mode: `agent` (Interchange invoices on your behalf; no additional payload needed), `operator`, or `advertiser` (the storefront invoices your operator or advertiser directly). `operator` and `advertiser` entries require a `billing_entity` — the AdCP `BusinessEntity` payload with your legal name and, as applicable, tax/VAT/ registration ids, a postal address, billing contacts, and bank details: ```json theme={null} { "billing": "operator", "billing_entity": { "legal_name": "Acme Media Ltd", "vat_id": "DE123456789", "address": { "street": "Friedrichstrasse 100", "city": "Berlin", "postal_code": "10117", "country": "DE" }, "contacts": [ { "role": "billing", "name": "AP Department", "email": "ap@acme.example" } ] } } ``` `billing_entity.bank` is write-only: send it to provide payment coordinates, but it is never echoed back on a `sync_accounts` or `list_accounts` response — the seller stores it and confirms receipt without returning it. A storefront only accepts the billing modes it has published support for. An entry naming an unsupported mode returns HTTP `400` with `BILLING_NOT_SUPPORTED` and `details.supported_billing` listing the modes the storefront actually accepts: ```json theme={null} { "data": null, "error": { "code": "BILLING_NOT_SUPPORTED", "message": "billing=operator is not supported by this storefront. Supported: agent.", "details": { "reason": "billing_not_supported", "supported_billing": ["agent"], "requested_billing": "operator" } } } ``` ### Payment terms Payment terms can be requested per seller account, and separately for your whole organization. They are two different settings with different values: * **Per seller account (`sync_accounts`).** Each entry in a `sync_buyer_storefront_accounts` call (`POST /api/v2/buyer/storefront-accounts/sync`) can set its own optional `payment_terms`. Each call targets one seller storefront (`target.storefrontId`), so one request can carry different terms for different sellers. Allowed values: `net_15`, `net_30`, `net_45`, `net_60`, `net_90`, or `prepay`. Omit `payment_terms` to accept the seller's default terms. * **Organization level (`paymentTerms`).** One choice for the whole organization, set with [Update billing info](/v2/buyer/billing/tasks/update-billing-info) or the V3 `save_billing` tool. Allowed values: `net_15`, `net_30`, `net_45`, `net_60`, or `net_90`. `prepay` is not an organization-level value. Apostra uses this choice when it opens a consolidated-billing account for you; see [Your organization's payment terms](#your-organizations-payment-terms). * **The V3 hosted-agent tools cannot set per-seller terms.** `save_connection` has no payment-terms field and there is no V3 `sync_accounts` tool. The only payment-terms write in the V3 tools is the organization-level `save_billing` `paymentTerms`. To request different terms from one seller, use `sync_buyer_storefront_accounts` or its REST route. AdCP lets a `sync_accounts` entry request `payment_terms`. The protocol does not let a seller silently swap in different terms: the seller accepts the requested terms or rejects the account. The seller sees the requested terms when it reviews the account and can only accept or reject; it cannot edit them. The requested terms go to the seller with the account request: * **The seller reviews them.** Approving the account accepts the terms you requested, and the account then reports them in `payment_terms`. On a storefront that routes accounts to the seller's own sales agent, that agent decides instead; see [Who decides your account request](#who-decides-your-account-request). There is no counter-offer: if the terms don't work for the seller, the seller rejects the account. * **Net 60 or shorter can be approved automatically.** Unless the seller has chosen to review every new account, a storefront approves a request for `net_60` or shorter terms (or `prepay`) without a person reviewing it. A seller can lower that limit. A request for longer terms, such as `net_90`, always waits for the seller to review it, so it can take longer to approve. * **You can change the terms while the request is pending.** Send the same account again with different `payment_terms` and they replace the earlier ones; that account's row returns `action: "updated"` and the seller reviews the new terms. * **A decided account keeps its decision.** Once the seller has accepted or rejected the account, a request naming different terms changes nothing. That account's row returns `action: "failed"` with the AdCP error `PAYMENT_TERMS_NOT_SUPPORTED`, and the account keeps its status and its terms. Other entries in the same request are unaffected. To change the terms on an accepted account, agree them with the seller directly. #### Your organization's payment terms Your organization chooses the payment terms Apostra asks sellers for: `net_15`, `net_30`, `net_45`, `net_60`, or `net_90`. Until you choose, `net_60` applies. Set the choice with `paymentTerms` in [Update billing info](/v2/buyer/billing/tasks/update-billing-info); it applies to every advertiser in the organization. Like the rest of this section, it is available once requesting terms is available. Every account request for an agent-billed, non-sandbox account carries these terms to the seller, whichever seller it is, unless the request names its own `payment_terms`. Choosing terms is a go-live step for consolidated billing, so no consolidated-billing buy is placed on terms nobody chose. Until an organization administrator has chosen: * buyer readiness shows a **Payment terms** check (`payment_terms`) that is not complete, and it blocks go-live when you have sellers on consolidated billing; * a seller on consolidated billing shows `needs_commercial_setup`, and a buy from it is refused with the `payment_terms` blocker; and * direct-billed sellers are not affected. If you have already connected a seller on consolidated billing, Apostra holds that seller-account setup until you choose terms, then resumes it automatically. Sandbox accounts are not held. Choose terms explicitly, even to keep the default: send the default value itself. In Buyer Setup, `net_60` is pre-selected and the button reads **Accept Net 60**; accepting it counts as choosing, and nothing is recorded until an organization administrator does. Once chosen, the terms can be changed but not cleared, so `paymentTerms: null` is refused with a validation error and the recorded choice stays in place. If you have not chosen and a media partner is waiting on your terms, the Media Partners page shows **Confirm your payment terms**. One click records `net_60`, the default, as your organization's choice and resumes the held setup. It records the default only; to choose other terms, use Buyer Setup. Only an organization administrator can confirm, and Apostra never records terms for you on its own. Organizations that already had active consolidated-billing seller accounts or buys when this step became required on 11 October 2026 have `net_60` recorded as their choice, so nothing is held for them. They can change it like any other choice. When Apostra opens a consolidated-billing account for you with a seller (for example, when you turn a seller on for an advertiser or buy from a seller for the first time), the request asks for your organization's terms. Direct-billed and sandbox account requests do not carry them: * **Longer terms can mean more rejections.** Sellers carry the credit risk on their accounts, so some decline longer terms. On storefronts Interchange hosts or manages, `net_60` or shorter can be approved automatically, while `net_90` waits for the seller to review it. * **Decided accounts keep their terms.** Changing your choice applies to accounts opened afterwards. An account a seller has already accepted or rejected keeps that decision and its terms; an accepted account keeps working exactly as before. * **A seller can refuse your requested terms.** The account request then fails for that seller, and the connection reports that the seller does not accept your terms. Review the seller's terms before explicitly requesting an account on those terms, or leave that seller disconnected. Apostra does not retry the request on the seller's default terms. On the Storefront MCP discovery surface, an explicit opaque `account_id` must belong to the authenticated buyer on that storefront. A missing buyer grant or an ID belonging to another buyer returns the structured AdCP error `ACCOUNT_NOT_FOUND` with `details.reason: "account_not_authorized_for_buyer"`; it is not reported as a missing storefront account context. Retrieve an authorized account with `list_accounts`, or complete the seller's account-linking flow, before retrying. Buyer Setup returns derived capability verdicts rather than a writable Account mode. Before Organization proof, plan selection, and Terms, the Account can use bounded demo and onboarding surfaces. After those steps it can perform real non-spend work. Spend additionally requires the media-buying product entitlement. Seller-direct transactions need nothing further from Apostra. Consolidated-billing transactions additionally need your organization's billing details: a payer name, street address, and country, so Apostra can invoice you. They never need a card or an Apostra credit line, because Apostra takes no credit risk on the seller's media. An organization that already has a verified card or an Apostra credit line on file can keep buying while it adds its billing details. Account holds block every capability. Confirming or changing the buyer operator does not change your WorkOS organization, sign-in, invitations, SSO, account membership, billing company, advertiser, or brand domain. ## Destination go-live Every seller or platform then shows only its own remaining requirement: * A credential-free AdCP seller requires no connection step. * An official adapter may require a free provider connection, account choice, and advertiser mapping. * A seller can be temporarily unavailable even when your connection is healthy. * Direct-billed sellers and platforms bill the operator or advertiser and do not use an Apostra rate card or payment method for that route. * A seller on consolidated billing requires active media pricing supported by the current pricing engine and your billing details before live spend. Add them in **Plan & Billing → Payment & invoices**. Until they are complete, the buy is refused with the `billing_details` blocker, readiness lists `BILLING_DETAILS`, and `commercial.billingDetailsComplete` is `false`. Readiness no longer reports `PAYMENT_AUTHORITY` or the `payment_authority` blocker. Treat any blocker value you do not recognize as blocking. A child account automatically uses the media pricing on its billing organization; the account-delegation placeholder does not control that inheritance. Once choosing payment terms is available, your organization must also have chosen them; see [Your organization's payment terms](#your-organizations-payment-terms). The media rate card used to price a buy is separate from the Organization IU Rate Card. Choosing an IU plan is not a buyer-readiness requirement. You can connect destinations, inspect accounts, and view mirrored campaigns without charge. “Can buy now” means the applicable capability verdict is allowed and at least one destination’s actual requirements are complete. # Paying by card Source: https://docs.apostra.com/v2/buyer/billing/concepts/pay-by-card Save a card as your organization's payment method for eligible platform and IU charges Adding a card gives your organization a payment method for eligible platform and IU charges. You save it once on the [Plan & Billing page](/v2/buyer/billing/plan-and-billing), and it becomes your organization's payment method — there is no checkout step, no plan you must pick first, and no commitment. ## What "add a card" does When an org admin adds a card on **Settings → Plan & Billing → Payment & invoices**: 1. The card details are collected in a secure form embedded in the page. Card numbers are encrypted in transit and **never stored on Apostra servers** — Apostra keeps only the card's brand, last four digits, and expiry date, for display. 2. If your bank requires it, a quick verification step happens right in the form (you may recognize this as the confirmation prompt your bank uses for online purchases). 3. Once verified, the card becomes the organization's payment authority. Saving it does not by itself accept an IU Rate Card, select a plan, or turn on paid entitlements. The card belongs to the **organization**, not to the person who added it. Only org admins can add, replace, or remove it. If you close or cancel the form before choosing **Save card**, no payment method is added. You can return to **Payment & invoices** and start again. ## While card confirmation is processing After you submit the secure card form, Plan & Billing shows **Card confirmation in progress** with the submission time. This processing state is saved for the organization, so it remains visible if you close the panel, refresh, navigate away, or sign out and return later. You can safely leave the page while Apostra waits for authoritative server confirmation. Most cards confirm within seconds. If confirmation is delayed, Apostra checks the payment processor directly so a delayed or missed notification can still converge. While an unexpired confirmation is processing, Plan & Billing does not offer a second indistinguishable card submission. The processing attempt is not a payment method: it cannot be charged, grant entitlement, or replace a verified card. When replacing a card, the existing verified card remains active until the new card is confirmed. The saved state then changes to one of these outcomes: * **Verified** — the card's display-safe brand, last four digits, and expiry replace the processing state. * **Failed** — Plan & Billing explains that the card could not be confirmed and offers **Try another card**. Apostra does not expose raw processor or bank errors. * **Expired** — confirmation did not reach a terminal result within 30 minutes; Plan & Billing offers a fresh attempt. If Plan & Billing cannot load the confirmation status, it shows **Unable to load card confirmation status** with **Try again** instead of the add-card form, because a card you already submitted may still be confirming. If you have a saved card, it stays visible, but **Replace** is unavailable until the status loads. If your session has expired, you are asked to sign in again. ## When the card is charged Nothing is charged when you add the card. Automatic charging starts only when your organization has an accepted paid IU plan. A plan commitment may be charged at cycle start; usage and overage are charged after they accrue according to the accepted Rate Card. A fee invoice records each issued charge. There are no card processing fees added to what you pay. If your organization would rather receive invoices and pay on net terms — purchase orders, AP portals, bank transfer — that path is always available: choose **Set up invoicing** instead. Both paths lead to the same Plan & Billing page, and you can switch between them later. Larger organizations often pick the card too: it skips vendor setup on both sides. ## If a payment fails If a charge fails, Apostra retries automatically on a published schedule. It emails the initial failure, failed-retry warnings, and the payment-hold notice; there is no separate day-30 email in the current alpha: | When | What happens | | - | - | | Right away | Email: the payment failed, with a link to update your card | | 3 days later | First automatic retry | | 7 days later | Second (final) automatic retry, with an email warning that an unresolved payment hold will be recorded soon | | 10 days later | If still unresolved: Apostra records and displays a payment-failure hold. The current alpha does not yet enforce that hold in spend, campaign, subscription, or entitlement authorization. | | 30 days later | If still unresolved: the dunning case is marked lapsed. The current alpha does not automatically downgrade the organization to the free tier or add a new enforced restriction. | The hold is currently a billing-status and operations signal, not an authorization gate. It does not pause new intelligence-credit spend, media commitments, subscriptions, or upgrades. The intended future restriction model would preserve access to: * Your data, reports, and dashboards * Data export * Media that is already delivering (a card hiccup never pauses delivery mid-flight — that would create make-goods and harm the seller) * This page and updating your card — the fix-your-card flow is never gated by the problem it exists to fix **Payment recovery is self-serve.** Update your card, then either wait for the next automatic retry or click **Update card & pay now** to retry immediately. A successful charge resolves the dunning case and removes its recorded hold; because the current alpha does not enforce that hold at authorization call sites, this is payment-state recovery rather than access restoration. The timings above come from the card-rail's global dunning configuration; they are not adjusted per account today. ## Updating or removing the card * **Replace** — add the new card the same way; it becomes the payment method and the old one is discarded. Do this any time, including before the old card expires. * **Expiry** — when your bank reissues a card, the saved card usually updates automatically with no action from you. When it can't, Plan & Billing shows an "expires soon" badge and you get an email about 30 days before the expiry date — a heads-up, not a restriction: an expiring card with no failed charge never blocks anything on its own. * **Remove** — allowed when nothing depends on the card. If it is your organization's only way to pay, removal is blocked with a pointer to add a replacement first (or set up invoicing). This preserves a valid payment path; card removal does not itself change entitlements. ## For agents The saved payment method appears in the [billing account document](/v2/buyer/billing/tasks/get-billing-account) as `payment.paymentMethod` — kind, brand, last four digits, expiry, and status. When an organization has no payment method and needs one, the document's `nextAction` is `ADD_PAYMENT_METHOD`. Card entry itself is always a human step (it happens in a secure form), so agents hand that moment to a person — and there is a dedicated task for exactly that handoff. ### Requesting a card via your agent (`add_payment_authority`) An agent working on the organization's behalf requests a payment method with the `add_payment_authority` MCP task, in three steps: 1. **Request** — call with `action: "request"`. The task stages the request and returns a single-use `confirmationToken` (valid 5 minutes). Nothing is created yet. 2. **Confirm** — call again with `action: "confirm"` and that token. The task returns `{ method: "capture_link", url, status, expiresAt }` — a link valid for 30 minutes that serves one verified card capture. The URL is returned exactly once; the agent saves it and hands it to the person who holds the organization's card. This legacy URL can be reopened until the capture is verified or expires; it is not a one-time browser exchange. 3. **The cardholder opens the link** in any browser — no account or sign-in is needed; the link itself is the authorization. The page shows which organization requested the card and the same secure embedded form as Plan & Billing. Card details go directly to the payment processor and never pass through the agent or Apostra API. The agent then polls `action: "status"` (every 15–30 seconds is plenty) until the status is `verified` or `expired`. A verified capture is historical evidence: read the billing account's current `payment.paymentMethod` before reporting an active payment authority. If the capture is expired, the legacy task requires a fresh request. The link statuses are `pending → opened → verified`, with `expired` as the terminal didn't-complete state. For a private Partner Offer, this is the MCP continuation after the organization administrator accepts the exact Offer in `open_iu_plan_task`. Offer acceptance, verified card authority, and Partner operating authority are separate facts. After a verified capture, read the Partner access decision: `purchased: true` proves the contract-backed entitlement, while `allowed: true` also requires the organization’s owner claim, registration, current production certification readiness, usable payment standing, and no hold. Never send PAN, CVC, or a card token in an MCP tool argument. ### V3 agents (`request_payment_authority` and `confirm_payment_authority`) On `/mcp/v3`, call `get` with `{ "kind": "billing" }` to read the selected organization's billing readiness, current Terms and payment status in either text or structured output. It omits card details and browser credentials. **The billing read does not add a secure one-time cardholder handoff.** `paymentAuthority.secureHandoff.available` is `false`. To request a card, call `request_payment_authority`, which returns a confirmation token, then `confirm_payment_authority` with that token to get the hosted link for your organization's cardholder. Deployed `save_billing` integrations that send `paymentAuthority` with `request`, `confirm` or `status` keep working on the MCP server, with the same permissions and response shapes, until a later release removes that earlier shape. The legacy browser credential can be reopened until verification or expiry; it does not provide a one-time exchange with fresh human proof. Its continued availability does not complete the new secure text-only setup flow. Poll current readiness by repeating the billing read every 15–30 seconds. `paymentAuthority.status` and `paymentAuthority.expiresAt` describe the latest capture link; there is no separate status tool. `submission` describes a submitted card form, including `processing`, `verified`, `failed` and `expired` outcomes. Only `paymentAuthority.verified` reports whether the canonical billing account currently has a verified payment method. A request, an opened browser page, and an old verified capture are not equivalent to that current state. Earlier-shape `save_billing` calls with `{ "operation": "payment_authority", "paymentAuthority": { "action": "status" } }` retain their capture-history response: `method`, `status` and `expiresAt`, with the existing `payment_authority_pending`, `payment_authority_opened`, `payment_authority_verified` or `payment_authority_expired` action. Existing permissions still apply, and no capture record returns `NOT_FOUND`. From a Buyer or Seller account, authorized organization admins can accept the current Terms through `save_billing` and verify the result with another billing read. Parent-container sessions can read readiness, but retain their existing write restriction; `nextAction.canCompleteInChat` is `false`. Follow the [text-only billing flow](/v2/setup/v3/tool-reference#plan-and-billing) for the read/write/read sequence and status fields. The new readiness flow stops before card setup. A versioned one-time handoff with fresh cardholder confirmation is still needed. Existing capture-link clients continue to use their deployed contract. Behind the legacy capture page sits one public endpoint, `GET /api/v2/capture-links/{token}`: the hosted page exchanges the link token for the embedded form's credentials (and marks the link opened), and polls it with `?view=status` while verification completes. The token is the sole authorization — unknown tokens return nothing about any organization, and requests are rate-limited. Requesting a link requires org-admin access or an org-scoped service token; the task is available on both the buyer and legacy v2 `storefront` MCP servers because Plan & Billing is org-level regardless of role. Two more `nextAction` values cover the failure/recovery path above: * `RESOLVE_HOLD` — a payment-failure hold is recorded (day +10 in the table above). The current alpha does not enforce it as an authorization gate. Resolved by `POST /api/v2/billing/payment-methods/pay-now`, which retries the outstanding charge on the org's saved card immediately. The route returns `processing`; poll the billing account document and watch `nextAction`/`standing.holds` for confirmation rather than inferring success from the response (the actual recovery is confirmed server-side, asynchronously). * `UPDATE_PAYMENT_METHOD` — a soft nudge shown when the card is inside the expiry lookahead window. It never gates anything on its own; it resolves the same way as `ADD_PAYMENT_METHOD` (replace semantics). `standing.holds` on the billing account document lists any active holds — today only `PAYMENT_FAILED` — each with a `reason` and `createdAt`. ## Related > **[Plan & Billing page](/v2/buyer/billing/plan-and-billing)** > > Where the card is added and managed > **[Get billing account](/v2/buyer/billing/tasks/get-billing-account)** > > The document that carries the payment method and next action # How IU billing works Source: https://docs.apostra.com/v2/buyer/billing/how-iu-billing-works The whole path from a published price to an invoice you can check — with worked examples and a plan calculator This page is the walkthrough: what happens between a published price and a charge on your account, what you do at each step, and **what you can check afterwards**. If you only want the price list, the [Organization IU Rate Card](/v2/buyer/billing/organization-iu-rate-card) has it. > [!NOTE] > > The IU plans and prices below are published and effective in production. > Every eligible organization can review and accept an applicable public plan; > no invitation or enrollment is needed. Accepting a paid plan starts billing > under its terms; every organization with an accepted paid plan is charged. > Existing accepted > media-pricing arrangements are unchanged. Your confirmation shows the exact > version, term, list price, and effective price that apply to you. ## The short version * **Connecting costs nothing.** Connecting an account, discovering it, and mapping it to an advertiser never consume IUs. * **You pay for successful work**, priced by an activity term on the Rate Card you accepted. Attempts, retries, and our own failures are free. * **One plan for the whole organization.** Buyer activity, Seller Account work, and Apostra draw from the same plan and the same wallet. * **You choose your plan; nobody is placed on one.** An organization with no plan is asked to choose, and cannot add accounts until it does. Choosing the pay-as-you-go plan with no monthly commitment counts as choosing. Apostra refuses a Seller's brief responses before a plan exists only where it has turned that on for the Seller; buyer activity is not refused. * **A commitment lowers the per-IU price.** Usage beyond your included IUs bills at the metered rate. * **Every charge traces back to a term you accepted**, and you can read that trail yourself — see [checking our numbers](#checking-our-numbers). ## The seven steps **1. A price is published** Apostra publishes an immutable Rate Card revision: plan sizes, monthly commitments, included IUs, activity prices, currencies, and an effective date. Publishing a revision does not commit anyone to anything. **You do:** nothing. **2. Your Effective Rate Card is resolved** Your organization gets one **Effective Rate Card** — the complete commercial result for you. It starts from a published plan and can include one authorized adjustment: a corporate discount, a promotion or referral code, a negotiated private package, or a customized Offer based on an exact Rate Card plan. **You can check:** the list price, the adjustment, and the effective price appear side by side before you confirm. Discounts do not stack, and only one complete offer is effective at a time. **3. An administrator accepts it** Only a verified organization administrator can accept a plan. An agent, API key, service account, or support session cannot make that commitment on your behalf. Acceptance snapshots the exact agreement, revision, plan, adjustments, prices, actor, and time into an immutable **acceptance record**. A later price change never rewrites it — changes arrive as a new offer you can review. **You do:** review and accept, decline for that revision, or decide later. All three are recorded; none of them is assumed. An organization with no plan in force is asked again at its next sign-in, because a plan is required. **4. You give us a way to collect** Either save a card, or be set up for invoicing on your existing terms. This is separate from accepting a plan. Once you have accepted a paid plan, charging follows your plan's terms; with a card saved, the card is charged automatically. **You do:** add payment details in **Settings → Plan & Billing**. **5. You use IU-rated capabilities** When a capability has a published IU price, successful use draws from your plan. Your wallet shows the balance, what consumed it, and a projection for the rest of the period. Usage draws from expiring credit lots first, then rolled-over IUs, then the period's included allowance, then overage. **You can check:** usage appears on **Usage & credits** as it happens, attributed to the activity that caused it. The activity list and daily trend are measured in IUs and reconcile to the wallet's **IUs used** total. They do not come from Apostra's cost to run an AI provider. A capability can incur an internal provider cost without consuming a priced IU activity; in that case it does not appear as customer IU usage. **6. The cycle closes into an invoice** Each charged month produces one numbered fee invoice: plan commitment, overage as IUs × your rate, and payments applied. Commitment and overage are switched on separately, so an invoice can carry the commitment while overage is still only accruing — see [billing cycle and fee invoices](/v2/buyer/billing/organization-iu-rate-card#billing-cycle-and-fee-invoices). **You can check:** invoice numbers are sequential with no gaps, and an issued invoice's charges never change. Payments are appended as they settle. **7. You reconcile it — and so do we** The invoice, the usage that produced it, and the term that priced it are the same three records we reconcile internally. Nothing on your invoice should be unexplainable from your own reads. **You can check:** see [checking our numbers](#checking-our-numbers). ## What consumes IUs, and what never does **One thing consumes IUs:** successful activity that has a published price on the Rate Card your organization accepted. New Rate Cards use the three published v4 activities on the [Organization IU Rate Card](/v2/buyer/billing/organization-iu-rate-card#complete-activity-schedule): Brief response, Enhanced Reporting, and Apostra media buy. Older accepted Rate Cards retain their own immutable activity terms. Everything below is always free, and stays free whether or not you have a paid plan: * connecting an account or an ad server; * discovering accounts and mapping them to advertisers; * reading, reporting, and exporting your own data, unless you enable Enhanced Reporting for a connected account; * approving something we proposed; * retries, routing, cache hits, and no-op work; * anything that failed; and * work caused by an Apostra defect. The order of operations is fixed: **zero-rate first, then credits, then bill.** If it is ambiguous whether something should be charged, it resolves free. ### The v4 activity schedule New integrations can read the current three-activity catalog from `GET /api/v2/billing/iu-rate-card/v4`. The response carries both the accepted `activityTerms` and the resolved `activitySchedule` so a client can show the exact price and boundary that govern the organization: | Activity code | Customer-facing activity | Starting rate and boundary | | - | - | - | | `COMPLETED_INTERCHANGE_MERCHANDISING_CYCLE` | Brief response | 1 IU for one completed Apostra merchandising cycle | | `SOCIAL_ACCOUNT_INTELLIGENCE_BILLING_PERIOD` | Enhanced Reporting | 4 IUs for one connected account in a billing period after its account-level Enhanced Reporting control is enabled and that account's existing reporting subscription successfully syncs | | `INTERCHANGE_MEDIA_BUY_BILLING_PERIOD` | Apostra media buy | 1 IU for one qualifying media buy in a billing period: for the buyer, a placed, active buy (social included); for the seller, a non-social buy with positive impressions or spend | The unversioned, `/v2`, and `/v3` reads remain compatibility boundaries for clients that use the older activity vocabulary; they do not add the v4 rows. Opt in to `/v4` before depending on these codes. An accepted Rate Card still freezes its catalog version and resolved prices, so reading the newer endpoint never rewrites an earlier agreement. An Apostra media buy is charged to each side whose plan prices it, each under its own plan and in its own billing period, so one buy can carry one buyer unit and one seller unit. * **The buyer is charged 1 IU for each media buy that is active in a billing period, social buys included.** A buy does not need to have delivered yet. It counts once it has left draft and the seller has accepted it: it is confirmed, active, paused, ending, or completed. It does not count while it is a draft or waiting for approval, if the seller rejects it, or if it is canceled before its flight starts. * **A buy running across a billing-period boundary counts in each period it is active.** A buy that was already running is not charged for a new period it only spills into for less than 24 hours. Billing periods follow your organization's IU billing cycle and are measured in UTC, so a flight that ends at 7 PM London time on the first day of a period (18:00 UTC in summer) is not charged for that period. The first period a buy is active always counts, however short. * **The seller is charged 1 IU for each non-social media buy with positive impressions or spend in a billing period.** * **An organization that buys its own inventory is charged once.** For a non-social buy that is the seller's charge, so it applies only in a billing period in which the buy has positive impressions or spend; a buy of your own inventory that has not delivered in a period is not charged for that period. A social buy of your own inventory is charged once, as the buyer's charge. * **A buy that carries a percentage-of-media fee above 0%** (for example, a buy created at 8% under earlier terms) finishes under that fee and is not charged to the buyer as an IU. A buy created at 0% is charged normally. Brief responses are charged to the seller. Enhanced Reporting is charged to the organization that owns the connected account. For the seller's media-buy unit, the latest authoritative delivery totals control the result. If both impressions and spend become zero before invoicing, the provisional IU is removed. The buyer's unit is checked against the buy's status and dates instead: if the seller rejects the buy, or it is canceled before its flight started, the buyer's provisional IU is removed before invoicing. After invoicing, the same correction produces an auditable one-IU credit or reversal against the original period. A later correction can reinstate that same unit once, but cannot create a second unit for the buy and period. See the [complete activity schedule](/v2/buyer/billing/organization-iu-rate-card#complete-activity-schedule) for all qualification details. ### What a plan with no monthly commitment charges for A plan with no monthly commitment, such as Connect & Buy for a buyer or Storefront agent for a seller, charges nothing to hold. You pay only when an activity in the plan's schedule happens, and only for an activity the Rate Card publishes with a price. The plan chooser lists these charges from the same schedule, so it never shows a charge, or a price, that is not on the Rate Card revision offered to you. * **As a buyer**, you pay when a media buy you place is active in a billing period, whether or not it has delivered yet, and when you turn on Enhanced Reporting for a connected account. * **As a seller on Storefront agent**, you pay for every brief you run and for each non-social media buy that delivers, when the plan charges for them. The Rate Card's schedule says which of these a plan charges for. * **Listing is free.** It has no monthly commitment and no IU price, so the chooser lists no charges for it. An activity with no published price on your Rate Card is not charged, so it does not appear. Read the exact units and prices for your organization from the [Organization IU Rate Card](/v2/buyer/billing/organization-iu-rate-card#complete-activity-schedule) or `GET /api/v2/billing/iu-rate-card/v4`. ## Plan shapes | Plan | Monthly commitment | Included IUs | Price per included IU | Overage | | - | -: | -: | -: | -: | | Pay as you go | \$0 | 0 | — | \$5.00 / IU | | 250 | \$1,000 | 250 | \$4.00 | \$5.00 / IU | | 1,000 | \$3,000 | 1,000 | \$3.00 | \$5.00 / IU | Two break-even points fall out of those numbers, and they are worth knowing before you choose: * Above **200 IUs a month**, the 250 plan costs less than pay as you go. * Above **650 IUs a month**, the 1,000 plan costs less than the 250 plan. On a committed plan, unused included IUs carry into the next monthly period only, up to **50% of that next period's included quantity** — at most 125 IUs on the 250 plan, 500 on the 1,000 plan. Carried IUs expire after that one period and never accumulate again. Pay as you go has no allocation to carry. **This is the standard list, not everyone's price.** Your Effective Rate Card is the authority. An authorized corporate discount, a promotion or referral code, a negotiated private package, or a customized Offer replaces these figures, and Plan & Billing shows the list price and the effective price side by side before you confirm. If your organization has been quoted something else, that quote — once it is an Effective Rate Card you accept — governs your charges, not this table. **Prices are set per currency, never converted.** The figures above are the USD list. A Rate Card revision carries a separately published price for each supported currency, so a plan in EUR, GBP, or AUD is **not** the USD figure at an exchange rate — it is its own published number. Your organization's currency is fixed on the revision you accept, and your invoice, charge, and wallet all use it. Read the numbers on your own card, not these, if your card is not in USD. **And these are today's standard figures.** This table describes the standard model as agreed on 2026-07-22. Standard prices can change, and when they do they arrive as a **new published revision** — never as an edit to a revision anyone accepted. Your acceptance record snapshots the exact version, term, and prices you agreed to, so a change to this table does not change what you are paying: you would see a successor offer and decide whether to accept it. ## Calculator Enter the IUs you expect to use in a month. The cost of each plan is shown, with the cheapest one marked. Intelligence Units used in a month Overage bills at \$5.00/IU on every plan. This estimate assumes nothing carried in from the previous period — rollover only reduces the total. It does not include any corporate discount, promotion, private package, or platform-license fee on your Effective Rate Card, or any media spend. This computes the **standard USD list** and is not a quote. If your organization has a corporate discount, a negotiated package, a customized Offer, or a card in another currency, put the numbers from your own Effective Rate Card into the formula below — prices are published per currency rather than converted from USD. The formula, if you would rather compute it yourself or have an agent do it: ``` cost = commitment + max(0, used − (included + carried_in)) × 5.00 ``` `carried_in` is the smaller of last period's unused included IUs and half of this period's included quantity. ## Worked examples > **A seller trying things out — pay as you go** > > Uses 12 IUs in the month. > > ``` > commitment $0 > metered 12 × $5.00 $60.00 > ────────────────────────── > invoice $60.00 > ``` > > No commitment, no included allocation, nothing to roll over. Every IU is \$5.00. > **A buyer who went over — the 250 plan** > > Committed to 250 IUs for \$1,000. Uses 310 IUs, with nothing carried in. > > ``` > commitment $1,000.00 > included 250 IUs — covered > overage 60 × $5.00 $300.00 > ──────────────────────────────── > invoice $1,300.00 > effective per IU $4.19 > ``` > > Because they are on a card, the \$300 of overage is charged when it crosses the > account's threshold during the month rather than arriving as a surprise at close. > The remainder settles when the cycle closes. > **A quiet month, then a busy one — rollover** > > On the 250 plan. Period 1 uses 180 of 250 IUs. > > ``` > period 1 used 180 of 250 unused 70 > carry min(70, 125) = 70 IUs into period 2 > ``` > > Period 2 has 250 included + 70 carried = 320 IUs before any overage. If they use > 300, the invoice is the \$1,000 commitment and no overage. The 20 IUs left over > from the carried lot expire — carried IUs do not carry again. > **Your first plan — the setup credit** > > Whether you receive a setup credit depends on the Rate Card you accept. **Plan & > Billing shows the credit, if there is one, before you accept.** A Rate Card that > carries no setup credit grants none, and the rest of this section does not apply. > **The current plans carry no setup credit**, so accepting one grants none. An > organization that already holds a credit keeps it until it expires, whichever > Rate Card it was granted under. > > When the Rate Card carries one, a seller billing organization receives the amount > and validity the Rate Card states on its first IU plan acceptance. You do not have to be a new customer — if you have been with us a > while and are accepting an IU plan for the first time, the credit is yours too. > > It can be spent on **any IU-priced activity**. The window is a fixed number of days > from your signup commit, as the Rate Card states, not a number of billing periods, so it does not shift with > your billing cycle and you never need to know where your cycle boundary falls. > **Usage & credits shows the exact expiry date.** > > One credit per billing organization: later Seller Accounts do not create another. It > does not roll over, has no cash value, and does not stack. ## Reading your invoice Every charged month produces one numbered fee invoice on **Settings → Plan & Billing → Payment & invoices**, showing: * the plan commitment, at the price fixed in your accepted terms; * overage as a quantity of IUs multiplied by your plan's rate; * payments applied, as they settle. Two properties are worth relying on. **Invoice numbers are sequential with no gaps**, so you can tell whether you have seen every invoice. And **an issued invoice's charges never change** — a correction arrives as its own document, never as an edit to one you have already received. Organizations paying by card are charged automatically. Invoiced organizations receive the fee invoice only and pay on their existing terms; no card charge is made. ### When a card payment settles before the cycle closes A card payment can succeed during the month before its fee invoice is issued. The payment stays attached to that exact billing cycle and is recorded against what you owe Apostra for IU fees, so the amount still owed falls as soon as the payment succeeds. A retry or **pay now** recovery replaces the failed attempt with the successful payment on the same cycle; it does not create a second cycle or detach the payment from its usage. When the cycle closes, Apostra issues its invoice from the frozen usage drawdown and accepted plan terms, including payment applications for successful charges. Apostra's billing checks compare those records with successful charges and invoice arithmetic for differences. If Apostra pauses charging after a payment was collected, no new charge is made, but that paid cycle still closes and the accounting checks continue so the payment is not left without an invoice. For day-to-day billing, nothing changes: finance teams can use the numbered invoice and its applied payments as their audit trail, while smaller teams can continue using Plan & Billing without a new setup step. ## If a payment fails A published recovery ladder applies: automatic retries on a schedule, email notices, and a self-serve **pay now** action that clears the failed-payment status as soon as it succeeds. Plan & Billing reports an unresolved dunning-backed hold; reaching the account's cap records a separate internal hold. In the current alpha, neither hold is consulted by a production authorization path, so it does not automatically block new paid platform activity. **Viewing your data, reporting, exporting, and fixing your payment method remain available.** ## Checking our numbers You should not have to take an invoice on trust. These reads are the same records the charge was built from: | To check | Read | | - | - | | The exact v4 terms you accepted | `GET /api/v2/billing/iu-rate-card/v4` | | Your plan, standing, and payment methods | `GET /api/v2/billing/account` | | Your invoices and their line items | `GET /api/v2/billing/fee-invoices` and `/fee-invoices/{invoiceNumber}` | | Invoice activity as it happens | `GET /api/v2/billing/invoice-activity` | Authenticate with `Authorization: Bearer $SCOPE3_API_KEY`. Agents should use the typed `open_iu_plan_task` and `get_billing_account` tools rather than reconstructing this from generic calls. The reconciliation you can do yourself: **the IU quantity on an invoice line should equal the usage attributed to that activity for the period, priced at the rate on your accepted terms.** If those three do not agree, that is a defect on our side — tell us and we will explain the difference rather than adjust the invoice quietly. ## Two bills, kept separate IUs price platform activity. Media spend and its accepted contract fee terms are a separate commercial stream on a separate bill. Plan & Billing can show both, because an organization may owe both, but neither replaces or silently amends the other. ## Related > **[Organization IU Rate Card](/v2/buyer/billing/organization-iu-rate-card)** > > The price list, adjustments, rollover, and acceptance in detail > **[Plan & Billing](/v2/buyer/billing/plan-and-billing)** > > The surface where you review terms, usage, and invoices > **[Pay by card](/v2/buyer/billing/concepts/pay-by-card)** > > Saving a card, thresholds, and the recovery ladder > **[Media billing](/v2/buyer/billing/overview)** > > How media spend is invoiced, and where remittance details come from # Media billing entity configuration Source: https://docs.apostra.com/v2/buyer/billing/media-billing Configure and preview the legal entity intended for consolidated media invoices Media billing entities let an organization configure the legal recipient of its media invoices. They do not decide who invoices a transaction: that comes from its billing party (`agent` vs `operator` vs `advertiser`). * **Direct billing (`operator`).** When you choose [direct billing](/v2/buyer/storefronts/tasks/accept-direct-billing) for a seller, Apostra sends the advertiser's resolved entity to that seller when it asks the seller to open your account, so the seller knows whom to invoice. An advertiser with no resolved entity cannot open a directly billed account. * **Consolidated billing (`agent`).** The entity is the intended recipient of Apostra's consolidated media invoice. > [!WARNING] > > For consolidated billing this surface is still configuration and preview > only: the media-invoice producer does not yet consume the resolution, so > creating or attaching an entity does not by itself change invoice issuance or > delivery. ## Two streams, kept separate * **Apostra fees** — platform fees and IU bundles/overage when your organization's commercial terms enable them. * **Media billing** — legal-recipient configuration for media transactions, sent to directly billing sellers and previewed for consolidated invoices. This page covers that model. A surface, invoice, or line item is always identifiably one stream or the other — never a blended "billing." See [Plan & Billing page](/v2/buyer/billing/plan-and-billing) for where each stream renders. ## The resolution hierarchy Media billing entities are configured at up to three levels and resolved **most-specific-first**. The presence of an entity at a level is what makes the preview resolver select it: | Level | Rule | | - | - | | **Advertiser** | If an advertiser has its own billing entity attached, the preview resolves to that entity. | | **Account** | Else, if the advertiser's child account has an attached entity, the preview resolves there. | | **Organization** | Else, the preview uses the organization's primary entity when one exists; without one it returns `resolvedVia: "none"`. | "Most specific wins": an advertiser's own entity beats its account's, which beats the organization default. ## Country is metadata, not a routing key A billing entity's country is stored identity metadata. Currency is configured independently; this preview does not derive tax treatment, currency, or address format from country. **Country never participates in resolution.** Matching is purely structural (advertiser → account → organization); it never looks at where media delivered or where an advertiser operates. To preview different entity matches by market, model each market with a **market-scoped advertiser** (for example, "Acme Beverages South Africa") and attach it to the corresponding configured entity. This matches how agencies structure market P\&Ls without introducing a separate geo-resolution key. The invoice producer does not consume this preview yet. ## Entities A media billing entity carries: * **Entity name** — the legal entity's name * **Country** — stored ISO 3166-1 alpha-2 identity metadata * **Currency** — ISO 4217 currency stored for the configuration preview * **Billing email(s)** — invoicing contact * **Address** — street, city, state/region, postal code * **Tax ID** — optional (e.g. VAT number, EIN) When an organization has entities, exactly one can be **primary** as the organization-level fallback. Your first entity automatically becomes primary; promoting a different entity demotes the previous one in the same step. The current product does not require an entity before a buy and can resolve to `none` when the organization has not configured one. ## Attachments An attachment associates one advertiser or one account with a specific entity for resolution preview — exactly one of the two per attachment. ## In the product The **Media billing** tab of the [Plan & Billing page](/v2/buyer/billing/plan-and-billing) is where this model renders. It appears for organizations with buyer capability (a pure seller can have platform fees and payouts, but not media-billing configuration) and shows: * **Billing entities** — your entities as cards: name, country, currency, billing email, and an "Org default" tag on the primary. An entity missing its tax ID is flagged. When you have no entities yet, the tab explains that no entity exists yet and offers the setup action. * **Resolution** — one row per attachment (advertiser or account → entity), plus an "Everything else" row showing the organization default when one exists. * **Which configured entity matches?** — an inline inspector: pick any advertiser or account and see the three resolution steps (advertiser attachment, account attachment, organization default) with the matching step highlighted, then the resolved entity with its country and currency—or `none` when nothing is configured. The tab writes through the same admin-gated API documented below. UI and API share the same configuration rules; neither path is wired into invoice production yet. ## For agents > **List media billing entities** > > `GET /api/v2/billing/media-entities` > **Create media billing entity** > > `POST /api/v2/billing/media-entities` (admin) > **Update media billing entity** > > `PUT /api/v2/billing/media-entities/{entityId}` (admin) > **Delete media billing entity** > > `DELETE /api/v2/billing/media-entities/{entityId}` (admin) > **List attachments** > > `GET /api/v2/billing/media-entities/attachments` > **Create attachment** > > `POST /api/v2/billing/media-entities/attachments` (admin) > **Delete attachment** > > `DELETE /api/v2/billing/media-entities/attachments/{attachmentId}` (admin) > **Resolve media billing entity** > > `GET /api/v2/billing/media-entities/resolve?advertiserId=` or `?childCustomerId=` The resolve endpoint previews which configured entity matches an advertiser or account: it returns the resolved entity plus `resolvedVia` (`advertiser` | `account` | `org`), or `resolvedVia: "none"` when your organization has no entity at all yet. Admin-gated writes are enforced server-side, so any future client shares the same rule as the REST API. Media billing always belongs to the organization. A request made while you work in one of the organization's accounts reads and changes the organization's entities and attachments, never a separate set for that account, and it requires you to be an administrator of the organization itself. Administering only the account is not enough. Entities and attachments an account configured for itself before this still show there, are updated or removed from there, and take precedence for that account: its own primary entity is used before the organization's, and the preview reports it as `resolvedVia: "account"`. New entities and attachments always belong to the organization. ## Related > **[Plan & Billing page](/v2/buyer/billing/plan-and-billing)** > > Where interchange fees and media billing both render > **[Billing overview](/v2/buyer/billing/overview)** > > How invoicing and remittance work # Organization IU Rate Card Source: https://docs.apostra.com/v2/buyer/billing/organization-iu-rate-card How one Effective Rate Card defines pricing, access, rollover, and activity terms across your organization's workloads The Organization IU Rate Card is the price list your organization will use for Intelligence Unit (IU) activity across Apostra. It applies once at the billing-organization level: buyer activity, Seller Account work, Apostra assistant usage, and other IU-rated capabilities draw from the same plan and wallet. > [!NOTE] > > The standard IU Rate Card is published and effective in production. Every > eligible organization can review and accept an applicable public plan; no > invitation or enrollment is needed. Plan & Billing shows the exact effective > revision. After you accept, Plan & Billing may show the accepted included-IU > allocation, observed usage, and a display-only forecast. The accepted Rate > Card has the complete current activity schedule. An admitted published > activity may move the displayed IU balance and appear as usage. Accepting a > paid plan starts billing under that plan's terms: every organization with an > accepted paid plan is charged, with no separate enrollment. Entitlement > enforcement remains separately controlled. Existing accepted > media-pricing arrangements are unchanged. An IU Rate Card is not a > prerequisite for buyer setup or media buying. ## The simple model * **Connect at no cost.** Connecting an account, discovering it, and mapping it to an advertiser do not consume IUs. * **Pay for IU-rated use.** When a capability has a published IU price, its usage draws from your organization's one IU plan and wallet. * **Commit for a volume discount.** A monthly commitment lowers the effective price of the included IUs. Overage uses the metered rate. * **Choose the package that matches the relationship.** A Rate Card plan can combine pricing and IUs with contracted product rights, premium support, and proposal defaults while the same wallet continues to price metered usage. There is no separate plan for buyer activity, Seller Account work, Apostra assistant usage, or BYOBA. BYOBA—bringing or building your own buyer agent—is simply one way to use the platform. ## Buying with Apostra Advertisers can connect media accounts and create campaigns for free, and Apostra takes 0% of media. Each Apostra media buy you place is a metered activity, charged in IUs for each billing period in which it is active; see the [complete activity schedule](#complete-activity-schedule) for the rate and when a buy counts. You can work in Apostra, through your own agent, or in your chat of choice. Availability depends on the accounts, integrations, and sellers that support the request. Optional Intelligence for planning, recommendations, and enhanced reporting is currently in beta. The activity catalog sets Enhanced Reporting at 4 IUs per connected account per billing period, and no plan in the current template waives it. Customer-facing plan pricing for other buy-side Intelligence activities is coming soon. Apostra shows the resolved price before you opt in, and eligible activity draws from one IU wallet shared across your organization. Enterprise is a contact-us offer. A scoped agreement can include the Apostra team operating Apostra for you, custom integrations, cooperative agent development with forward-deployed engineers, dedicated human support, and enterprise governance controls. The proposal or statement of work defines the services, responsibilities, support commitments, and price. Ordinary product support through Apostra remains part of the product. Connect & Buy covers one Buyer Account. **Business Manager** is for agencies and teams that run many brands: it covers up to 100 Buyer Accounts under one organization, on one IU bill. It is a public plan at the same price as Connect & Buy, so any buyer organization can choose it. These are buy-side choices, not Seller Account plans. The Effective Rate Card governs any IU activity your organization accepts, while a separate Enterprise agreement governs contracted services. ## Before you choose a plan Nobody is placed on a plan. An organization with no accepted plan in force, because nobody has accepted one yet or the accepted plan ended, has not chosen a plan yet: * Its next action in Plan & Billing is **Select an IU plan**. Seller administrators are also asked to choose at every sign-in. * It cannot add accounts until it has chosen a plan, because a plan's account limit is what allows another account. The refusal says to choose a plan in Plan & Billing. * A Seller's brief responses are refused with the same next step only where Apostra has turned that enforcement on for the Seller. It is not on for everyone yet, and buyer activity is not refused. Reading, setting up, and work that is always free stay open. Choosing the pay-as-you-go plan with no monthly commitment counts as choosing. The chooser puts first the plan that fits what your organization holds today: * **Connect & Buy** for Buyer Accounts. * **Listing** for Listing-only storefronts. * **Storefront agent** for Agentic Media Company storefronts, with or without Buyer Accounts. * **Developer** for an organization whose accounts are all Developer accounts. That plan is only a suggestion. Your organization is not on it, nothing is billed under it, and it does not decide which accounts you can add. Accepting it makes it your organization's plan. The v4 IU Rate Card read reports the suggestion as `suggestedPlan` and the next step as `nextAction: SELECT_IU_PLAN`. `suggestedPlan` is `null` while an accepted plan is in force, and for an organization no such plan covers, such as one holding Buyer Accounts beside a Listing-only storefront. That organization uses **Get in touch**. The earlier `defaultPlan` field is deprecated and always `null`: nothing governs an organization by default any more. ## Building your own agents An organization whose accounts are all Developer accounts, where its agents live, can accept the public **Developer** plan. It has no monthly commitment. It covers registering, testing and certifying the organization's own agents, and the Agent page. IU-rated activity, if any, uses the standard overage rate of \$5, €4.50 or £4 per IU. Developer does not include buyer or seller tools. Operating agents for other companies needs the **Certified Agent Partner Program**, and adding a Buyer or Seller Account needs a plan that covers it. Developer accounts are in a closed preview. ## One Effective Rate Card The Rate Card shown for acceptance is the complete commercial result for your organization. Each plan on an exact published Rate Card revision is a complete commercial package: its pricing and IU allowance, entitlements, support, and proposal defaults travel together. It can incorporate one authorized adjustment source: * a corporate percentage adjustment; * a promotion or referral code; * a negotiated private package, such as 500 IUs for \$500; or * an explicitly customized version of one published plan. These are not separate terms scattered across the product. A Commercial Offer or code distributes the terms; the resulting **Effective Rate Card** is what you review, accept, and later see in Plan & Billing. It is versioned and snapshotted with the acceptance record so a later edit cannot rewrite what you accepted. Entitlements are enforced separately from billing, but the access bundle and support included in a plan appear beside its price. A new Offer selects one exact Rate Card revision and plan. It either uses the standard plan terms or records an explicit customization with its reason and exact differences. The Offer snapshots the resolved bundle revision, every canonical feature key it contains, any premium-support package, and the resolved proposal. A later plan or bundle revision cannot change an Offer already issued or a term already accepted. Draft revisions also preserve their commercial lineage. A successor identifies the published revision it carries forward; a deliberate fresh start records why no predecessor terms apply. If an operator abandons an unpublished draft, Apostra retains its terms, lineage, actor, timestamp, and reason as immutable audit history. An abandoned draft is never published, offered for acceptance, or used to price activity, and it does not alter any revision your organization accepted. ## Choosing a seller plan The plan chooser groups seller plans under **Listing** and **Merchandising**. Listing choices include **Listing** and **Listing + Distribution**. Merchandising choices are the Agentic Media Company plans: **Storefront agent** and **Storefront agent + Distribution**. The displayed price and included access always come from the published revision offered to your organization. A plan name does not set a price or change your accepted terms. The available choices follow your organization's active products. A seller that only lists its Sales Agent sees Listing choices. A seller whose storefronts are all on Agentic Media Company can see both groups, because a Listing plan also covers an Agentic Media Company storefront. A public plan for an organization with several products must cover all of them, so an organization that also holds Buyer Accounts or a sales agent offering sees only Merchandising choices. If no public plan fits, the chooser offers **Get in touch**. Your plan also decides which accounts the organization can hold: | Plans | Accounts they hold | | - | - | | Buyer plans: Connect & Buy, Business Manager, Enterprise | Buyer Accounts only. Connect & Buy holds one; Business Manager holds up to 100; Enterprise holds up to 5 | | Listing plans: Listing, Listing + Distribution, Enterprise — Listing | Seller Accounts only, on either seller product. No additional accounts | | Global Market Maker | Seller Accounts on either seller product, together with Buyer Accounts and a sales agent offering; up to 5 accounts | | Certified Agent Developer | A sales agent offering, plus one Seller Account with a Listing-only storefront for testing | | Agentic Media Company plans: Storefront agent, Storefront agent + Distribution, Enterprise — Storefront agent | Seller Accounts on Agentic Media Company, together with Buyer Accounts and a sales agent offering. Storefront agent holds one account; Storefront agent + Distribution and Enterprise — Storefront agent hold up to 5 | | Developer | Developer accounts only | These account limits are part of the plan terms in the current Rate Card revision. Archived accounts do not count toward them, and restoring an archived account needs a free slot. An organization whose plan was accepted under an earlier revision keeps those earlier terms until it accepts the current one. An organization that already holds more accounts than its plan's limit keeps every one of them; it cannot add another until it is on a plan with room. See [Organization account capacity](/v2/account-capacity). Adding a Buyer Account to a Seller organization on a Listing plan would make it both buy and sell. It needs an Agentic Media Company plan, and the change is refused until the organization has one. Global Market Maker is the one exception: a private seller plan that also covers Buyer Accounts and a sales agent offering, even when the storefront is Listing only. See [Which plan holds which accounts](/v2/setup/account-access#which-plan-holds-which-accounts). **Enterprise — Listing** and **Enterprise — Storefront agent** are contact choices for the applicable Listing or Merchandising group. They have no public price and cannot be accepted as a zero-priced plan. Any private Commercial Offer prepared for your organization returns to this same chooser for review and acceptance. ## Getting help with a plan Select **Get in touch** in plan selection, including when public plans are available. You can also open the chooser from **Plan & Billing**. A billing administrator must send the request from their own account. Staff impersonation and service tokens cannot send it on your behalf. Add an optional message of up to 1,000 characters and select **Send request**. Replies stay in the app. Your request is tied to the plans you were shown and the plan choice you asked about. It does not accept a plan, change pricing or access, or promise a response deadline. Retries of the same current request are attached to the same commercial request. After **Request received**, follow progress in **Your requests**, under **Help** in Apostra, when tracking is available. If tracking is temporarily unavailable, keep the reference when asking for an update. **Back to plans** reloads the current offer. If the available plans changed, no request is sent. Review the current plans, then send again. During signup, **Get in touch** explains how to continue after account creation. Nothing is sent anonymously. Finish creating your account, then send the request from **Plan & Billing**. Seller signup does not ask for your seller product separately. It shows every public plan that applies to a Listing-only Seller or to an Agentic Media Company, and the plan you accept sets your starting product. A Listing plan, or no paid plan, starts you as Listing only. A Storefront agent plan starts you as an Agentic Media Company, because Merchandising comes with the Merchandising plan. After signup, the product follows your plan: change it in **Settings → Plan & Billing**. A paid plan you choose while creating a Seller account is accepted as part of that signup, once your email address is verified, even though your Seller Storefront is created a moment later in the same signup. The plan takes effect when signup completes. Only an accepted plan sets your starting product. If your email is not yet verified, or the plan you picked is no longer offered when you finish (for example because the Rate Card changed while you were signing up), signup finishes on the free pass-through terms as Listing only and asks you to choose a plan again. To choose a Storefront agent plan after that, open **Settings → Selling mode** and choose **Compare plans**, which records your intent to merchandise so the Merchandising plans are offered. ## Rate Card plans Rate Card revisions can contain public plans and private negotiated packages. The seller templates use **Listing**, **Listing + Distribution**, **Storefront agent**, and **Storefront agent + Distribution**. A template is an unpublished starting point: an operator must approve and publish an exact revision before it can be offered. Buyer and other product plans may also appear when they cover your organization’s products. Saving, enabling, and evaluating AI Business Rules is not an IU-rated activity. No plan in the current template waives Enhanced Reporting; it carries the catalog default of 4 IUs. Listing paths waive brief responses and qualifying media buys. Merchandising paths—the Storefront agent plans—keep the catalog's 1-IU defaults for brief responses and qualifying media buys. The current production Rate Card publishes **Listing** and **Storefront agent** for sellers and **Connect & Buy** for buyers. Existing accepted plans remain readable and enforceable. The commitment, included quantity, overage rate, activity terms, product rights, support, and proposal defaults belong to the selected plan. The standard USD commitments, included quantities, overage rates, and break-even points are on one page — **[How IU billing works](/v2/buyer/billing/how-iu-billing-works)** — along with a calculator and worked examples. That page is the standard USD list; this page is about the exact revision and plan that govern *your organization*. Usage only provides no included monthly IU allocation. Once you accept a plan, commitments are billed at the start of each monthly period; metered usage and committed-plan overage are billed after the period. The standard plans are published in production. Your confirmation always shows the exact version, term, list price, and effective price that apply to you. The same Rate Card includes the complete activity schedule; several rows remain under calibration and are marked non-billable below. **Whatever the standard list says, your Effective Rate Card is the authority.** An authorized corporate discount, a promotion or referral code, a negotiated private package, or a customized Offer replaces the standard figures, and Plan & Billing shows the list price and the effective price side by side before you confirm. If your organization has been quoted something else, that quote — once it is an Effective Rate Card you accept — is what governs your charges. Prices are also published per currency rather than converted, so a card in EUR, GBP, or AUD carries its own published numbers. ## Pricing adjustments and private offers If Apostra has authorized a corporate discount for your organization, it applies once across the plan—not separately to buyer and Seller Account activity. Before you confirm, Plan & Billing shows the list price, discount, and effective price. Discounts do not stack. A negotiated package is shown as its own private Effective Rate Card, not as a second hidden discount. For example, an Offer for 500 IUs at \$500 shows that commitment, included quantity, and overage price directly. It also shows the selected plan's entitlement bundle, support, and activities that are included rather than IU-rated. The package price is the complete subscription economics; there is no second “platform access” fee. Only one complete Offer is effective for acceptance at a time. A later discount or Rate Card change never rewrites an accepted term. Changes apply through a new offer or successor term that you can review. ## Product access and premium support When a plan includes product access, the Offer shows an immutable bundle identity and revision plus the exact feature keys included. Apostra validates those keys against its canonical feature registry before issuing the offer. An unregistered feature, an alias in place of its canonical key, or a feature that is not contract-gated cannot be issued as an authoritative entitlement bundle. A plan can also include premium support. The Offer preserves one of two simple forms: unlimited premium support, or a monthly premium package with included hours. Additional support follows the Enterprise agreement or statement of work; it is not an automatically metered IU activity. Coverage windows, severity targets, onboarding allowances, and escalation policies are not separate commercial fields. The proposal's **Included** scope and the structured premium-support package are separate terms. The customer view shows every included-scope line exactly as the server-authored proposal records it, while it shows a named support package only when the Offer carries one. A scope line must not be silently converted into a support package, or vice versa. If its meaning is unclear, the issuer and customer must resolve it in the audited Offer preview before acceptance; a Task does not infer a support commitment from ambiguous wording. Acceptance preserves these exact product and service terms in the acceptance record and creates each contracted product right against that same record atomically. Runtime-gated rights become usable only while the accepted terms and governing contract are in force. The paid right to operate agents for clients is one such runtime right; sales-agent registration access, registration, deterministic certification, and certification evidence are free and cannot be sold in an entitlement bundle. Setup rights remain explicit fulfillment obligations; acceptance does not silently assign a support owner or bypass payment standing for chargeable operations. Each plan also declares the default feature profile that presents those rights as one coherent product experience. The profile is not a second plan or an independent grant of access: Apostra validates that every entitlement the profile requires is included and active. When you accept a plan, its entitlements and default profile are provisioned together; if they are incompatible, neither change is applied. An approved beta or operator-assisted profile transition follows the same rule and cannot add a commercial right that the organization does not already hold. Renewals preserve the accepted profile revision with the successor term rather than silently adopting a later profile definition. ## Rollover On a committed plan, unused included IUs can carry into the next monthly period up to 50% of that next period's included quantity: * the 250 plan can carry at most 125 IUs; and * the 1,000 plan can carry at most 500 IUs. Carried IUs expire after that one successor period and cannot accumulate again. Pay as you go provides no monthly allocation to roll over. Setup and promotional credits never roll over. ## Enhanced Reporting Connecting and mapping an account costs nothing. The catalog default for **Enhanced Reporting** is **4 IUs per connected account per billing period** after its account-level control is enabled and that exact account's existing reporting subscription completes a successful sync. The control is set for one exact connected account; it does not enable sibling accounts or change ordinary connection and campaign features. An enabled account without a reporting subscription or successful sync draws no IUs. Enhanced Reporting is a required term on every new Rate Card, and no plan in the current template waives it. A Rate Card that does not mention Enhanced Reporting carries the catalog default. Ordinary connection, synchronization, and campaign actions are included. This is an activity term, not a buyer plan. The selected plan resolves the catalog default before acceptance. For example, five eligible accounts would have a 20-IU total per billing period at the catalog default. A Rate Card or plan that sets another price, or waives Enhanced Reporting, changes that total. > [!NOTE] > > The catalog default of 4 IUs applies unless your accepted Rate Card or plan > sets another price or waives the activity. **Usage & credits** applies the > resolved terms from your accepted Rate Card. A future customized Offer would > have to show any different activity term before acceptance. Accepting a paid > plan is what starts billing—see [Billing cycle and fee > invoices](#billing-cycle-and-fee-invoices). > [!NOTE] > > The 4-IU count is the current standard, not a final one. Your accepted version > is what applies to you: a change reaches you only through a successor Rate > Card that you accept, and it never reprices a period already metered. ## Complete activity schedule Every Rate Card shows the same complete activity list. Each row starts with the catalog unit and price. The selected plan then applies its activity terms, and a Commercial Offer can apply an authorized customer-specific adjustment after that. Either layer can set a custom IU price or waive the activity; an activity without an override keeps the preceding value. Acceptance freezes the resolved result, so later catalog, plan, or Offer changes cannot rewrite it. The row status is part of the Rate Card. **Published** means an admitted meter may debit the accepted IU balance. The v4 catalog contains exactly the three published rows below. The accepted Rate Card freezes its catalog version and resolved rows, so later catalog changes do not rewrite the agreement. Existing accepted v0-v3 Rate Cards keep their earlier catalog and terms. Historical agreements that predate the complete schedule retain only the activity charges they explicitly contained. Showing the newer catalog does not add a charge to one of those agreements; an activity absent from that immutable snapshot stays absent until a successor Offer is accepted. | Activity | Starting rate | Included boundary | | - | -: | - | | Brief response | 1 IU/response | One completed Apostra merchandising cycle | | Enhanced Reporting | 4 IUs/account billing period | One exact connected account per billing period after its control is enabled and its existing reporting subscription completes a successful sync; ordinary connection, synchronization, and campaign actions are included | | Apostra media buy | 1 IU/media buy billing period | Buyer: one Apostra media buy per billing period in which it is active, social included, once the seller has accepted it, whether or not it has delivered. Seller: one non-social Apostra media buy per billing period with positive impressions or spend | An Apostra media buy is charged to each side whose plan prices it, so the buyer and the seller can each be charged 1 IU for the same buy and billing period. An organization buying its own inventory is charged once: for a non-social buy that is the seller's charge, which needs positive impressions or spend in the period, so a buy of your own inventory that has not delivered in a period is not charged for it; a social buy of your own inventory is charged once, as the buyer's charge. For the buyer, a media buy counts once it has left draft and the seller has accepted it: it is confirmed, active, paused, ending, or completed. It does not count while it is a draft or waiting for approval, if the seller rejects it, or if it is canceled before its flight starts. A buy counts in each billing period in which it is active, so a buy running across a period boundary counts in each period. Billing periods follow your organization's IU billing cycle and are measured in UTC. A buy that was already running is not charged for a new period it only spills into for less than 24 hours (a flight ending at 7 PM London time on the first day of a period does not count for that period); the first period a buy is active always counts. A buy that carries a percentage-of-media fee above 0% is not charged to the buyer as an IU; a buy created at 0% is charged normally. For the seller, qualification uses the latest authoritative delivery totals for that buy and billing period, and social buys are not charged. If a correction leaves both impressions and spend at zero before invoicing, the provisional IU is removed. The buyer's unit is instead removed before invoicing if the seller rejects the buy or it is canceled before its flight started. If the correction arrives after invoicing, it is handled through the normal auditable one-IU credit or reversal process against the original period. A later positive correction can reinstate the same unit once, but cannot create an additional unit for that buy and period. For what a plan with no monthly commitment charges for, by buyer and seller, see [how IU billing works](/v2/buyer/billing/how-iu-billing-works#what-a-plan-with-no-monthly-commitment-charges-for). Customer approval actions, retries, routing, caching, and internal model calls never create additional charges. Human expert support and Apostra-operated modular-source fulfillment are contracted managed services, not metered IU activities. Custom modular-source composition is included with Premium and Enterprise through the Merchandising profile, not a per-source usage charge or separate entitlement. ## New Seller Account setup credit A Rate Card may or may not carry a setup credit. **Plan & Billing shows the credit, if there is one, before you accept**, and a Rate Card that carries none grants none. **The current plans carry no setup credit.** A credit belongs to the Rate Card revision it was published with, so a later revision can drop it without affecting a credit an organization already holds: that credit stays until it expires. A signup code works whether or not the Rate Card carries a setup credit: its price or access adjustment and its attribution apply either way, and with no credit it grants no IUs. When the effective Rate Card carries a setup credit, a seller billing organization receives the amount of IUs the Rate Card states on its first IU plan acceptance. You do not need to be a new customer: if your organization has been with us for a while and is accepting an IU plan for the first time, the credit is yours too. The credit is valid for the number of days the Rate Card states, and it can be spent on **any IU-priced activity** — it is not limited to setup work. Those days run from your signup commit as a fixed window, not a number of billing periods, so it does not move with your billing cycle. **Usage & credits shows the exact expiry date** rather than asking you to derive it. Seller Account provisioning may complete asynchronously without moving that window. One credit per billing organization: later Seller Accounts do not create additional credits. It does not roll over, has no cash value, and does not stack. A valid promotion or referral code can attribute the same credit and may carry an authorized pricing or access adjustment, but it cannot add a second credit or change the program's value or expiration. A code that is limited to a number of redemptions counts every organization that used it, whether or not that organization received a setup credit. The preview is recomputed after the code is entered, and the adjusted Effective Rate Card—not the code itself—is the accepted record. The setup credit is part of the published Rate Card and does not create billable usage. ## Acceptance and renewal When an effective Rate Card is available, **Settings → Plan & Billing** shows: * the exact Rate Card version and effective date; * every eligible plan size; * your complete Effective Rate Card, including any corporate adjustment, promotion, referral, or explicitly customized package; * the exact entitlement-bundle revision, included feature keys, and premium-support package when the offer carries them; * a frozen customer proposal, when assigned, containing the scope, initial term, renewal and notice terms, and the exact monthly and/or term-prepayment choices; * included IUs, rollover, overage, billing timing, and term; and * the governing agreement and renewal posture. The online proposal and its downloadable PDF come from the same immutable Offer snapshot. The PDF is labeled **Draft — not yet accepted** until the Offer has been accepted. An accepted PDF records the canonical Offer version, acceptance date, and selected payment choice; alternatives remain visible as not selected. If the negotiated terms change, Apostra revokes or supersedes the Offer and issues a new version; it does not edit the version you were sent. The organization name on the proposal comes from the organization account, not from a separate sales profile. If it is incorrect, Apostra updates the organization record and issues a new Offer. Billing contacts, payer name, tax information, and billing address likewise remain in the organization's Billing information. When an Offer contains multiple payment choices, the administrator selects one before accepting. The client submits only that option's key; Apostra resolves and stores the complete frozen option as immutable acceptance evidence. A full-term prepayment is one exact price in the Offer currency for the complete service term; it is not a partial-term payment or a rounded number of monthly payments. After acceptance, **Set up payment** opens the organization's payment setup. Automated term-prepayment collection is not yet self-service, so Apostra coordinates its invoice or ACH funding separately and does not run monthly base charges for that schedule. After acceptance, you may also see the exact included allocation, remaining balance, overage rate, rollover policy, observed usage, and projected month-end usage on **Usage & credits**. These figures do not create charges or enforce entitlements. Only a verified organization administrator can accept a plan. An agent, API credential, service account, or impersonated support session cannot make that financial commitment. If the offer changes while you are reviewing it, the confirmation reloads and asks you to review the new version. Signup may present the current Terms of Service and IU Rate Card in one flow, but the acceptances remain distinct: the record preserves the exact agreement, Rate Card version, selected plan, complete adjustments, prices, actor, and time. An existing organization without an accepted plan will receive the same **Choose an IU plan** action after a Rate Card becomes effective. Apostra never silently accepts a paid plan on its behalf. The plan task also lets an administrator **Continue without a paid plan** or **Decide later**. Continuing without a plan dismisses automatic login prompts for only the exact Rate Card revision reviewed; a successor revision may be shown again. Deciding later allows Apostra to show the task on the next login. In either case, **Settings → Plan & Billing** keeps the manual **Choose an IU plan** action available while the offer remains current, so you can return without waiting for another prompt. One acceptance applies to the billing organization. If you accepted from a Seller Account task, the buyer view shows the same plan; if you accepted from a buyer task, the Seller Account view does too. Standard plans and private offers marked **pin exact** renew through immutable monthly terms at the accepted price. An offer marked **review required** stops before automatic renewal and requires a new explicit commercial decision. A later Rate Card or adjustment never silently reprices the current term. Ending the IU plan at term is an organization-wide decision: it prevents the next shared plan term from starting, rather than ending only one buyer or Seller Account capability. Individual capabilities are enabled or disabled separately. ## Billing cycle and fee invoices Once you accept a paid plan, your IU plan bills on the **calendar month**, measured in UTC. Every accepted paid plan is billed this way, whether you pay by card or by invoice, and whatever day you accepted it. There is no separate enrollment: * **Commitment at cycle start.** The plan's monthly commitment is charged at the start of each calendar month, at the price fixed in your accepted terms. * **Overage during the cycle.** Usage beyond your included and rolled-over IUs accrues as overage at your plan's per-IU rate. For organizations paying by card, accrued overage is charged automatically once it crosses a threshold, so month-end never brings one large surprise; any remainder settles when the cycle closes. * **A fee invoice at cycle close.** Every charged month produces a numbered fee invoice — plan commitment, overage (IUs × rate), and payments applied — on **Settings → Plan & Billing → Payment & invoices**. Invoice numbers are sequential with no gaps, and an issued invoice's charges never change; payments are appended as they settle. * **Nothing from before your plan.** Usage before the moment you accepted your plan is never billed. A billing period that ended before you accepted is not charged or invoiced, and in the period in which you accepted, only usage from acceptance onward counts, at your plan's rates and with its included IUs and credits. **Your first month is prorated.** The month you accept the plan is billed as a partial cycle: the plan commitment and the included IUs are both scaled by the days remaining in that month, counting the day you accepted as a full day. Your usage for the whole month is still rated, and no later month is prorated. A plan accepted on 15 July, for example, owes 17 of July's 31 days. **Moving to calendar months.** Before 1 November 2026, an organization paying by card was billed on cycles that began on its acceptance day. Such an organization keeps those cycles until 1 November 2026. A cycle that would run past that date ends on 1 November instead, so it closes with one invoice for the shortened period. If an organization's usual renewal date falls in October, its cycle closes on that date as before, and a short final cycle from that date to 1 November closes with its own invoice. Calendar months start on 1 November. An organization paying by card that accepts its plan before 1 November has a first cycle that runs from acceptance to 1 November, prorated by days like any first month. Nothing already charged or invoiced changes, and nothing is charged twice for a shortened cycle. A cycle already closed keeps the dates it was closed with. Apostra can pause IU charging for every organization at once. While it is paused, no new commitment or overage charge is made, overage still accrues and stays visible on **Usage & credits**, and no overage line is added to a fee invoice, so a month in which you used more than your included and rolled-over IUs can invoice as the commitment alone. Payments already collected are still applied and their cycle still closes with an invoice. Overage that a closed month did not bill is not collected later: issued invoices never change. When charging resumes during a period, overage already accrued in that same open period becomes chargeable. Organizations paying by card are charged automatically against the saved card. Invoiced organizations receive the fee invoice only and pay on their existing terms — no card charge is made. If a card charge fails, the standard payment recovery ladder applies: retries on a published schedule, email notices, and a self-serve **pay now** action on Plan & Billing that clears the failed-payment status once it succeeds. Plan & Billing also reports unresolved dunning-backed holds, while reaching the account's cap records an internal hold. In the current alpha, neither hold is consulted by a production authorization path, so it does not automatically block new paid platform activity. Viewing, reporting, exporting, and fixing your payment method remain available. ## For agents and API clients `GET /api/v2/billing/iu-rate-card` is the canonical organization offer read. `GET /api/v2/billing/fee-invoices` lists the organization's fee invoices, and `GET /api/v2/billing/fee-invoices/{invoiceNumber}` returns one invoice with its line items. Agents should use the typed `open_iu_plan_task` tool to open the same portable plan-selection task rather than reconstructing it from a generic API call. Its model-visible receipt is deliberately bounded; the authenticated app receives the exact commercial state. `get_iu_rate_card_offer` remains available only as a compatibility read for deployed clients. Acceptance and renewal changes are direct human commitments. The task invokes app-only operations to append the displayed, declined, deferred, or accepted outcome and to schedule or reinstate renewal. Every response is idempotent and tied to the exact Rate Card revision; an accepted outcome links to the immutable acceptance record. These commit operations are not model-visible. The older `storefront-rate-card` REST paths and storefront-named tools remain deprecated compatibility aliases; new clients should use the organization IU names. ## Separate from media pricing IUs price platform activity. Existing media-pricing arrangements cover media spend and their accepted contract fee terms. This Organization IU Rate Card does not replace, amend, or silently remove those terms. Plan & Billing can show both because an organization may owe both platform/IU charges and media charges, but each is a separate commercial stream. ## Related > **[Plan & Billing](/v2/buyer/billing/plan-and-billing)** > > Where organization administrators review commercial terms > **[Seller Account billing](/v2/storefront/billing/overview)** > > Payouts, settlement, and the Seller Account view of the shared plan # Billing Source: https://docs.apostra.com/v2/buyer/billing/overview How media billing responsibility and platform-fee collection work, what remittance details to use, and how to keep billing info current Who invoices media is set per transaction by its **billing party**: * **Consolidated billing (`agent`)** — the seller sends its invoice to Apostra instead of to you. Apostra finance puts it on your consolidated media invoice, which carries the bank remittance details to pay against, and passes your payment to the seller once it arrives. Apostra acts as a billing agent: it does not guarantee the seller's payment, does not pay the seller before you pay, and takes no credit risk on the seller's media. The seller carries that credit risk. * **Operator-billed** — the seller or connected platform bills the account operator directly. * **Advertiser-billed** — the seller or connected platform bills the advertiser directly. For compatibility, `agent` projects to legacy `Decisioned`, while `operator` and `advertiser` both project to legacy `Routed`. That projection is lossy: `Routed` cannot distinguish the operator from the advertiser. Billing party remains the authority for who invoices; `routingType` does not say whether optimization happened or which adapter/execution path was used. For a seller-sponsored buyer, the sponsoring seller fixes this authority at provisioning time: `prepay` selects consolidated billing (`agent`), while `credit` selects seller direct-billed `operator`. The sponsored account remains the seller's governed account, and the buyer cannot change that billing choice. > [!NOTE] > > Platform/IU fees are a separate billing stream from media. For organizations > with an accepted paid IU plan, Apostra issues a separate fee invoice and may > collect it from the card saved on Plan & Billing. Existing > accepted media-pricing arrangements remain separate and unchanged. > [!NOTE] > > Sponsored `prepay` uses the existing media invoice/deposit funding and > account-capacity mechanisms. It does not make platform-fee card collection an > automated media-funding flow. Sponsored `credit` is seller direct billing, > not an Apostra-granted credit line. Keep your **billing info** current for Apostra-issued media or platform-fee invoices — who the invoice is addressed to and where it is sent. Manage it in **Settings → Plan & Billing → Payment & invoices** or via the API (updates are admin-only): ``` GET /api/v2/billing/info PUT /api/v2/billing/info ``` Authenticate every request with `Authorization: Bearer $SCOPE3_API_KEY`. ## What Apostra needs for its invoices The minimum for an invoice you can pay — and that your accounts-payable team will accept — is a **payer name** and a **payer address**. Beyond that, keep these current so invoices route correctly: | Field | Purpose | | - | - | | Billing contact name | Who the invoice is addressed to | | Billing email + CC emails | Where the invoice is delivered | | Phone | Finance-team contact for payment queries | | Billing address | The payer address printed on the invoice | | Tax ID | VAT/EIN or equivalent for your jurisdiction | ## Where the remittance details come from Bank remittance details are printed **on each Apostra-issued invoice** — you do not need to request them separately, and you should always pay against the details on the invoice you received. Apostra maintains a bank account per invoicing currency, so the remittance details match the currency you are billed in. A card-collected platform/IU fee instead follows the saved-card flow shown in Plan & Billing. ## When invoices arrive Apostra-issued invoices follow your billing cycle and payment terms. A media invoice covers only consolidated-billing activity; operator- and advertiser-billed media follows that counterparty's invoice process. Questions about an Apostra invoice go to the finance contact listed on it. ## Billing activity The **Invoice activity** section at the top of the Invoices tab on the org Billing page shows invoice activity as it happens — status, what it's for, currency, amount, and due/paid date — via [Get invoice activity](/v2/buyer/billing/tasks/get-invoice-activity). It's empty until your organization has an issued IU fee invoice; media invoice activity and other reserved row kinds do not have producers yet. ## Task reference > **[Get billing info](/v2/buyer/billing/tasks/get-billing-info)** > > `GET /billing/info` — current contact, address, tax ID > **[Update billing info](/v2/buyer/billing/tasks/update-billing-info)** > > `PUT /billing/info` — keep invoicing details current > **[Get invoice activity](/v2/buyer/billing/tasks/get-invoice-activity)** > > `GET /billing/invoice-activity` — invoice activity as it happens # Plan & Billing page Source: https://docs.apostra.com/v2/buyer/billing/plan-and-billing One page for your commercial boundary — plan, balance, usage, agreements, and the next step to stay in good standing > [!TIP] > > In an agent host, Plan & Billing opens as a Page and its facts are readable > with `get`. See the [V3 Plan & Billing guide](/v3/plan-and-billing). Plan & Billing is the Settings section for your account or organization's commercial boundary. Open **Settings → Plan & Billing**; the section stays in the Settings frame while you move between its commercial tabs. Older billing and organization-settings links open this same section. It answers, in one place: which plan and terms that boundary is on, what each action costs, what has been used, what is funded and what is owed, and which agreements are in force — plus the one concrete step, if any, needed to complete billing setup or stay in good standing. Selecting **Plan & Billing** opens **Overview** for parent and standalone buyer or Seller Account organizations. From a child account, Plan & Billing points to the parent billing organization and opens its Overview; users without parent-admin access do not receive a billing entry point. Other commercial-boundary links for a specific task, such as reviewing invoices, open the relevant tab directly. A standalone Buyer or Seller account is its own commercial boundary until an administrator explicitly attaches it to an organization. Matching email domains or CRM classification never move billing, Terms of Service, standing, or administrators. After attachment, those responsibilities resolve to the parent organization. The page's core commercial-boundary facts come from the [Get billing account](/v2/buyer/billing/tasks/get-billing-account) API. For an eligible commercial boundary, the Plan & pricing tab adds the one organization IU plan, complete Effective Rate Card, and renewal state from the canonical `GET /api/v2/billing/iu-rate-card` read. The older `GET /api/v2/billing/storefront-rate-card` path is a deprecated compatibility alias; it does not make the plan Seller Account-only. Every billing read uses the same organization and admin boundary. > [!NOTE] > > The standard [Organization IU Rate > Card](/v2/buyer/billing/organization-iu-rate-card) is published and effective > in production. Every eligible organization can review and accept an > applicable public plan; no invitation or enrollment is needed. Only an > organization administrator can accept a plan. After you accept, Plan & Billing > may show your accepted included-IU allocation, observed usage, and a > display-only forecast. Existing accepted media-pricing arrangements are > unchanged. ## Choosing your first plan An organization with no plan has not chosen one yet, and nobody is placed on a plan for it. Plan & Billing shows **Select an IU plan** as the next action, and Seller administrators are also asked to choose at every sign-in. Until the organization chooses, it cannot add accounts. A Seller's brief responses are refused with the same next step only where Apostra has turned that on for the Seller, and buyer activity is not refused. Choosing the pay-as-you-go plan with no monthly commitment counts as choosing. See [before you choose a plan](/v2/buyer/billing/organization-iu-rate-card#before-you-choose-a-plan). ## When charging starts Accepting a paid plan starts billing; every organization with an accepted paid plan is charged. Billing follows the plan you accepted, starting from its effective date. Three things are worth knowing before you accept: * **The platform-access fee and the subscription fee are one recurring charge**, not two separate fees. * **One organization has one plan and one IU wallet**, covering both buying and selling work rather than a separate plan for each. * **Accepting a plan locks its terms for that term.** The monthly price, included IUs, overage price, rollover rule, priced activities, and included product access are recorded as you accepted them, and a later price change does not rewrite an accepted plan. A saved card covers eligible subscription and IU charges. It is not used to fund media. > [!NOTE] > > Invoice history and downloadable invoice documents are not in Plan & Billing > yet, for subscription or IU charges. Media billing stays outside the platform > as well: media invoices, payment status, disputes and credit notes, and seller > payouts all continue through the existing process rather than through Plan & > Billing. We are holding the invoice experience back until it can show you the > complete billing picture rather than part of it. ## Who can see it Plan & Billing describes money for the current **commercial boundary**, so it follows that boundary's admin authority: * **Admins of an organization or standalone account** see the full page for the boundary they administer. * **Admins of a child account who also administer its parent organization** are forwarded to the parent's page — billing, including authority over payout destinations, lives at the organization level. A child-only admin cannot open or change the parent's billing information. * **Ordinary members** don't see the page or its Settings entry at all. * **Advertiser-scoped access and sponsored buyers** don't see the Settings entry, even as admins. You do not need any internal or Apostra-side role to view your own effective commercial terms — admin standing on the relevant boundary is enough. ## Key figures **Available balance** appears only for organizations that prepay and hold a funded balance. It is your funded balance minus any amount owed. Apostra does not grant buyer credit lines or use an Interchange credit limit to block a buy. If your organization pays on terms, Plan & Billing shows what you owe and your invoices instead of an available balance. **Intelligence usage this period** is the customer Intelligence Unit (IU) total for the current billing period. It matches the priced IU activity shown in **Usage & credits** and does not include internal AI-provider operating costs. ## The next-action banner The banner at the top of the page shows the **single most important thing to do next**. On Overview, a four-step setup indicator puts that action in context: accept the current terms, select an IU plan when the organization is eligible, add billing details, then add a payment method. Each marker reflects the commercial boundary's current facts, so a later step completed early stays complete if an earlier prerequisite reopens. Organizations that are not eligible for an IU plan continue directly to billing details. Established boundaries also receive funding and standing-recovery actions when needed: | Next action | What it means | | - | - | | Accept terms | The current Terms of Service haven't been accepted yet. | | Accept a governing agreement | A private IU plan offer is ready, and its governing agreement must be reviewed before plan selection. | | Choose an IU plan | An effective Organization IU Rate Card is available for review. When billing is enabled, this commercial boundary must choose a plan before it can use IU-priced capabilities. | | Add billing info | No billing contact or payer address is on file, so Apostra can't invoice you. | | Add a card | This commercial boundary has no payment method yet — [saving a card](/v2/buyer/billing/concepts/pay-by-card) makes it available for eligible platform/IU charges. Saving a card does not accept an IU Rate Card, select a plan, or activate paid entitlements. | | Fund your account | This commercial boundary is on prepay and its balance is exhausted. | | Resolve a hold | A card payment remains unresolved. The hold is a billing-status signal; the current alpha does not enforce it as a spend or entitlement gate. | | None | Nothing outstanding — the account is in good standing. | Only the highest-priority action is returned to the page and compact Plan & Billing app. When there's nothing to do, the banner says so. When there is, it links to the tab or plan-review task that resolves it. ## Choose how to pay On **Payment & invoices**, a business without a payment method can [save a card](/v2/buyer/billing/concepts/pay-by-card) to pay Apostra's platform/IU fees by card, with no commitment. Plan & Billing no longer offers a credit application. Buying from sellers on consolidated billing needs only your billing details (payer name, street address, and country); it does not require a card or a credit line. If a seller separately extends payment terms for the media it sells you, that arrangement is between you and the seller — sellers decide credit on their own terms, not Apostra. For a business with one buyer account, this appears as one business billing setup; you do not need to manage an organization/account hierarchy. The business remains the owner of the card and billing history. Buyer accounts separately own operator identity, advertisers, and campaigns, so adding accounts later does not move or recreate the business's payment authority. Eligible administrators may see an automatic login prompt with **Choose an IU plan**, **Continue without a paid plan**, and **Decide later**. Continuing without a plan suppresses that prompt only for the exact Rate Card revision; deciding later permits it again on the next login. The manual **Choose an IU plan** action on Plan & pricing remains available in both cases while the organization is eligible and has not accepted a plan. ## What each tab shows | Tab | Content | | - | - | | **Overview** | Account standing (billing method: prepay or credit, effective payment terms), available balance with funded/owed breakdown (prepaying organizations with a funded balance only), intelligence usage for the period, and — for organizations with a Seller Account — a shortcut to the Payouts tab. | | **Plan & pricing** | The plan your organization is on, what it includes, and the other plans you could switch to (see [Your plan, what it includes, and switching](#your-plan-what-it-includes-and-switching)). Below those, your effective terms: the exact Rate Card revision and complete selected plan, including pricing and IUs, rollover, the immutable entitlement-bundle revision and feature keys, any premium-support package, proposal terms, included activity terms, effective and renewal dates, and whether the Offer uses standard plan terms or explicit customizations. A customized Offer records its reason and exact differences from the source plan. A private proposal also shows its executive summary, included scope, initial term, automatic-renewal and non-renewal notice terms, and every exact payment choice. You can download the same frozen proposal as a PDF for internal approval. An offer that arrived through a [private offer signup link](#private-offer-signup-links) also shows the saved proposal's narrative and public sources, and its PDF renders that same saved version. The package price is the complete subscription price; there is no second platform-access fee. The **How you're charged** card keeps IU/plan charges separate from existing accepted media-pricing arrangements. When a plan has been accepted, this tab also shows automatic-renewal status, the change cutoff, and any scheduled no-plan date. | | **Usage & credits** | Every organization sees its IU account here, including before it accepts a plan. Before an IU plan is accepted, the tab shows the plan shell with no IU usage. Once a plan is accepted: IUs consumed by each ungrouped, priced activity and by day; the exact accepted included-IU allocation, remaining balance, overage rate, rollover policy, and display-only month-end forecast. If no priced activity consumed IUs, the page says **No IU activity this period**. AI-provider operating cost does not count as customer IU usage. The tab also shows your funded, owed, and available balance. | | **Payment & invoices** | Two labeled money streams. **Apostra fees**: the business's billing details, payment method, account standing, funding, and platform/IU fees. **Media billing**: activity for invoiced media spend. The two streams stay separate. | | **Media billing** | For organizations with buyer capability: configure [media billing entities](/v2/buyer/billing/media-billing), attach advertisers/accounts, and preview the most-specific match in the “Which configured entity matches?” inspector. This is currently configuration and preview only; the invoice producer does not yet consume the resolution. | | **Payouts** | For organizations with a Seller Account: payout entities (one per legal entity and currency, with a primary), setup wizard, and payout activity — including setup history showing who changed payout details and when. Parent admins select the organization or a direct child Seller Account from the account dropdown; they also see per-account payout status. | | **Contracts & orders** | Contract lifecycle (status, terms-of-service version, effective and end dates), agreement documents with who accepted them and when, the organization IU plan history when published, and the separate media rate-card detail. | | **Accounts** | For parent organizations only: the money view of your child accounts — customer IU usage attributed to each account during the organization's current billing period. A child with accepted rating terms and no activity shows 0 IUs; unavailable or unratified usage stays blank. Account-level fee totals and billing delegation controls remain hidden until their product contracts are ready. Switching into an account stays in the account switcher; this tab is about money, not navigation. | For an organization that offers a sales agent to other sellers, a private Offer may include the paid `operate-agents-for-clients` feature key and its support or IU allowance. Sales agent registration, testing, and certification remain free and do not appear as purchased features. This entitlement also does not buy a buyer-visible badge, organic rank, or Apostra recommendation preference; Certified Partner verification and any labeled Featured Partner placement are separate program decisions. See [Partner Program and agent operations](/v2/storefront/inventory-sources/partner-account). ## Your plan, what it includes, and switching Plan & pricing opens with the plan your organization is on. A plan with no monthly commitment appears the same way as any other plan. The page reads every figure from your billing account, so the numbers match the published [Organization IU Rate Card](/v2/buyer/billing/organization-iu-rate-card) revision you are on; this documentation does not repeat them. Each fact appears once on the page. **The plan you are on** shows its name, monthly commitment, included IUs and IU usage price, rollover, renewal and change cutoff, and any upcoming plan you have already accepted. Under those it says **what your plan includes**, in the words of the plan's own rate card revision, and how many of your accounts you use against your account limit. If the limit your usage is measured against differs from the account limit the plan includes, both are labelled. The plan's product access, account limit and activity prices are in **Terms and agreement**, so they are not repeated. **An organization with no plan** is on none. The page shows the plan we suggest, labelled **Suggested** with **No plan chosen**, with its figures, what it includes, and **what you pay for**: the priced activities the plan charges for, each with its IU price and what counts as one unit. For a buyer these are the activities a buyer incurs, such as a media buy that is active in a billing period or turning on Enhanced Reporting. For a seller they are the activities a seller incurs, such as each brief you run and each non-social media buy that delivers, when the plan charges for them. Every activity the plan bills for is listed for the side that pays it (a charge both sides incur lists for either), using the activity's own name, IU price and unit when the page has no dedicated sentence for it. An activity with no published price is not listed, each plan lists only what its own rate card charges for, and a plan that charges for nothing says so. [What a plan with no monthly commitment charges for](/v2/buyer/billing/how-iu-billing-works#what-a-plan-with-no-monthly-commitment-charges-for) explains these charges. Nothing is billed under a suggested plan. **Other plans** (or **Other available plans** beside a suggested plan) lists every other plan available to your organization with its figures, rollover, benefits and charges. It never lists the plan you are on. If your plan is a custom agreement, the page keeps its terms visible, says it is changed with Scope3 and not here, and offers no list or switch; use **Get in touch**. If no other plan is available to your organization, the page says so plainly. When a private proposal is waiting for you to accept, its own table shows what it proposes in place of this list. **Switching.** An organization administrator sees **Switch plan** (**Choose an IU plan** when the organization is on none), which opens the plan chooser, where you pick the plan and accept it. Nothing changes until an administrator accepts, as described under [When charging starts](#when-charging-starts). Everyone else sees the plans and their benefits, and can ask an organization administrator to make the change. Because every accepted paid plan is billed, a switch in the middle of a billing period is not self-serve when either plan has a monthly commitment: Apostra staff make that change, so contact Apostra and nothing is changed until then. Choosing a new plan after your previous term ended, while that term's final billing is still being completed, needs Apostra staff in the same way. ## Private offer signup links Apostra may send one named person a private signup link for a reviewed private Offer. **Opening the link in your browser:** 1. The link opens an **Offer invitation** page. Opening it does not accept anything or charge you. 2. If you are signed out, choose **Create an account** or **Sign in**, using the email address the link was sent to. You come back to the page afterwards. If you sign in with a provider that opens a new tab or window, open the link from your email again. 3. The page shows the email you are signed in with and the organization the Offer will go to. If that is the wrong organization, switch accounts first, then open the link again. 4. Choose **Add offer to Plan & Billing**. The Offer is claimed and Plan & Billing opens on **Plan & pricing**. Opening the link again while signed in to the same organization takes you straight back; from any other organization the page says the link is not recognized or already used. **What you see in Plan & Billing:** the Offer's exact terms, plus the saved proposal it was issued from: its version, why the Offer fits your organization (where the proposal includes it) and the public sources it relies on. **Download PDF** renders that same saved version, marked **Issued Commercial Offer – not yet accepted** with the Offer version. When you are ready, select **Choose an IU plan**, pick a payment choice and accept. If Plan & Billing first asks you to review a governing agreement, do that first. > [!NOTE] > > Plan & Billing shows a claimed Offer to the organization it was added to. An Offer can only be added to an organization that has the account its plan is for, such as a Seller account for a seller plan. If yours doesn't, the page says our team will follow up, and the link still works once that's resolved. If you claimed an Offer and it does not appear on **Plan & pricing**, email support; you do not need a new link. **Who can claim it:** only the person the link was sent to. They must be signed in with that exact email address and be an administrator of their organization's billing account. **What claiming does:** it connects your organization to the Offer and makes that exact Offer available here for review. The Offer carries the same proposal, terms and price that were reviewed before the link was sent. **What it does not do:** claiming never accepts the Offer, starts paid service or charges a card. You still review and accept the Offer, and choose how to pay, as described above. **Limits:** * A link can be claimed once. * It expires after the period set when it was sent, at most 30 days. * Apostra can revoke a link or replace it with a newer one. * If the published terms behind the Offer change after the link was sent, the old link stops working and a new one is needed. **When a claim cannot finish:** you see the reason, for example an expired or already-used link, a different signed-in email, or a missing billing-administrator role. Sign in with the invited email, ask an administrator to claim it, or ask the person who sent the link for a new one. Some cases, such as an organization already connected to a different Apostra account, are recorded for the Apostra team to review, and you don't need to retry. If the claim collided with another change, or we could not record it for review, you are asked to try again. **How the link stays private:** the link's code sits after the `#` in the address, so your browser does not send it to Apostra when the page loads. The page removes it from the address bar straight away and keeps it only in that browser tab, for up to 30 minutes, while you sign in. **Support:** for questions about the Offer itself, contact the person who sent you the link. For help with the link or your account, email [support@apostra.com](mailto:support@apostra.com) with the email address the link was sent to. Do not send the link itself; support can see your organization's link status without it. ## Media billing responsibility The transaction's **billing party** is the authority for who invoices and settles media: * **Agent** — consolidated billing: the seller's invoice comes to Apostra, Apostra finance bills you on your consolidated media bill and collects, and passes your payment to the seller once it arrives. Apostra does not guarantee payment to the seller or take credit risk on the seller's media. * **Operator** — the seller or connected platform bills the account operator directly. * **Advertiser** — the seller or connected platform bills the advertiser directly. For compatibility, `agent` projects to legacy **Decisioned**, while `operator` and `advertiser` both project to legacy **Routed**. That projection is lossy: **Routed** cannot distinguish operator from advertiser. Billing party remains the authority for who invoices; `routingType` does not say whether Apostra optimized the transaction or which adapter/execution path was used. See the [campaign object](/v2/object-guides/campaign) for the canonical field relationship. ## Organization IU plan renewal After an IU Rate Card is published and your commercial boundary has accepted a plan, an administrator for that boundary can select **Manage renewal** from Plan & pricing, then choose **End plan at term** before the displayed change cutoff. The current plan and price stay in force through the term end; the page shows a pending non-renewal and the exact timestamp the commercial boundary will return to its no-paid-IU-plan posture. This is an organization-wide decision because buyer, Seller Account, Apostra assistant, and other IU-rated workloads use the same plan. It does not terminate the platform agreement or change an existing accepted media-pricing arrangement. Individual capabilities can be disabled separately without ending the shared plan. Before that same cutoff, **Reinstate renewal** restores automatic renewal. Apostra retains every decision immutably; the renewal task shows the 500 most recent and labels each entry **Renewal target** or **Earlier term**, so an earlier decision cannot look like it controls the term being managed. It never edits the accepted term, and an earlier term cannot be changed after its successor has been created. Plan & Billing is the one human-facing home for billing: the organization settings entry redirects here, and the previous contract summary in account settings also redirects here, so one page answers every billing question. Some sections show a "coming soon" or non-billing state. **Published price list** contains only immutable activity terms in the commercial boundary's accepted Rate Card. **Activities under calibration** shows separately labeled starting hypotheses for work whose cost and commercial terms are still being measured; those rows cannot debit the IU balance or alter an accepted contract. **Deposits history** lists your recent prepay deposits, straight from the platform ledger. Budgets, auto-top-up, and invoices populate as their underlying commercial capabilities launch. ## Money figures Every money figure on the page renders from the ledger — balances, owed amounts, and payment state are never inferred or cached. The figures match what [Get billing account](/v2/buyer/billing/tasks/get-billing-account) returns, in the commercial boundary's billing currency. ## For agents The core account document is available to agents via `GET /api/v2/billing/account` — see [Get billing account](/v2/buyer/billing/tasks/get-billing-account) for its full contract, including service-token access and redaction. Organization IU plan and renewal facts come from `GET /api/v2/billing/iu-rate-card`. Agents should use the typed `open_iu_plan_task` tool to open and present the shared portable task. Showing, declining, deferring, or accepting the offer appends an idempotent response tied to the exact revision. Accepting a plan or changing renewal requires an organization administrator's explicit confirmation; only the task may invoke those app-only commits, which are not model-visible. The `storefront`-named REST path and tools remain deprecated compatibility aliases. ## Related > **[Organization IU Rate Card](/v2/buyer/billing/organization-iu-rate-card)** > > Shared plans, rollover, activity terms, and availability > **[Get billing account](/v2/buyer/billing/tasks/get-billing-account)** > > The consolidated document behind this page > **[Billing overview](/v2/buyer/billing/overview)** > > How invoicing and remittance work # Campaign autonomy settings Source: https://docs.apostra.com/v2/buyer/campaigns/autonomy-settings Control whether the agent acts automatically or pauses for buyer approval per campaign Autonomy settings let a buyer control how independently the agent operates on a campaign. The preference persists across sessions, so the buyer does not have to repeat it each time they start a conversation. ## Two dimensions Each campaign has two independent autonomy dimensions: | Dimension | Controls | | - | - | | `inventorySelection` | Whether the agent stages media buys automatically or pauses for buyer approval before each one | | `rebriefing` | Whether the agent updates the campaign brief automatically or proposes changes for buyer approval | ## Three modes Each dimension accepts one of three mode values: | Mode | Meaning | | - | - | | `manual` | Human-driven. The agent proposes the action and the buyer executes it. This is the default when no preference has been saved. | | `propose` | Agent drafts, buyer approves. The agent prepares the action and presents it for an explicit yes before proceeding. | | `automatic` | Agent acts. The agent executes the action without a per-action confirmation, relying on the campaign brief and budget as the standing authority. | ## Setting autonomy via the API Pass the `autonomy` field to the campaign create or update operation. Each dimension is optional — omitting a dimension leaves its current value unchanged. ```json theme={null} { "autonomy": { "inventorySelection": "automatic", "rebriefing": "propose" } } ``` At least one dimension must be present when the `autonomy` object is supplied. ## Reading the stored preference The response shape differs between the V2 REST API and the V3 MCP agent surface. ### V2 REST API The V2 REST campaign response includes `autonomy` only when at least one dimension has been explicitly set. Each present dimension is returned as a plain enum string: ```json theme={null} { "autonomy": { "inventorySelection": "automatic" } } ``` An absent dimension means no preference has been saved and the effective mode is `manual`. The `autonomy` object itself is absent from the response when neither dimension is set. ### V3 MCP agent surface The V3 campaign workspace always returns both dimensions, each as an object: | Field | Values | Meaning | | - | - | - | | `mode` | `manual` \| `propose` \| `automatic` | The effective mode | | `source` | `inherited` \| `overridden` | How the mode was determined | A `source` of `inherited` means no preference has been saved and the dimension is using its default (`manual`). A `source` of `overridden` means the buyer explicitly set the mode. ```json theme={null} { "autonomy": { "inventorySelection": { "mode": "automatic", "source": "overridden" }, "rebriefing": { "mode": "manual", "source": "inherited" } } } ``` ## Scope and persistence Autonomy is a per-campaign setting. Changing it on one campaign does not affect other campaigns for the same advertiser or account. The preference survives session restarts and is included in every campaign read response. # Campaign suggestion sets Source: https://docs.apostra.com/v2/buyer/campaigns/campaign-suggestion-sets How optimizer suggestions that move budget between media buys are approved When Apostra's optimizer works across a whole **campaign** rather than one media buy at a time, its recommendations stop being independent. Taking budget out of one media buy is what funds putting budget into another, so the two halves are one decision. A **campaign suggestion set** is that decision: every media buy the optimizer moved money between, grouped under the single run that computed them. > [!NOTE] > > If you are not receiving sets yet, nothing is missing — see > [Availability](#availability). ## Why a set is approved as a whole Each member of a set carries a `netAllocationDelta` — the amount that media buy gains or gives up. Those are transfers, not standalone recommendations, and they sum to exactly zero across the set. That is why there is no way to approve one media buy's share on its own. Approving a `+4000` minor units without its offsetting `-4000` would create budget that the campaign never freed; approving the `-4000` alone would strand it. And rescaling a subset so it balances by itself produces an allocation the optimizer never evaluated against your goals or your sellers' capacity. So the set is the unit: you approve all of it, or none of it. The per-media-buy breakdown is there so you can see what the transfer does — [read a single set](/v2/buyer/campaigns/tasks/get-campaign-suggestion-set) to get it. It is not there so that parts of it can be actioned separately. ## The lifecycle of a set **1. Awaiting messages** The optimizer publishes one message per media buy it evaluated, and they arrive independently. Until all of them have, the set is incomplete and Apostra will not act on it — applying half a transfer would leave your campaign's budget unbalanced. A set can sit here legitimately while the rest of the run arrives. **2. Awaiting approval** The set is complete and balanced, and it is yours to decide. You are notified when a set reaches this state. **3. Approved or rejected** Your decision applies to the whole set. [Approve](/v2/buyer/campaigns/tasks/approve-campaign-suggestion-set) it, or [reject](/v2/buyer/campaigns/tasks/reject-campaign-suggestion-set) it — rejection optionally carries a reason; approval takes no body. **4. Applied** Apostra sends the changes to your sellers and waits for each one to take effect. This is not instant — see [What happens after you approve](#what-happens-after-you-approve). If optimizations are set to apply automatically, a complete set goes straight to approved without waiting for you. That setting is read from the campaign, falling back to the advertiser's default when the campaign does not set one — so a set can auto-approve on a campaign that appears to specify nothing. It is fixed when the run is first seen, so changing it mid-run affects the next run, not one already in flight. ## What happens after you approve Approving does not move the money. It clears Apostra to move it, and the move takes time, because each media buy lives at a different seller and a seller takes a change when it takes it. Two things about that are worth understanding, because they are what you will see while polling. **Budget is freed before it is spent.** Every media buy giving budget up is changed first, and Apostra waits until each seller has actually enacted its decrease before sending a single increase. It never commits budget the campaign has not yet freed. That waiting is the reason a set stays in `APPLYING` for a while, and it happens twice — once for the decreases and again for the increases. How long that takes is up to your sellers: often minutes, longer when one queues the change for review. Apostra keeps checking rather than assuming, and if a seller goes quiet it stops and raises the set for a person rather than waiting indefinitely. **If a change cannot be made, the whole transfer is put back.** A set is one decision, so it is applied as one. If a media buy's change fails after another buy's change has already landed at its seller, Apostra restores every media buy it had already moved to the budget it held before the set was applied. The one exception is a reversal that cannot itself complete — a seller refuses the undo, or never enacts it. Apostra then stops rather than guessing, moves the set to `NEEDS_RECONCILIATION`, and a person puts the budgets right. Until they do, your live budgets can sit part-way between the suggestion and their pre-approval values; see the statuses below. > [!NOTE] > > A media buy whose **flight has ended** while the set was applying is left out — > of the transfer, and of the reversal too. A finished buy cannot spend what a > budget change would give it, and failing the set over one would undo the other > buys' correct changes over a calendar date. The set still completes and names > the media buys it left out, on `statusReason`. > > The exception is a media buy that was **giving budget up**. If its flight ends > before its seller has enacted the decrease, that budget was never freed — the > buy may already have spent it — so Apostra does not send the increases it > would have funded. The whole transfer is put back instead, and a later run > re-plans the campaign without the finished buy. > > When a finished buy is left out, your budgets will not match either the > suggestion or their pre-approval values exactly. A buy left out of a reversal > may still be sitting at the applied budget, and because its flight has ended, > nothing further will change it. ### Statuses you may see | Status | Meaning | | - | - | | `AWAITING_MESSAGES` | Not every media buy in the run has reported yet. Nothing to decide. | | `AWAITING_APPROVAL` | Complete and balanced. Waiting on your decision. | | `APPROVED` | Accepted — by you, or automatically if the campaign applies optimizations without review. | | `APPLYING` | In progress: changes are being sent to your sellers and confirmed. How long it lasts is up to your sellers — the decreases are confirmed before any increase is sent. | | `APPLIED` | Done. Every media buy took its change, except any whose flight had ended — those are named on the set. | | `COMPENSATING` | Something could not be applied — or a media buy giving budget up finished its flight before its decrease landed — so the media buys that had already moved are being restored to their pre-approval budgets. | | `COMPENSATED` | The transfer was reversed. Every media buy it could reach is back at its pre-approval budget; any it could not are named on the set. | | `APPLY_FAILED` | Refused before any change reached a seller. Nothing moved, and there was nothing to put back. | | `NEEDS_RECONCILIATION` | A seller accepted a change but never enacted it within the hour, or the reversal itself could not complete. Apostra stops rather than guessing. Apostra is alerted and a person is resolving it. | | `REJECTED` | Declined. Nothing moves. | | `SUPERSEDED` | A newer, complete run for the same campaign replaced this one. Decide the newer set instead. | | `EXPIRED` | The recommendation no longer describes your campaign — a media buy in it changed after the optimizer read it, or one reached a terminal state, or the campaign is gone. A later run will produce a fresh set. | | `PARKED` | Held back because the run did not satisfy its own consistency rules. Apostra is alerted; you do not need to act. | | `DRY_RUN_RECORDED` | Applying campaign-scoped optimizations is not switched on for your account, so nothing was sent to your sellers. What would have moved is recorded. Which set lands here is decided by the campaign: one you review terminates here instead of being offered to you, and one that applies optimizations without review terminates here instead of applying. Either way it is final rather than queued — nothing retries it, and a later run produces a fresh set. | Only `AWAITING_APPROVAL` is actionable by you. Everything else is either in progress or terminal. `NEEDS_RECONCILIATION` is the one status where your live budgets may not match either the suggestion or their pre-approval values. Apostra is alerted automatically and resolves it; you do not need to open a ticket. Do not hand-edit the affected media buys' budgets while a set is in this state — doing so changes what Apostra is working from to put them right. The `status` filter accepts the full enum. Treat an unrecognised status as "not actionable by me" rather than assuming this list is closed. ## Finding sets that need you [List campaign suggestion sets](/v2/buyer/campaigns/tasks/list-campaign-suggestion-sets) and filter on `status=AWAITING_APPROVAL` to see just the decisions outstanding. You can also filter by `campaignId`. The list gives you each set's `campaignMediaBuyCount` but not its per-media-buy breakdown — a single campaign can span many media buys, and a list is for finding the set you care about. Fetch that one set to see where the money goes. `campaignMediaBuyCount` is the number of media buys the optimizer **declared** for the run, not the number reported so far. A set still in `AWAITING_MESSAGES` can declare four and return an empty `mediaBuys` array, because membership is fixed only once the set is complete. ## How a set relates to a suggestion A campaign suggestion set is made of ordinary optimization suggestions — one per member media buy — and each still appears through `GET /api/v2/buyer/optimization-suggestions` as usual. What changes is where the decision lives: a suggestion that belongs to a set cannot be approved or rejected on its own, and attempting it returns `409` naming the set to decide instead. ## Availability Campaign-scoped optimization is being rolled out. No campaign-scoped runs are published yet, so these endpoints return nothing for anyone — they filter nothing by account, and an empty list today is not a statement about your rollout. Once runs are published, an account that is not yet enabled will have its sets recorded rather than acted on, whether or not the campaign applies optimizations without review, and no approval notification arrives for the ones you would otherwise have reviewed. Nothing about your existing per-media-buy optimization suggestions changes in the meantime. # Channel groups Source: https://docs.apostra.com/v2/buyer/campaigns/channel-groups Express composite inventory intent, compile it into media buys, and preserve the same grouping in reporting A **channel group** is one coherent inventory selection inside a campaign. It combines AdCP inventory dimensions without inventing new AdCP channel values. For example, mobile web display means: ```json theme={null} { "channels": ["display"], "property_types": ["website"], "device_type": ["mobile"] } ``` It does not mean `channel: "mobile_web"`. `mobile_web` is not an AdCP media channel. > [!WARNING] > > Interchange uses the channel-group ID to split and label media buys, and a V3 > `request_proposals` round uses the groups to choose which sellers to ask and > which returned products to keep (see > [Proposal rounds follow your channel groups](#proposal-rounds-follow-your-channel-groups)). > It does not validate products you select against the group's inventory > definition, or translate that definition into package targeting. Apostra and API > clients must verify discovery evidence and seller product details before > assigning `channelGroupId`. Within one inventory dimension, values are alternatives (OR). Across populated dimensions, declared values combine (AND). A group with `channels: ["ctv", "olv"]` and `device_type: ["ctv", "mobile"]` therefore allows either channel and either device, while still declaring both a listed channel and a listed device. If you need pairwise logic such as “CTV on CTV devices OR OLV on mobile devices”, make two channel groups. ## Why groups create media-buy boundaries One downstream AdCP media buy cannot preserve arbitrary Boolean combinations across channel, property type, device type, and format. Interchange therefore uses this compilation rule: ```text theme={null} channel group × seller × settlement currency = one media buy ``` If a campaign contains `mobile-web-display` and `ctv`, products assigned to those groups become separate media buys even when they use the same seller and currency. They remain part of one campaign. ## Save pre-built groups Pass `channelGroups` to `save_campaign` on `/mcp/v3`. A preset is authoring shorthand. Interchange expands and saves the full definition with the preset version, so later preset changes do not silently change an existing campaign. ```json theme={null} { "advertiserId": "123", "name": "Streaming launch", "flight": { "startAt": "2027-02-01T00:00:00Z", "endAt": "2027-02-28T23:59:59Z" }, "budget": { "total": 50000, "currency": "USD" }, "channelGroups": [ { "channelGroupId": "mobile-web-display", "presetId": "mobile_web_display" }, { "channelGroupId": "ctv", "presetId": "ctv" } ], "idempotencyKey": "streaming-launch-2027-02" } ``` The available presets expand as follows: | Preset | Saved inventory definition | | - | - | | `display` | `channels: ["display"]` | | `olv` | `channels: ["olv"]` | | `ctv` | `channels: ["ctv"]` | | `mobile_web_display` | `channels: ["display"]`, `property_types: ["website"]`, `device_type: ["mobile"]`, and AdCP display `format_kinds` (`image`, `html5`, `display_tag`, `image_carousel`, `native_in_feed`, `sponsored_placement`, `responsive_creative`, `custom`) | | `mobile_web_olv` | `channels: ["olv"]`, `property_types: ["website"]`, `device_type: ["mobile"]` | The simple channel presets deliberately make no property, device, or format assumption. For example, `ctv` means the AdCP CTV channel; it does not silently mean only a physical television device or only a CTV app. ## Define your own group You can provide the same AdCP-aligned dimensions directly: ```json theme={null} { "channelGroupId": "premium-streaming-video", "name": "Premium streaming video", "inventory": { "channels": ["ctv", "olv"], "property_types": ["ctv_app", "website"], "device_type": ["ctv", "desktop"], "format_kinds": ["video_hosted", "video_vast"] } } ``` The dimension names are AdCP's own: `property_types` (as in AdCP property-list filters), `device_type` (as in AdCP targeting) and `format_kinds` (as in AdCP product filters). `save_campaign` and `get` use these names. The camelCase spellings `propertyTypes`, `deviceTypes` and `formatKinds` are refused on V3. Use separate groups when combinations must not collapse into a cross-product. Do not put an alias such as `mobile_web` into `channels`; aliases belong in the group name or preset ID, while the saved inventory definition stays aligned to AdCP. ## Proposal rounds follow your channel groups When a campaign has channel groups, a broadcast V3 `request_proposals` round uses them in three ways: 1. **Which sellers are asked.** A seller is contacted only when at least one of the channels it declares can match a channel in one of your groups. Channel aliases match their AdCP value: a seller listed as `audio` matches `streaming_audio`. For an official platform integration, such as Spotify or Amazon, the platform's own channel coverage is used. A seller that declares no channels is still asked, because nothing shows it cannot match. 2. **What each seller is told.** Every contacted seller receives the union of your groups' channels as the AdCP `channels` filter on its request. If a group names no channel, no channel filter is sent, because that group accepts every channel. 3. **Which products come back.** A returned product whose channels, or whose creative format kinds, fit none of your groups is dropped, together with any proposal that allocates budget to it. The seller's outcome reports the number dropped as `channelExcludedProductCount`. A product that does not declare a channel or format is kept. The response lists the eligible sellers that were not contacted in `skippedSellers`: each entry names the seller, the channels it declares, and the reason `channel_mismatch`. The same list also names sellers left out for another reason, such as `not_transactable` (see who a broadcast round asks in [Buyer workflows](/v2/setup/v3/buyer-workflows)). This seller-narrowing guarantee does not yet apply to directed or campaign-list rounds. Those rounds use channel coverage only when the buyer-routing rollout is enabled; it is currently off. When it is enabled, a directed seller that does not match is reported as a coverage skip, not in `skippedSellers` with `channel_mismatch`. At most 25 sellers are listed; `total` counts them all. If no eligible seller can match any group, the round ends `failed` with `cohortError.code` `NO_ELIGIBLE_SELLERS` and the same `skippedSellers` list. To include more sellers, add a group for their channel and start a new round with the new campaign revision. Seller narrowing needs every group to name at least one channel. If one group bounds only property type, device type, or format, that group admits sellers of any channel, so the round asks every eligible seller and sends no channel filter. Product filtering still applies the channel and format bounds of the groups that have them. A group that bounds only property type or device type turns filtering off, because neither sellers nor returned products declare those dimensions reliably. A round whose one seller is fixed by the buyer's account, such as a sponsored buyer or an own-supply sandbox campaign, never lists that seller in `skippedSellers`. The seller still receives the `channels` filter, and its returned products are still filtered. ## Assign products when creating a media buy Discover products, verify that the seller evidence matches one group, then pass that group's ID when creating the draft. With `save_media_buy`, `channelGroupId` applies to all products in that creation call: ```json theme={null} { "campaignId": "cmp_123", "sellerId": "42", "channelGroupId": "mobile-web-display", "products": [ { "productId": "sf1:42:bW9iaWxlLWRpc3BsYXk", "budget": 15000 } ], "idempotencyKey": "pq_123" } ``` When using the batch `create_media_buys` operation directly, put `channelGroupId` on each product or proposal selection. Every selection needs a group ID when its campaign defines channel groups. A media buy records the group ID and name that created it. Define groups before the campaign creates its first ungrouped media buy. Once a saved selection or media buy references a group, keep it immutable; add a new group instead of removing or redefining it. The batch operation also accepts fully expanded `channelGroups` in `campaign.create`. Preset shorthand is available through V3 `save_campaign`; REST clients either send the canonical saved shape inline or create the campaign first and pass its `campaignId`. REST does not accept preset shorthand. Send `name`, `source`, and the expanded inventory snapshot. REST keeps the camelCase dimension names `propertyTypes`, `deviceTypes` and `formatKinds`; only V3 uses AdCP's snake\_case names: ```json theme={null} { "channelGroups": [ { "channelGroupId": "mobile-web-display", "name": "Mobile web display", "source": { "kind": "preset", "presetId": "mobile_web_display", "presetVersion": 1 }, "inventory": { "channels": ["display"], "propertyTypes": ["website"], "deviceTypes": ["mobile"], "formatKinds": [ "image", "html5", "display_tag", "image_carousel", "native_in_feed", "sponsored_placement", "responsive_creative", "custom" ] } } ] } ``` Always pass product and proposal IDs exactly as discovery returned them. The qualified `sf1:` value in the example is illustrative; do not construct or decode it in a client. ## Report by the same group Use the V3 `get_delivery` tool with the `campaign_delivery` report and the `channel_group` dimension: ```json theme={null} { "report": "campaign_delivery", "metrics": ["impressions", "spend", "clicks"], "dimensions": ["channel_group"], "range": { "startDate": "2026-09-01", "endDate": "2026-09-30" }, "filters": { "campaignId": "cmp_123" } } ``` To read one group only, add `filters.channelGroupId`. Ungrouped legacy media buys roll up under a null channel group rather than being guessed from their names, products, or delivery. Channel groups answer “which coherent inventory selection did the buyer ask for?” Campaign targeting answers “who, where, or when should it reach?” Keep those concepts separate even when both mention a dimension such as device. # Campaigns on connected provider accounts Source: https://docs.apostra.com/v2/buyer/campaigns/directed-campaigns Mirror and inspect campaigns from a connected provider account > [!NOTE] > > Provider-account campaign mirroring is available to buyers with an eligible > connected account. It is read-only: Apostra does not create, > update, pause, reactivate, or cancel campaigns in the connected account. ## Tracked campaigns Connecting a provider account lets Apostra materialize its campaigns as read-only, `management: "tracked"` projections. The connected provider remains the source of truth. Apostra periodically reconciles the projection and provides shared identity, audit, relationship rollups, and live delivery access around it. The compatibility API value for these projections is `mode: "directed"`. `directed` is a deprecated wire discriminator, not a product name. Use the `management` field to understand who operates a campaign: * `management: "tracked"` means it was mirrored from provider-account activity that Apostra did not author. The projection is read-only. * `management: "managed"` means the campaign uses the canonical Apostra buyer lifecycle. Discover products and create, read, update, pause, or cancel its media buys through the buyer media-buy APIs. Each tracked campaign is a one-to-one projection of one storefront campaign and one upstream AdCP media buy. Its name, status, flight, budget, and packages come from that buy and cannot be edited independently. The projection preserves both Apostra's `media_buy_id` and the storefront's `adcp_media_buy_id`. Tracked campaigns return `directed.provenance: "connected_account"` with connection/account IDs, provider, mirror freshness, and upstream media-buy identity. ## Connect and subscribe an account First [connect the official adapter](/v2/guides/connecting-ad-platforms) and [map its discovered account to an advertiser](/v2/concepts/ad-platform-accounts#after-connecting). For an agent-driven setup: 1. Call `list_storefronts`, then `connect_storefront` to obtain the human OAuth handoff URL. 2. Call `list_storefront_connections` and `list_storefront_connection_accounts` to choose a buyable account. 3. Read `list_storefront_connection_account_mappings`. If needed, call `map_storefront_connection_account_to_advertiser` with the intended advertiser. 4. Call `subscribe_directed_campaigns` with the mapped connection and account IDs to start the read-only mirror. Organization/manager and publisher-identity accounts cannot be subscribed. ```bash theme={null} curl -X POST \ "https://api.apostra.com/api/v2/buyer/storefront-connections/42/accounts/81/directed-campaigns/subscribe" \ -H "Authorization: Bearer $SCOPE3_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "advertiserId": "12345" }' ``` Subscription immediately attempts a metadata sync. Active campaigns are always included; terminal history is backfilled for one year by default. Subsequent worker sweeps reconcile changes every 15 minutes by default. This does not import delivery history. The response includes `backfillStart`, `lastSyncedAt`, `lastSyncStatus`, `lastSyncError`, and mirrored/retired/skipped counts. Read the same state later with: ```text theme={null} GET /api/v2/buyer/storefront-connections/{connectionId}/accounts/{accountId}/directed-campaigns/subscription ``` `backfillStart` is a fixed metadata-history boundary, not a progress meter. A subscription remains durable when enumeration is unsupported or temporarily unhealthy; the status reports `ERROR` and the worker retries. Existing valid mirrors survive a failed or incomplete snapshot. When a broad account read omits a known nonterminal campaign, Apostra performs a bounded direct read before retiring its projection. A complete terminal response preserves campaign and package history even if the provider returns no packages. A failed or incomplete direct read retains the last valid mirror. To stop mirroring, call `unsubscribe_directed_campaigns`. Apostra pauses the sweep and retires local projections; it does not delete upstream campaigns. ### Registered AdCP storefront sources A buyer may project a registered third-party AdCP storefront source into the same connection plane. Register source credentials, discover the source account with `list_available_accounts`, map it to an advertiser, then call `connect_adcp_storefront`: ```bash theme={null} curl -X POST \ "https://api.apostra.com/api/v2/buyer/storefronts/42/sources/publisher-sales/adcp-connection" \ -H "Authorization: Bearer $SCOPE3_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "advertiserId": "12345", "accountId": "provider-account-7", "credentialId": "81" }' ``` The response returns `connectionId` and `connectionAccountId`; use them with the normal subscribe, subscription-status, campaign-read, and delivery-read operations. The operation accepts only sources already registered on an active storefront. It never accepts an arbitrary endpoint URL or raw secret. For registered sellers, a snapshot is complete only when every requested page returns boolean `pagination.has_more`, every cursor advances, every buy carries a valid ISO currency, and any returned account ID matches the pinned account. Incomplete snapshots never retire an existing projection. ## List and inspect mirrors Use the shared campaign surface and compatibility mode filter: ```bash theme={null} curl \ "https://api.apostra.com/api/v2/buyer/campaigns?mode=directed&advertiserId=12345" \ -H "Authorization: Bearer $SCOPE3_API_KEY" ``` `GET /api/v2/buyer/campaigns/{campaignId}` returns the tracked campaign and its single mirrored media buy. Discovery, execute, pause, reactivate, and ordinary campaign mutations reject tracked projections. To prioritize a fresh upstream snapshot without changing the provider account, send the refresh-only compatibility request: ```json theme={null} { "mode": "directed", "refresh": true } ``` ### Meta: observed automation controls during refresh Tracked Meta campaigns that use Advantage+ audience targeting can return provider-observed automation controls during refresh. The Meta adapter validates and retains those controls in its provider readback evidence so a known control does not make the complete refresh fail as unsupported. The shared directed campaign response does not expose raw Meta automation controls, and they do not appear in the package's canonical `targeting_overlay` or grant write authorization. The tracked surface remains read-only, and the values reflect provider state rather than an Apostra-authored targeting decision. Active `individual_setting` controls are reported only when `advantage_audience: 1` is also present; an active individual control without the automation anchor is not representable and causes the upstream read or refresh to fail closed. All other active automation dimensions (e.g. `creative_audience_pairing`) and any malformed or unrecognized values are likewise not representable. Adapter-created packages with an expected ledger use full durable product/request/confirmation authorization rather than provider-observed readback. ## Relationship rollup Scale lives on the provider-account relationship rather than the default campaign list. Each account may return a `campaignRollup`: | Field | Meaning | | - | - | | `campaignsTracked` | Non-retired tracked campaigns on the account | | `campaignsActive` | Tracked campaigns currently active | | `trackedSpend` | Provider-reported delivered spend across tracked campaigns in the trailing window | | `managedSpend` | Delivered spend across canonical Apostra-managed campaigns in the trailing window | | `managedShare` | `managedSpend / (trackedSpend + managedSpend)`; `null` when no spend was accounted | | `currency` | Account ISO 4217 currency | | `windowDays` | Trailing aggregation window | Money never rolls up across accounts because accounts on one connection may use different currencies. ## Presences: audiences and event sources Connected accounts may expose first-party audiences and event sources as account presences. Use `GET /api/v2/buyer/storefront-connections/{connectionId}/accounts/{accountId}/presences` or `list_account_presences`. This is a read-only discovery surface; it does not sync an audience or authorize a provider mutation. ## Read delivery through the provider Use the normal campaign delivery endpoint: ```bash theme={null} curl \ "https://api.apostra.com/api/v2/buyer/campaigns/cmp_123/delivery?startDate=2026-01-01&endDate=2026-07-10" \ -H "Authorization: Bearer $SCOPE3_API_KEY" ``` Each call reads through to the connected provider's `get_media_buy_delivery`. Raw payloads and time series are not cached. Only the latest aggregate media-buy/package metrics, exact requested window, and refresh time are persisted. Subscription and metadata sweeps never pull delivery. If dates are omitted, the read requests the one-year window through yesterday. Provider window limits may cause Apostra to split the request into consecutive chunks before merging aggregate results. ## Errors and recovery * Account or mapping not found: re-list the official connection and discovered accounts, then use only returned IDs. * `lastSyncStatus: "ERROR"`: inspect safe `errorCode`, `errorField`, `errorReason`, and `upstreamCode` fields. Reconnect only for authentication or authorization failures; retry provider-shape or availability failures. * `mirrored: 0` with successful sync is an empty eligible history window, not a failed backfill. ## Boundaries * The tracked-campaign surface is subscription, mirror, refresh, presence, relationship-rollup, and delivery read-only. * Campaigns created or managed through Apostra use the canonical buyer product and media-buy lifecycle, not a tracked-campaign write path. * Change webhooks, scheduled delivery/export, invoice-grade reporting, and BigQuery delivery history are not part of tracked-campaign mirroring. * Availability never subscribes an account and never grants authority to mutate its campaigns. # Media-buy Frequency Caps Source: https://docs.apostra.com/v2/buyer/campaigns/media-buy-frequency-caps Apply an AdCP 3.2 frequency cap independently to each seller media buy in a V3 campaign ## Overview Use `frequencyCap` on V3 `save_campaign` to limit exposure within each seller's media buy. The seller maintains one counter across every participating package in that media buy, following the AdCP 3.2 aggregate frequency-capping contract. Existing campaigns are unchanged. The field is optional and is only available while the campaign is a draft. > [!WARNING] > > This is not a cross-seller campaign cap. Each seller maintains its own > counter. If a campaign buys from two sellers with a cap of three impressions > per individual, the same individual could receive three impressions from > each seller. ## Set the cap Pass the cap when creating a campaign, or update it before launch: ```json theme={null} { "campaignId": "CAMPAIGN_ID", "frequencyCap": { "level": "mediaBuy", "config": { "maxImpressions": 3, "per": "individuals", "window": { "interval": 7, "unit": "days" } } }, "idempotencyKey": "campaign-frequency-cap-v1" } ``` | Field | Description | | - | - | | `level` | Must be `mediaBuy`. Other levels are rejected. | | `config.maxImpressions` | Maximum impressions in the configured window. | | `config.per` | Reach unit: `individuals`, `households`, `devices`, `accounts`, `cookies`, or `custom`. | | `config.window` | Positive interval with a unit of `seconds`, `minutes`, `hours`, `days`, or `campaign`. | Pass `frequencyCap: null` to clear the cap from a draft before accepting a proposal. Omit the field to leave the current value unchanged. ## Seller and product eligibility When a cap is present, `request_proposals` asks sellers for products that can participate in the requested shared media-buy counter. A seller must declare the AdCP 3.2 `media_buy.aggregate_frequency_capping` capability, and every selected product must declare compatible `media_buy_support.frequency_cap` support. A seller proposal must also state the exact cap it is offering. If it leaves the cap out or changes it, that proposal is not shown as a usable quote. The seller's compatible products may still be available for a direct MediaBuy request, which must confirm the cap when created. The campaign launch rechecks those declarations against the exact selected products. It fails before seller dispatch if support is missing or the cap is outside a seller or product constraint. ## Cap levels | Level | Counter scope | V3 campaign support | | - | - | - | | Campaign | Shared across every seller in the campaign | Not available. This requires cross-seller identity and counter coordination. | | Media buy | Shared across participating packages bought from one seller | Available through `save_campaign.frequencyCap` with `level: "mediaBuy"`. | | Package | Maintained for one package | Available on `save_media_buy.products[].targetingOverlay.frequency_cap` when the seller supports it. | Selecting one seller does not change the declared level to `campaign`. It remains a media-buy cap, which keeps the saved contract stable if cross-seller campaign capping is added later. ## Read and update behavior The campaign workspace returned by `save_campaign` and `get(kind: "campaign")` includes the saved `frequencyCap`. The value is the campaign's requested media-buy policy. Each seller's completed AdCP 3.2 create response must echo the effective root `frequency_cap` before synchronous or asynchronous creation is treated as capped. `get(kind: "media_buy")` then returns `frequencyCap.status: "confirmed"` with the seller's effective cap. Package caps are independent. Put the AdCP `frequency_cap` directly on one product selection when only that package should count exposures: ```json theme={null} { "campaignId": "cmp_123", "sellerId": "42", "products": [ { "productId": "sf1:42:reach", "budget": 5000, "targetingOverlay": { "frequency_cap": { "max_impressions": 3, "per": "individuals", "window": { "interval": 7, "unit": "days" } } } } ], "idempotencyKey": "package-frequency-cap-v1" } ``` The package cap is sent only on that package. It is not combined with, or promoted into, the MediaBuy-wide counter. On Meta and Pinterest, a capped package must resolve to at most one offering (one ad set or ad group). Each offering would otherwise have a separate provider counter. If a package resolves to several offerings, the request fails before creating the provider buy; separate the offerings into packages or remove the package cap. Package-cap shapes are seller-specific. V3 accepts the AdCP 3.2 cooldown controls (`suppress` or deprecated `suppress_minutes`) and the `max_impressions`/`per`/`window` control. The seller advertises package-cap targeting through `media_buy.execution.targeting.frequency_cap` and must reject any shape it cannot represent exactly. Live cap changes are not available yet. A saved MediaBuy draft keeps the cap it had when it was created, even if the campaign's draft cap changes later. MediaBuys saved before this feature remain uncapped. To use the new cap, remove the old draft, request proposals again, and create a new MediaBuy. Once a proposal is accepted and bound to a MediaBuy, remove that buy before changing the cap. Launch rejects a buy if its saved cap differs from the cap used for the accepted proposal. # Media Buy Source: https://docs.apostra.com/v2/buyer/campaigns/media-buys One AdCP transaction with one sales agent — platform-created or mirrored from a connected provider account ## Overview A **Media Buy** represents one AdCP transaction with one sales agent. For platform-managed campaigns, Apostra stages DRAFT media buys before a [Campaign](/v2/object-guides/campaign) executes. During the tracked-campaign alpha, Apostra can also project one storefront-managed media buy into one read-only [tracked campaign](/v2/buyer/campaigns/directed-campaigns). Campaigns managed by Apostra use the canonical buyer product and media-buy lifecycle instead of a separate tracked-campaign write path. When a campaign defines [channel groups](/v2/buyer/campaigns/channel-groups), each media buy also preserves the coherent inventory selection it was created for. The compiler creates one buy per channel group, seller, and settlement currency, and reporting can roll delivery back up by that same group. > [!NOTE] > > For platform-managed campaigns (`management: "managed"`), DRAFT media buys > exist after staging and reach a seller at execution. After > `POST /campaigns/:id/execute`, the campaign holds a > `mediaBuys[]` array — one entry per channel group, sales agent, and settlement > currency (or per sales agent and currency when no groups are defined). A > `directed` campaign instead > always projects exactly one mirrored media buy and has no execute step. > Manual product staging via `create_media_buys` is available on campaigns with > `autonomy.inventorySelection: "manual"` (the default) or `"propose"`. Campaigns > with `autonomy.inventorySelection: "automatic"` reserve composition for > `auto_select_products` and reject manual staging calls. ### Where a media buy sits in the hierarchy A campaign fans out into media buys, each of which fans out into packages, each of which delivers: ``` Campaign └── Media Buy (one per channel group, sales agent, and currency) └── Package (one per product per pacing period) └── Delivery (impressions, spend, clicks) ``` See [Package](/v2/buyer/campaigns/packages) for the per-product, per-pacing-period unit beneath each media buy. ## Product format identity Products embedded in a media-buy read expose their URL-free canonical `formatOptions` declarations. For a composed product, these declarations are the only format identity returned; Apostra does not manufacture a legacy `creativeFormats` value or an agent URL from the canonical kind. A directly connected older seller may still return `creativeFormats` while its catalog is migrating, but new integrations should read `formatOptions` and use the stable `format_option_id` to select the exact option during media-buy creation. For a wholesale product, Apostra captures `formatOptions` from the exact discovery result you selected and carries that snapshot onto the media-buy line item. It does not re-read a later public or account-private seller cache when serving the buy. Historical wholesale buys created before snapshots were stored may omit the optional field; re-run discovery to create a buy with current requirements. Media buys created from an older composed-product row can temporarily retain the exact legacy `creativeFormats` readback until that row is refreshed. This compatibility path does not affect newly composed products. ## Budget A media buy's budgets — its per-product budgets and the package budgets beneath it — are **gross**: fee-inclusive, in the buyer's currency, the same denomination as the campaign's `budget.total`. The media/fee split is derived at the fee terms locked when the buy was created, and media buy reads return it read-only: ```json theme={null} { "mediaBuyId": "mb_abc123", "budget_denomination": "gross", "budget_breakdown": { "media_budget": 23000, "fee_amount": 2000, "fee_rate_percent": 8, "effective_gross_cpm": 20.11 } } ``` * `budget_denomination` — always `"gross"`. * `budget_breakdown.media_budget` — the portion of the gross budget that buys media. * `budget_breakdown.fee_amount` — Apostra fee inside the gross budget. * `budget_breakdown.fee_rate_percent` — the fee rate locked when the buy was created. Budget updates on a live buy re-split at this rate; later contract changes affect new buys only. * `budget_breakdown.effective_gross_cpm` — the buy's gross budget ÷ impression goal × 1000: the all-in price per thousand impressions, so "budget ÷ CPM = impressions" holds on the numbers you operate on (for a buy priced at a single fixed seller CPM, it works out to `seller CPM ÷ (1 − fee rate)`). `null` when the buy has no positive impression goal or no gross budget. * `budget_breakdown.operator_fees` — present only on a buy with an operator fee, for a caller who may see it: the operator's fee inside the gross budget as `{ name: "operator_fee", amount, beneficiary }`. A caller who may not see it receives no `budget_breakdown` for that buy. See [Operator fees](/v3/operator-fees). `budget_breakdown` is informational — you set and update gross budgets only. Legacy buys created before fee terms were locked carry neither `budget_denomination` nor `budget_breakdown`, and report delivered spend net, as the seller reported it. On all other buys, delivered spend on buyer surfaces is gross too, so delivery compares directly against these budgets. Sellers receive and report the media amount with the fee already carved out, and never see your fee rate. See [Budgets and fees](/v2/concepts/budgets-and-fees). ## Price control The selected product pricing option determines whether you set a bid. For a fixed-price option, the seller has already set the price: omit `bidPrice`. Sending a bid with a fixed-price option is refused before Apostra changes a draft, whether you create a new draft or add to an existing one. For an auction option, send the positive `bidPrice` you are willing to pay, subject to the option's published floor and any maximum. ## Status A media buy's `status` is one of: | Status | Meaning | | - | - | | `DRAFT` | Created but not yet submitted | | `PENDING_APPROVAL` | Submitted; awaiting seller approval | | `INPUT_REQUIRED` | Seller needs additional information before approval can proceed | | `ACTIVE` | Live and delivering | | `PAUSED` | Spend halted; resumable | | `COMPLETED` | Flight finished or fully delivered | | `CANCELED` | Stopped before completion | | `FAILED` | Submission or execution error, including a buy the storefront approved but could not send to any seller | | `REJECTED` | Seller declined the buy | | `ARCHIVED` | Soft-deleted | > [!NOTE] > > This is Apostra buyer rollup status — a coarser, derived view of the > per-buy AdCP wire status (`pending_creatives`, `pending_start`, `active`, …) the > sales agent returns, surfaced here as `adcp_status` on > [Get media buy status](/v2/buyer/campaigns/tasks/get-media-buy-status). > > When a seller's first delivery response makes a media buy terminal, the failed > buy says that it will not be retried automatically. It does not say that > retries were exhausted. That wording is reserved for a delivery that used its > retry budget. In both cases Apostra shows buyer-safe guidance, never the > seller's raw response text. > > `pending_creatives` and `pending_start` both collapse to `PENDING_APPROVAL` > here; `active` → `ACTIVE`, and so on. See > [Media buy lifecycle](/v2/concepts/media-buy-lifecycle) for the full mapping. ### Most-restrictive rule For a multi-agent campaign, the surfaced status is the **most restrictive** across all agents. For example, if one media buy is `ACTIVE` and another is `PENDING_APPROVAL`, the campaign reports `PENDING_APPROVAL`. `INPUT_REQUIRED` signals that a seller is blocked waiting on more information. ### How status flows Status updates reach you two ways: * **Webhooks** from sales agents — preferred, near-real-time. No polling needed. * **Polling** via `GET /api/v2/buyer/campaigns/:id/media-buy-status`, which queries each agent directly and persists any changes. When a directly connected sales agent returns a submitted task, Apostra records `pendingAt: "salesagent"` and keeps that task as the sales agent's polling handle. It does not treat the buy as waiting for Storefront approval. `pendingAt: "storefront"` is reserved for a buy held by the Storefront approval path. ```bash theme={null} curl https://api.apostra.com/api/v2/buyer/campaigns/cmp_987654321/media-buy-status \ -H "Authorization: Bearer $SCOPE3_API_KEY" ``` > [!TIP] > > Polling is most useful right after execution, while you wait on publisher approvals. Once webhooks are flowing, prefer them — see [Get media buy status](/v2/buyer/campaigns/tasks/get-media-buy-status). If you attach a creative while a seller is reviewing the media buy, Apostra automatically retries delivery after approval. You do not need to detach, reattach, or resubmit it. Retry can continue for up to 24 hours; if delivery still has not completed, contact the seller or Apostra support. Some sellers return their own creative ID after delivery. Apostra displays it when available, but a missing seller ID alone does not mean delivery failed. If you edit the content of a creative attached to a submitted media buy while that buy's seller is still reviewing the previous version, Apostra sends the new version to the seller straight away instead of waiting for the earlier review to finish. A creative held only in a seller's library, with no submitted buy, reaches that seller the next time you sync it. Either way, a seller's answer about a previous version is recorded but not applied, so a creative is never shown as approved for content the seller has not reviewed. ### Creative format fit Creative format fit is evaluated for each product, not once for a campaign. When a Creative is attached or considered for a media buy, Apostra compares its canonical `formatKind` and `formatParams`, plus inspected uploaded-media facts when available, with that product's declared format option. The response is `fits`, `fits_with_adaptation`, `insufficient_facts`, or `nothing_fits`. If the product requires a size, duration, codec, or container that the Creative does not declare and its uploaded asset has not established, Apostra returns `insufficient_facts` and names what is missing. It cannot confirm that an uninspected or parameter-less Creative fits, but the attachment proceeds unconfirmed. Required adaptations are named as `resize`, `letterbox`, `trim`, or `transcode` with the target option's parameters. `nothing_fits` names the closest option and what differs. An attachment does not perform adaptations. A Creative that needs adaptations is forwarded as-is for seller review, and the attachment records the required adaptations in its audit record. When a product requires a fixed size, or a list of accepted sizes, and an attached HTML5 or display-tag Creative has no known width and height, launch stops before anything is sent to the seller. The media buy stays in Draft and the error names the Creative and the size the product needs. Re-upload the Creative with its size declared (see [Bring your own creative](/v2/buyer/creatives/bring-your-own-creative)), then launch again. Products that accept any size, or a range of sizes, do not require a declared size. ### Why a storefront held or rejected your buy A `PENDING_APPROVAL` status means the storefront's operator is reviewing your buy before it reaches inventory; a rejection carries a buyer-safe reason. Sellers judge buys against **Brief Acceptance** (the categories, advertisers, and rules they will and won't carry). A deterministic hard finding supplies a strong operator-facing rejection signal, while an ambiguous or AI-judged conflict escalates or fails closed. Clearly on-policy buys auto-forward; ambiguous results, evaluator errors, and deterministic hard findings enter human review. Understanding the seller-side model helps your agent interpret a hold or rejection and adjust — see [AI Business Rules](/v2/concepts/ai-business-rules). For an exact own-supply campaign managed by the Media Company that owns the Storefront, revision-fenced launch confirmation and authenticated non-cancellation updates are Storefront media-buy decisions. They do not create a second operator approval task. The source can still return a pending or rejected status, and every validation, readiness, financial, and safety gate remains in force. ## Why isn't my buy live? For storefront-routed buys, the platform derives a **why-visibility annotation** from its forwarding records and exposes it in three places: the single-buy lookup below, the nested `mediaBuys[]` in `GET /campaigns/:id` (camelCase fields), and the [media-buy status poll](/v2/buyer/campaigns/tasks/get-media-buy-status) (snake\_case fields). The fields are present only when forwarding state exists; a buy transacted directly with a sales agent carries none. ### One-call lookup ```bash theme={null} curl https://api.apostra.com/api/v2/buyer/media-buys/mb_abc123 \ -H "Authorization: Bearer $SCOPE3_API_KEY" ``` ```json theme={null} { "mediaBuy": { "mediaBuyId": "mb_abc123", "name": "CTV — Premium News", "status": "PENDING_APPROVAL", "pendingAt": "salesagent", "pendingReason": "awaiting_source_moderation", "pendingSince": "2026-07-03T09:12:00Z", "sourceMessage": "Awaiting manual moderation", "forwardedAt": "2026-07-03T09:11:58Z", "buyerReference": "sf:42:sf_mb_1783031864469_8pbc8mmr", "startTime": "asap", "endTime": "2026-07-31T23:59:59Z", "createdAt": "2026-07-03T09:10:00Z", "updatedAt": "2026-07-03T09:12:01Z" } } ``` ### `pendingReason` — what the buy is waiting on `pendingReason` is a **derived annotation, never a status** — it explains a wait; the buy's `status` is unchanged. It is derived per source leg and rolled up to the most-blocking wait. See [Media buy lifecycle](/v2/concepts/media-buy-lifecycle#pendingreason-whose-side-owns-the-wait) for the full vocabulary and whose side owns each wait. ### `errorCode` — structured failure codes When forwarding failed or the buy was rejected, `errorCode` carries one of a small buyer-safe set, and `errorOwner` says who owns the fix: | `errorCode` | `errorOwner` | Meaning | What to do | | - | - | - | - | | `product_no_longer_available` | `platform` | A selected product no longer resolves on the storefront (catalog drift) | Re-run discovery and rebuild the buy with current products | | `source_rejected` | `seller` | The seller declined the buy | Read `sourceMessage`, address the reason, resubmit. When the seller's reason cannot be shown, `sourceMessage` reads "The seller rejected the media buy." — contact support with the `mediaBuyId` for the reason | | `storefront_rejected` | `seller` | The storefront operator rejected the buy in review, or the storefront rejected it automatically because its flight ended before anyone decided | Read `sourceMessage` (the reviewer's note, or the flight-ended reason), adjust, resubmit with a current flight | | `source_unavailable` | `seller` | The source could not be reached or went silent — not a refusal | Retry later; escalate with your `buyerReference` if it persists | | `source_configuration_error` | `seller` | The source is not configured to process this media buy | Ask the seller to correct the source configuration; retrying the same request will not help | | `product_configuration_error` | `seller` | The selected product contains stale or unusable configuration | Re-run discovery and rebuild the buy with a current product; ask the seller to correct the product if it remains available but still fails | | `invalid_request` | `buyer_input` | The request needs a correction from you | Fix the flagged input and resubmit | | `quote_expired` | `platform` | The held cross-currency rate expired before the buy forwarded | Re-discover and submit a new buy at the current rate | | `platform_error` | `platform` | A platform-side fault | Contact support with your `buyerReference` | `sourceMessage` is the first detail to read. When present, it contains a sanitized explanation from the source or reviewer, or buyer-safe correction guidance from the storefront forwarder. ### `buyerReference` — the handle to quote `buyerReference` (`sf::`) identifies this buy's exchange with the seller. Quote it, together with `forwardedAt`, when contacting the seller or Apostra support about a stuck buy — it is the reference both sides can look up. ### Media-buy identifiers and source references A storefront-routed buy can have a second identifier minted by the inventory source that executes it. Successful `create_media_buy` and `update_media_buy` responses may include buyer-safe pointers in `ext.source_references`: ```json theme={null} { "ext": { "source_references": [ { "source_name": "Example Sales Agent", "media_buy_id": "mb_upstream_123" } ] } } ``` * A sales-agent source leg includes its source name when available and upstream `media_buy_id`. * An ad server source leg includes the upstream `media_buy_id` without naming the internal source. * Linked and modular source legs are omitted. Their underlying source topology is intentionally private. Use a returned source reference when the counterparty asks for the identifier in its own system. Continue to use `mediaBuyId` with Apostra APIs and `buyerReference` when asking Apostra or the storefront to trace the full exchange. The field is absent when no eligible upstream reference is available. ### Transition notifications The same facts push to your [notification stream](/v2/guides/notifications) as `media_buy.*` events, fired once per transition (never per retry): | Event | Fired when | | - | - | | `media_buy.forward_failed` | The first forward attempt failed (payload carries `errorCode`, and `pendingReason` when the platform is retrying or a correction is needed) | | `media_buy.awaiting_source_moderation` | The source accepted the buy asynchronously and is moderating it | | `media_buy.source_rejected` | The source declined the buy (payload carries the sanitized reason) | | `media_buy.stuck` | The buy never resolved at the source (timeout or task failure) and needs attention | Each payload includes `mediaBuyId`, `buyerReference`, and the applicable `pendingReason` / `errorCode` / `errorOwner` / `sourceMessage`, so a reacting agent does not need a second call to learn why. A live buy that cannot take a change to one of its property lists, or whose lists no route enforces after the change, raises `media_buy.list_action_required` instead. Its payload carries `mediaBuyId`, `outcome` (`paused`, or `pause_failed` when the pause itself failed and the buy may still be delivering without its lists enforced), `reason` (`list_update_not_applied` or `list_not_enforceable`), and `propertyListIds`. See [Property lists → When a live buy is paused](/v2/guides/property-lists#when-a-live-buy-is-paused). ## Optimization goals Optimization goals are applied at the media-buy level — they propagate to every package in the media buy at execution time. Each goal is either **event-based** (optimize against tracked conversions) or **metric-based** (optimize against seller-native delivery metrics). ```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 } } ``` ### Target kinds | 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) | This level does not yet support an explicit `strength` on a `cost_per`/`per_ad_spend` target — some sellers (e.g. Meta) require a specific strength (`cap` for `cost_per`, `floor` for `per_ad_spend`) and reject a goal here that omits it. Set the goal on the campaign's `performanceConfig.optimizationGoals` instead (see [Optimization goal target strength](/v2/object-guides/campaign#optimization-goal-target-strength-performanceconfig)) when targeting such a seller. ### Supported metrics `clicks`, `views`, `completed_views`, `viewed_seconds`, `attention_seconds`, `attention_score`, `engagements`, `follows`, `saves`, `profile_visits`, `reach`. `impressions` is a delivery rollup — something a package reports, not a metric you can target (see [Delivery metrics](/v2/object-guides/campaign#packages)). For a reach or CPM-style goal, target `metric: "reach"`: ```json Reach goal — target unique reach at a minimum frequency theme={null} { "kind": "metric", "metric": "reach", "reach_unit": "individuals", "target_frequency": { "min": 3, "window": { "interval": 7, "unit": "days" } } } ``` `reach_unit` is required when `metric` is `reach` — one of `individuals`, `households`, `devices`, `accounts`, `cookies`, `custom`, and must be a unit the product declares support for. `target_frequency` is optional — omit it to let the seller maximize unique reach without a frequency constraint. > [!NOTE] > > This is the `mediaBuys[].optimization_goals` field on an *executed* media buy (snake\_case, the AdCP wire format — `reach_unit`/`target_frequency` included). It is distinct from `performanceConfig.optimizationGoals` (camelCase), which sets goals when *creating* a performance campaign and does not yet support `reach_unit`/`target_frequency` — conversely, `performanceConfig.optimizationGoals` supports an explicit target `strength`, which this level does not yet support. ## Goal commitment Every media buy booked after commitments were recorded carries a `goalCommitment`: what the seller committed to for your goal at the moment the buy was created. It is read off terms, never prose, and it never changes on the row it was booked on; a later change to the buy books a new row with its own commitment. | Field | Meaning | | - | - | | `source` | Where the answers came from: `proposal` when at least one product on the buy keeps the accepted proposal version's answer; `campaign` when every answer was derived from the campaign's goals and the products' own pricing and delivery terms. | | `proposalVersionId` | The accepted proposal version the buy was booked from, whenever there was one. It stays on the buy even when `source` reads `campaign` because none of the buy's products matched that version's answers, so you can always trace what was accepted. | | `kind` | `guaranteed`, `best_effort`, or `report_only`, using the same rules as a proposal's commitment (see [Read what a seller committed to](/v2/setup/v3/buyer-workflows#read-what-a-seller-committed-to)). The buy is only as committed as its least committed product. | | `askedGoal` | The goal you stated on the campaign when the buy was booked, or `null` when there was none. | | `answeredTarget` | The target the products' terms commit or aim at for that goal, of the same kind as your ask when they answer it: the worst across products, meaning the highest cost per unit, the lowest rate, or the lowest return. `null` when nothing answered a number. | | `meetsAskedTarget` | Whether every product answers your target, judged by its kind: a cost per unit is met at or under your ask, a rate or a return on ad spend at or above it. `false` when any product misses it or did not answer with the same kind, `null` when your goal carries no target or a maximise target, which has no number to meet. | | `goalAnswers` | One answer per product: pricing model and fixed price, delivery type, measurement terms, and its own commitment kind. | | `goalAnswers[].storefrontId` | The Storefront the answered line resolves to, fixed when the answer was booked. A later change to the draft keeps that line's answer for exactly that Storefront; two Storefronts' like-named products never share one. Present on every booked answer whose line resolved to a Storefront, campaign-derived or copied from an accepted proposal alike; absent only on a line with no resolved Storefront. | A buy created without a proposal still gets a commitment from its own terms: a fixed CPC on a guaranteed product reads `guaranteed` even if the campaign stated no goal, so the price you are paying per result is always on record. The commitment always answers for every product on the buy. When you accept a proposal into a draft that already holds other products, each product the proposal answered keeps that answer and every other product is answered from its own pricing and delivery terms, so `kind` is the buy's weakest product, never the proposal's alone. A proposal version can only be booked onto the campaign it was quoted for; accepting a version that does not belong to the campaign fails the booking rather than recording a commitment that is not yours. A product booked on several lines of the same buy gets one answer per line. Each proposal answer goes to one line, and a line the proposal did not answer is answered from its own booked terms, so one line's price is never copied onto another. The same `goalCommitment` is returned on the V3 `media_buy` reads (`get` and `search`) and on each media buy in a campaign read with the `mediaBuys` include. It is read-only. `get` returns at most 50 line answers, the same bound as the line items it lists; `search` and the campaign include return at most 20 per buy. When a buy has more answers than a read carries, that read sets `goalAnswersTruncated: true`. ## Reading and tuning media buys `GET /api/v2/buyer/media-buys/:mediaBuyId` returns the selected media buy's `optimizationGoals` alongside its status and diagnostic fields. Use the campaign read when you also need packages, products, or delivery details. Because media buys aren't created directly, you act on them through `PUT /api/v2/buyer/campaigns/:id` using the `mediaBuys[]` array. Each entry targets one media buy by `mediaBuyId` and carries an `action`. When lowering a campaign's `budget.total` below what live media buys have already allocated, include explicit `mediaBuys[].packages[].budget` reductions in the same request — the update is validated against the projected post-update allocation and applied atomically. A budget decrease that leaves live buys allocating more than the new total is rejected with `INSUFFICIENT_MEDIA_BUDGET`. See [Reducing campaign budgets](/v2/buyer/campaigns/tasks/update-campaign#reducing-campaign-budgets). | Action | Effect | | - | - | | `update` | Default — modify budget, pacing, optimization goals, or creatives | | `cancel` | Cancel a running media buy | | `delete` | Archive the media buy | ### Update budget and optimization goals ```json Update budget + optimization theme={null} { "mediaBuys": [ { "action": "update", "mediaBuyId": "mb_abc123", "packages": [ { "packageId": "pkg_1", "budget": 15000, "pacing": "even" } ], "optimization_goals": [ { "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" } ] } ``` > [!WARNING] > > Always confirm `optimization_goals` with the buyer before changing them — silent goal changes break optimization continuity. ### Cancel a single package To cancel one package without touching the rest of the media buy, pass `packageIds` on the `mediaBuys[]` entry: ```json theme={null} { "mediaBuys": [ { "action": "cancel", "mediaBuyId": "mb_abc123", "packageIds": ["pkg_2"], "reason": "format not supported on this device mix" } ] } ``` See [Package](/v2/buyer/campaigns/packages) for what each package represents. ### Override creative attachment Omitting `creative_ids` preserves the buy's existing creative assignments without revalidating, resyncing, or forwarding any creative data. Include `creative_ids` only when you intend to change which creatives are attached. ```json theme={null} { "mediaBuys": [ { "action": "update", "mediaBuyId": "mb_abc123", "creative_ids": ["cr_video_15s", "cr_video_30s"] } ] } ``` * **Omit `creative_ids`** to leave existing assignments unchanged. The platform does not fetch, validate, sync, or forward creative data for the buy in this case. * **Explicit `creative_ids`** replaces the buy's current assignments with exactly the provided list. Each ID must already be linked to the campaign and match a format accepted by the media buy's products. A validation error is returned if any ID fails this check — the field is not silently filtered. * **`"creative_ids": []`** explicitly clears all creatives from the media buy. * `creative_ids` is only valid with `action: "update"`. It is rejected with `cancel` or `delete`. ### Package flight-date changes Updating a package's `startTime` or `endTime` is rejected with `CAPABILITY_NOT_SUPPORTED` when **both** of the following are true: the target package has no persisted flight dates, and the seller has not yet returned action metadata (`valid_actions` or `available_actions`) for this buy. Otherwise, the date change proceeds. This guard prevents forwarding a date change to a seller that has not declared what mutations it supports. It typically means the buy has not yet received a complete seller response — wait for the seller to confirm the buy is active and has returned its capabilities before adjusting package dates. ### Pausing never waits for seller approval A buyer pause reaches the seller's ad server immediately, including on a storefront that reviews every change manually and on guaranteed buys. Pausing suspends delivery only: it does not change the buy's budget, flight dates, or contract, and the buyer alone carries the cost of the buy going dark. The seller is notified that the pause took effect but is not asked to approve it. This applies to an update whose only change is the pause. A pause combined with any other change (a budget, date, or package edit) is an ordinary update and follows the storefront's approval rules. See [pause media buy](/v2/buyer/campaigns/tasks/pause-media-buy) for how to tell that delivery actually stopped. ### Pausing on each kind of storefront A pause is never refused because an earlier change to the same buy is still pending, for example a budget change waiting for the seller's approval. The earlier change keeps its place; when it is applied later, the buy stays paused. * **Storefronts that forward to a sales agent**, including Apostra-hosted storefronts on Google Ad Manager: the pause goes straight to the seller's sales agent. * **Ad-platform storefronts** (for example Google Ads, Amazon, Spotify, Meta, TikTok): the pause goes straight to the ad platform, whether you use Apostra's [pause media buy](/v2/buyer/campaigns/tasks/pause-media-buy) or call the storefront's AdCP `update_media_buy` with `paused: true` and nothing else. * **Meta:** if another change to the same Meta campaign is still being written to Meta, the pause can wait up to 20 seconds for it to finish. If that change is still running after the wait, the pause is refused with a retryable `CONFLICT` (reason `media_buy_update_in_progress`); retry the pause. Whenever a seller or ad platform refuses or fails a pause, you get an explicit error, never a silent success, and Apostra's operations team is alerted because the buy may still be delivering. ### When a seller is still applying a pause Some sellers answer a pause with an AdCP "accepted, still processing" status (`submitted` or `working`) instead of confirming it. Delivery has not stopped yet, so Apostra does not show the buy as paused: * The media buy stays `ACTIVE` and its `pauseRequestedAt` (on `GET /media-buys/:mediaBuyId`) records when the pause was first requested. A single-buy [pause](/v2/buyer/campaigns/tasks/pause-media-buy) returns `202 Accepted`. * When the seller reports the buy paused, Apostra moves it to `PAUSED` and clears `pauseRequestedAt`. * A [campaign pause](/v2/buyer/campaigns/tasks/pause-campaign) waiting on such a seller returns `503` and the campaign stays `ACTIVE`. It becomes `PAUSED` on its own once every media buy confirms. * If the seller has not confirmed after two hours, Apostra's operations team is alerted once. Apostra never sends the pause again on its own; pausing again is your call and is safe. A resume works the same way in reverse. When a seller answers a resume with "accepted, still processing", the buy stays `PAUSED`, because it is not delivering yet: * A single-buy [reactivate](/v2/buyer/campaigns/tasks/reactivate-media-buy) returns `202 Accepted` with `resumeRequestedAt`. * A [campaign reactivate](/v2/buyer/campaigns/tasks/reactivate-campaign) waiting on such a seller returns `503` with `reason` `campaign_reactivate_requested` when those buys are all that is outstanding. * The buy becomes `ACTIVE` on its own once the seller reports it delivering, and so does its campaign. ### Resuming a paused media buy A resume on its own restores terms the seller already approved, so it is not treated as a material change: * **Storefronts that forward to a sales agent** send a resume on its own straight to the seller's ad server, without asking the seller to approve it. * **Ad-platform storefronts with manual review** (for example Google Ads, Amazon, or Spotify) review every change, so a resume still waits for the seller there. * **A resume combined with any other change** (a budget, date, or package edit) is an ordinary update and follows the storefront's approval rules on every storefront. See [reactivate media buy](/v2/buyer/campaigns/tasks/reactivate-media-buy) for the responses. ### Updating a paused media buy A `PAUSED` media buy accepts the same updates as an `ACTIVE` one — budget, flight dates, name, packages, and creatives. Pausing halts delivery; it does not lock the buy against edits. * **Budget changes still require seller approval**, exactly as they would on an `ACTIVE` buy. The materiality classification that decides whether a change needs approval does not special-case `PAUSED` — any budget change is material regardless of the buy's current status. * **Approving an update never resumes the buy.** Once a queued change is accepted, the newly-activated values (budget, dates, etc.) go live, but the buy's status is preserved as `PAUSED` — approval changes what the buy is worth, not whether it's running. The only way to move a buy from `PAUSED` to `ACTIVE` is [reactivate media buy](/v2/buyer/campaigns/tasks/reactivate-media-buy) (or a campaign-level [reactivate](/v2/buyer/campaigns/tasks/reactivate-campaign)). * **Updates on a paused buy still propagate to the seller.** Submitting a change against a `PAUSED` buy is not a local-only edit — it queues exactly the same way it would for an `ACTIVE` buy. See [Pending changes: what is live vs what you asked for](/v2/concepts/media-buy-lifecycle#pending-changes-what-is-live-vs-what-you-asked-for) for how to read `pendingChange` while an update on a paused buy is in flight. ## Cascade behavior Actions on the parent campaign cascade down to its media buys and packages: * **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. * **Per-buy pause/reactivate** — [pause a single media buy](/v2/buyer/campaigns/tasks/pause-media-buy) and [reactivate a single media buy](/v2/buyer/campaigns/tasks/reactivate-media-buy) are the non-cascading counterparts: each touches exactly the targeted media buy, never the parent campaign row and never a sibling media buy. Use these when only one buy on a multi-agent campaign needs to stop or resume; use the campaign-level pause/reactivate above when the whole campaign should move together. ## Failures and debug When a media buy fails to execute — a publisher rejection or an ADCP error — `POST /api/v2/buyer/campaigns/:id/execute` returns structured `errors[]`: ```json theme={null} { "campaignId": "cmp_987654321", "previousStatus": "DRAFT", "newStatus": "ACTIVE", "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. ## Related concepts > **[Campaign](/v2/object-guides/campaign)** > > The parent media plan that spawns media buys at execution > **[Package](/v2/buyer/campaigns/packages)** > > One per product per pacing period, beneath each media buy > **[Get media buy status](/v2/buyer/campaigns/tasks/get-media-buy-status)** > > Poll live ADCP status across sales agents > **[Update campaign](/v2/buyer/campaigns/tasks/update-campaign)** > > The endpoint that reads and tunes media buys # Package Source: https://docs.apostra.com/v2/buyer/campaigns/packages 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. ### Where a package sits in the hierarchy ``` Campaign └── Media Buy (one per channel group, sales agent, and native settlement currency) └── 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__ ``` `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. ### 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. ### 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 > **[Get media buy packages](/v2/buyer/campaigns/tasks/get-media-buy-packages)** > > Resolve a description ("the display package ending 2026-08-11") to a packageId > **[Media Buy](/v2/buyer/campaigns/media-buys)** > > The parent ADCP transaction that spawns packages > **[Pacing periods](/v2/guides/pacing-periods)** > > Time-windowed spend intensity that multiplies products into packages > **[Campaign](/v2/object-guides/campaign)** > > The parent media plan # Campaign Targeting Source: https://docs.apostra.com/v2/buyer/campaigns/targeting Attach structured targeting intent to a campaign through the V3 MCP surface ## Overview You can attach structured **targeting intent** to a campaign when creating or updating it through the V3 MCP surface (`save_campaign` on `/mcp/v3`). Targeting records what the campaign *intends* to reach — geography, language, device type, daypart, and demographic dimensions — and persists that intent with the campaign object. > [!NOTE] > > Campaign targeting is stored and returned on the campaign workspace. When you > create a media buy with `save_media_buy`, its targeting can narrow the V3 > campaign overlay but can never widen it. ## Passing targeting to save\_campaign The `targeting` parameter is optional. Omit it to leave targeting unset; include it to attach one or more dimensions: ```json theme={null} { "targeting": { "geo": [ { "requirementId": "geo-us-only", "strength": "required", "include": ["US"] } ], "device": [ { "requirementId": "ctv-preferred", "strength": "preferred", "include": ["ctv"] } ], "language": [ { "requirementId": "en-only", "strength": "required", "include": ["en"] } ] } } ``` Each dimension is an array of **requirement entries**. A requirement entry has: | Field | Type | Description | | - | - | - | | `requirementId` | string | A stable, caller-assigned ID for this requirement. Use it to identify the entry in future updates. | | `strength` | `"required"` \| `"preferred"` | Whether the dimension is a hard constraint or a preference. | | `include` | string\[] | Values to target (must include at least one). | | `exclude` | string\[] | Values to exclude (optional). | ## Supported dimensions | Dimension | Values | | - | - | | `geo` | ISO 3166-1 alpha-2 country codes (e.g. `"US"`, `"CA"`) and DMA codes | | `language` | BCP-47 language tags (e.g. `"en"`, `"fr"`) | | `device` | `"ctv"`, `"mobile"`, `"desktop"` | | `dayparts` | Day-of-week and hour-of-day slots | | `demographics` | Demographic segment identifiers | ## Reading targeting back After save, `targetingOverlay` appears on the campaign workspace returned by `save_campaign` and by `get(kind: "campaign")`. Existing campaigns with no targeting set omit the field entirely. The deprecated `targeting` field keeps its existing behavior: it is returned verbatim only when the campaign has stored legacy targeting. ## Updating targeting Pass `targeting` in a `save_campaign` update to replace the stored targeting intent. Partial dimension updates replace the entire `targeting` object — each call is the full desired state, not a patch. ## Targeting stated in the brief The campaign brief reaches every seller in a proposal round. Sellers read it to understand the campaign and to choose what to recommend, for example inventory suited to an age group or an age signal they sell. A hard requirement stated only in the brief, such as "please target women 18-55", is not applied the same way by every seller: * [Storefront-built and Agent-supplied storefronts](/v2/guides/discovery#storefront-agents-in-discovery) read it from the brief and never refuse a request only because the brief states targeting. Whether it restricts delivery or is used only to recommend products depends on the AdCP version of the request; see [Exact Age Targeting](/v2/guides/age-targeting#overview). * Built-in platform adapters read one closed age range from the brief. * Other sellers interpret the brief with their own sales agent. AdCP asks buyers to put every requirement that has a structured field into that field, so the requirement does not depend on each seller reading prose. Set hard targeting in the campaign's `targetingOverlay` and keep the brief as written. The brief still goes to every seller as context. ```json theme={null} { "campaignId": "CAMPAIGN_ID", "expectedRevision": 3, "idempotencyKey": "acme-autumn-targeting-v1", "targetingOverlay": { "geo_countries": ["US"], "demographics": { "age": { "min": 18, "max": 55, "include_unknown": false } } } } ``` * `targetingOverlay` replaces the campaign's stored overlay. Send the fields already set, such as the campaign's countries, together with the new one. * Only move a requirement that has a field. A requirement with no structured field, such as gender, stays in the brief only. * A preference ("ideally 18-55") or a description of the audience ("our customers are mostly in their 30s") stays in the brief. A structured field is applied as a restriction wherever the seller supports it. * Keep the brief and the structured fields consistent. AdCP 3.2 requires a seller to refuse a request whose brief contradicts its structured targeting. Structured targeting changes what you get back. Proposal rounds send the campaign's age range to storefronts that read it, and those storefronts return only products that can deliver that range. At launch, a campaign age range reaches only the sellers that can apply it; see [Campaign age range at launch](#campaign-age-range-at-launch). ## Campaign targeting and media buys `save_media_buy` applies the campaign targeting overlay as a guardrail to each product's `targetingOverlay`. An explicit product `targetingOverlay` supplies that buy's targeting, and campaign targeting still narrows it: overlapping values are kept, and a product value outside the campaign returns `CONFLICT`. A product can omit a campaign field to inherit it, or narrow it where the field has defined containment rules. It cannot widen the campaign audience. `signal_targeting` is excluded from the overlay because it is a priced, product-scoped selection that will come through the signals path. Typed `signal_targeting_groups` remains supported. On a V3 media-buy product overlay: Deprecated. Numeric codes replace names. Removal will be announced in release notes at least 14 days ahead; planned for 2 November 2026 (AI-10440). Codes must be in the Nielsen DMA dictionary; unknown codes return nearby code candidates. For country, region, metro, postal, language, browser, device type, and device platform includes, the buy uses the overlap. Excludes are combined. A buy's demographic age range is intersected, unknown ages are included only when both overlays allow them, and accepted age bases are intersected. The stricter age restriction wins. Each buy daypart must fit wholly inside a campaign daypart on the same clock; dayparts are never clipped or converted between timezones. For proximity, named places, and other structured values without a containment rule, a product must omit the field or repeat the campaign value exactly. If the included values for the same field do not overlap, `save_media_buy` returns a `CONFLICT` error. The error names the field and includes the campaign and product values so you can correct one of them. It does not create a media buy that targets everywhere. Omit a product geography field to inherit the campaign's value for that field. Language, device, daypart, and demographic campaign intent now participate in this guardrail. A demographic overlay still cannot dispatch while seller transport is on AdCP 3.1, so `save_media_buy` rejects it before seller contact. ### Campaign age range at launch A campaign's age range (`demographics.age`) is copied into every package when the campaign launches. A media buy on a Meta storefront that approves media buys automatically is sent under AdCP 3.2, and Meta applies the age range. Every other seller cannot yet apply a campaign age range at launch. That includes the other built-in platform adapters, a Meta storefront that reviews media buys manually, Storefront-built storefronts and external AdCP sellers. Their packages are created without the age range, so the media buy launches and runs without age targeting. The launch response says so with a `demographics_not_applied` warning that names each affected product and package `buyerRef`. A product can carry its own fixed age audience. It counts only when the product delivers that age to every impression, and when the age is an AdCP included signal, only when the signal states how the ages are known in a way the campaign accepts: verified or self-declared ages, plus modeled ages when the campaign allows modeled age. A survey-based or modeled audience claim is treated as no fixed audience (see [Which products match a requested range](/v2/guides/age-targeting#which-products-match-a-requested-range)): * **Fixed to exactly the campaign's range:** no warning; the product delivers it. * **Fixed to a range inside the campaign's**, such as 18-34 for an 18-55 campaign: the package is bought with the product's range, on every seller. The launch response confirms what was sent with a `demographics_narrowed` receipt naming the product and both ranges. That covers a product selected by id or a campaign range changed after you reviewed proposals. * **Fixed to a range that reaches ages outside the campaign's**, such as 18-64 for a 25-54 campaign: that media buy is refused unless its seller can apply the campaign's range, because it would deliver to people the campaign excludes. ## Campaign audiences and catalogs `save_campaign` also accepts `audienceConfig` and `catalogId`. Use `audienceConfig.targetAudienceIds` to include up to 100 audience IDs and `audienceConfig.suppressAudienceIds` to exclude up to 100 audience IDs. On an update, `deleteMissing: true` replaces both lists; otherwise, supplied IDs are added to the existing lists. `catalogId` is the public catalog ID returned by the catalog read surfaces; pass `null` on an update to remove it. Both fields are stored and returned by `get(kind: "campaign")`. Campaigns with an audience or catalog cannot launch yet because those objects are not available to sellers. The campaign readiness response names this blocker. Remove the field to launch without it, or wait for seller object delivery support. ## Targeting each product offers Each product says which targeting you may set on a package for it: its AdCP `overlay_support`, such as countries, regions, Nielsen DMA metros or a frequency cap, with any per-package limits the seller sets. You can book only what a product offers. Where you see it: * **`get_products`** returns `overlay_support` on each product that declares targeting, in both wholesale and brief results. A product composed from inventory carries the declaration of the inventory product it is built from. See [The targeting a product lets you set](/v2/buyer/discovery/multi-storefront-get-products#the-targeting-a-product-lets-you-set). * **V3 `search(kind: "wholesale_product")`** returns `overlaySupport` and `supportedTargeting` on each product that declares targeting. A declaration too large to return inline is left out of search, leaving `supportedTargeting`. * **V3 `get(kind: "wholesale_product")`** returns one product, by the `productId` and `advertiserId` from your search, with its complete `overlaySupport`. * **V3 `get(kind: "seller")`** returns the seller's own targeting (`targeting` and `supportedTargeting`). It applies to the seller's products that declare none of their own. When you save a draft media buy, create one, or update its targeting, each package's targeting is checked against its product's declaration. The targeting checked is what the buy will send the seller, including campaign targeting the package inherits. A package is refused when it targets a dimension the product doesn't declare, a metro or postal system it doesn't list, or more values than a per-package limit allows. Nothing is saved or sent to the seller when a package is refused. * **Through Apostra's buyer tools** (`save_media_buy`, `save_campaign`, `create_media_buys`) the refusal is a validation error on `targeting_overlay`, for example: "The selected products do not offer the requested targeting: package line-1 targets what product … does not offer: geo\_metros system uk\_itl1 is not offered by this product (offered: nielsen\_dma)." * **Through a storefront's AdCP endpoint** the refusal is `UNSUPPORTED_FEATURE` with `details.reason` `undeclared_product_targeting` and the same message. To fix it, remove the named targeting from the package, or from the campaign when the package inherits it, or choose a product that offers it. A product on a seller's managed sales agent that the seller hasn't narrowed offers the targeting the seller's ad server can apply, and is checked against that in the same way. A product on any other source that declares no targeting of its own isn't checked this way. The seller's own targeting applies to it instead, and a booking that asks for targeting the seller can't apply is refused. ## Rollout and release check This is available immediately on the public V3 `save_campaign` and `get(kind: "campaign")` surfaces; it has no separate customer enrollment or feature flag. The Buyer V3 API release owner verifies the release through the automated `PR Acceptance Tests` catalog contract proof, then confirms in the released environment that a campaign can save and read back an audience or catalog and that a launch attempt returns the seller-sync blocker. The release check passes only when all three behaviors agree: save persists the requested IDs, `get` returns them, and launch stays refused while seller sync is unavailable. Treat a missing readback value, a launch that succeeds, or a blocker other than the seller-sync refusal as a mitigation trigger. Roll back the release or remove the audience/catalog from the affected campaign; do not override the blocker or treat the objects as delivered. ## Backward compatibility `targeting` is deprecated, but its behavior is unchanged. `targetingOverlay` is the new V3 targeting field. Its removal is a separately announced change (AI-10440) with at least 14 days' notice. Existing calls and campaigns are not affected. # Bring your own creative Source: https://docs.apostra.com/v2/buyer/creatives/bring-your-own-creative Upload creative in chat and route it to a campaign — no separate upload screen ## Overview Start with **Add creative** in the Creative Library when you have a finished image or MP4 file; for a WAV or MP3 file, use **Upload** under **More** or ask the assistant to upload it. When the upload finishes, the Library opens the appropriate creator with the file in its first slot. Complete the creative with a name, click-through URL, and format. From a campaign Library, this is the composer; from an advertiser Library, it is the inline creator. Audio assets are browse-only in the Library's own creator, so build an audio Creative through the assistant or `save_creative`. This assembly path uses your uploaded assets and does not require Creative Engines. You can still hand Apostra an image, video, or ad tag in a conversation. Apostra asks which campaign should receive it. HTML5 bundles also need their landing-page URL before delivery. > [!NOTE] > > This is the chat-first path for creative you already have. To generate creative from a brief instead, see [Creative](/v2/object-guides/creative) and the creative manifest tasks. ## Generate an asset in the Library If Creative Engines is enabled for your Buyer Account, the advertiser Library's **Assets** tab includes **Generate**. Choose image, video, or voice, write a short brief, and choose one result from the generated variants gallery. The selected result lands in the advertiser Library as a **Draft**. It is not attached to a campaign automatically. Apostra chooses an eligible generation engine; the Library never asks you to choose one. See [Creative Engines](/v2/setup/v3/creative-engines) for eligibility and limits. The advertiser Creative Library also has a **Catalogs** tab for checking the feeds that can later support catalog-based creative work. Connecting or managing a feed opens the existing catalog manager; catalog-based creative building is not available from this tab yet. ## Previewing a creative Select a Creative or Asset to open its previewer beside the list on wide layouts and in a sheet on narrow ones. The list stays in place with its search, filters, grouping, and scroll position, so close the previewer with **Escape** or **Back to creatives** to continue browsing. The previewer shows the available preview, status and summary facts, and attachment choices. > [!NOTE] > > Promoted managed audio has an API adoption contract. See [Adopt a managed > audio source](/v2/buyer/creatives/tasks/adopt-managed-audio-source). It > records a creative and optional campaign association only; it never initiates > seller or destination delivery, attaches to a media buy, or makes anything > live. ## The flow **1. Upload the asset** Attach an image, video, tag, or tag sheet in the conversation. Apostra recognizes the media kind automatically. **2. Answer “which campaign?”** Apostra opens a short picker showing the advertiser and that advertiser's recent campaigns. You choose where the creative should live: * **An existing campaign** — pick it from the list. * **A new campaign** — describe it in plain language and Apostra creates it. * **Save to the advertiser** — park the creative at the advertiser level to assign later. The API and agent equivalent is [Create advertiser creative master](/v2/buyer/creatives/tasks/create-advertiser-creative-manifest). **3. Apostra maps it to placements** Once the creative is on a campaign, Apostra maps it to that campaign's placements. If an asset lands in the wrong slot, you can remap it. For an HTML5 bundle, add the advertiser destination in the creative's click-through URL field before media-buy sync. ## Uploading to the Creative Library ### Review uploads before they go Live Every image, video, and audio upload through the V3 upload Task arrives in the Creative Library as a **Draft**. Select **See it in your Library** from the upload confirmation, or ask to open the uploaded asset, to review it. The Library opens that Draft directly so it stays visible while you decide what to do next. Uploads through the V2 Buyer tools are added Live as before. When any Draft needs review, the top of the Library list shows **N Drafts waiting for you**. Select **Review** to switch the current Creatives, Assets, or Catalogs list to Drafts. If the current list has no Drafts, Review opens a list that does. The Creatives and Catalogs lists show Live items by default; choose **Draft** in their Status filter to see Drafts. The review shows the uploaded file's kind and suggests a name from the file name. It also suggests existing advertiser collections when their names match whole words in the file name. You can edit the name and collection choices; the kind stays read-only because it was verified from the uploaded file. Select **Save** to apply those edits and make the asset Live in one step. A video or audio Draft can remain processing; in that case Save is unavailable until processing completes. If processing fails, the Library tells you that the Draft must be discarded. Select **Discard**, then confirm inline, to remove the uploaded file. The assistant can open a Draft for review but cannot save or discard it for you. On the Buyer V2 or shared V3 connector, ask to upload a creative and select its advertiser. Both expose the same `upload_creative_asset` Task and storage flow; no destination connection or provider account is required. Every Buyer Account can save WAV and MP3 files up to 50,000,000 bytes as durable assets in that advertiser's Creative Library. Accepted WAV MIME types are `audio/wav`, `audio/x-wav`, and `audio/wave`; accepted MP3 MIME types are `audio/mpeg` and `audio/mp3`. WAV and MP3 upload is available to every Buyer Account: use **Upload** under **More** in the Library, or ask the assistant to upload the file. If the upload Task doesn't offer WAV or MP3, audio upload is temporarily unavailable in that environment. The Task also accepts an MP4 up to 2,000,000,000 bytes when managed-video ingress and inspection are configured. Managed video does not depend on the private JPEG/PNG upload option. Eligible connections accept JPEG and PNG files up to 30,000,000 bytes. The V3 `upload_creative_asset` Task also accepts a VAST XML tag up to 1,000,000 bytes. It verifies the tag as VAST before saving it as a ready-made Creative draft. VAST upload is not available through the V2 Buyer tools. A VAST upload does not ask for a destination format at upload time; destination requirements are checked when you build for that destination. You can select or drag up to 25 files into the upload Task at once. The Task shows one progress list and runs no more than three private uploads at a time. Each file can be queued, uploading, verifying, done, or failed; retrying a failed file does not re-upload files that already finished. Before the batch starts, enter Library tags once to apply them to every file, or edit tags for an individual queued file. Tags are values in the built-in `tags` label dimension, so the Library can filter them beside any other dimensions defined for the buyer account. The same per-file limits still apply: JPEG and PNG up to 30,000,000 bytes, MP4 up to 2,000,000,000 bytes when enabled, and WAV or MP3 up to 50,000,000 bytes. The V3 `upload_creative_asset` Task also accepts VAST XML up to 1,000,000 bytes. If a client can call tools but cannot render the embedded upload Task, `upload_creative_asset` returns a `fallback_url`. Open that HTTPS link to continue in Apostra with the authenticated customer and advertiser already selected. Without campaign composition, it opens that advertiser's Creative Library and its upload flow; no campaign is created or selected. When campaign composition was supplied, the link instead opens the **Add creative** upload flow directly with the campaign and any supplied creative name selected; the user only needs to choose the local file and finish the form. The link never contains file bytes, a signed storage capability, or provider credentials. The browser calculates the file's SHA-256 while streaming it, then uploads the bytes directly to private object storage with a create-only capability. 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. Every Task call rechecks the buyer, advertiser, and exact authenticated principal. MP4 upload preparation must finish within 30 seconds. If managed-storage validation or secure upload setup does not finish in time, the Task returns a retryable `SERVICE_UNAVAILABLE` error with the failed `preparePhase` and creates no upload reservation. Reservation creation uses a bounded database commit and then checks the durable outcome if its acknowledgement is lost. A confirmed commit returns the upload capability, and a confirmed abort returns the same retryable error. If the outcome cannot be confirmed, the Task returns `COMMIT_OUTCOME_UNKNOWN`; do not retry that preparation, and contact support with the returned trace. Every error response withholds signed upload details. JPEG, PNG, WAV, and MP3 preparation is unchanged. The upload capability expires after two hours. Finishing the Task verifies the stored object's declared type and exact size, pins its immutable storage generation, and waits for asynchronous digest and media inspection. When the Task is saving an advertiser-owned Library asset, you can add up to 50 optional Library tags, each no more than 64 characters. After successful finalization, Task activity on a first-party host gives the assistant the durable asset ID, saved name, tags, content type, byte size, SHA-256 digest, and available verified media facts. Video facts include container, codecs, dimensions, and duration. Audio facts include container, codec, bitrate, channel count, sample rate, and duration. On Claude and ChatGPT, the follow-up gives the assistant only the formatted size, verified media kind, advertiser name when present, every Library asset ID, and Creative Library link. The uploaded file name, tags, SHA-256 digest, verified media facts, and authorisation guard stay in the Task disclosure. Signed upload URLs, private storage paths, publication URLs, video frame rate, and provider identity remain excluded. After an advertiser-scoped Library upload succeeds, the confirmation card shows the asset preview, its name, media kind, byte size, and the Library tags that were saved. For MP4 files, inspection chooses a representative still from the middle of the video. It avoids black, flat, and blurry samples where possible, and uses a lower-confidence fallback when every sample is unsuitable. The Library, receipt, and preview show that still after inspection completes; until then, they show the video icon or play glyph. For a video Creative, the image in the thumbnail slot is used wherever the Creative shows a still; otherwise it uses the inspected poster frame. The upload receipt shows the poster once it is ready. Sellers receive the poster only where their destination has a thumbnail slot. Today that is Meta, which receives the poster on every send of the video, whether it goes out with a campaign send, with a buy, or in a re-send after a change. A video with no ready poster is sent without one. After the upload finishes, the Task sends one plain sentence with a link to the Creative Library; the raw upload receipt remains behind an **untrusted receipt** disclosure. Select **See it in your Library** to open the Assets lens on that exact asset, or **Upload another** to start again. Hosts that cannot render the card receive the same plain confirmation and an authenticated Apostra chat link to the Library. The assistant can find promoted audio with `search(kind: "creative_asset")` by passing the advertiser ID in `filter.advertiserId`. To read one with `get`, pass the asset ID as `id` and the same advertiser ID as `sourceId`. Every read rechecks the customer, principal, and advertiser; the asset ID grants no authority. You can save an advertiser-scoped draft with `formatKind: "audio_hosted"`, its URL-free canonical `formatParams`, and the uploaded asset in `sourceAssets`. Finalization does not create or attach a Creative. Product fit is checked later when you attach the draft or use it in a media buy. Once a complete audio Creative is attached to a campaign, it is added to every media buy on that campaign whose product accepts its format, like any other Creative. Apostra sends each seller a link to the audio, never the file itself; the seller fetches it once when it ingests the Creative and does not serve ads from that link. Uploaded audio and video can be delivered for 90 days after source approval. After that, or once its delivery is revoked, Apostra holds the Creative back from sellers and says why; upload the file again and attach the new Creative. Existing provider-managed audio adoption is delivered the same way. Rejected or abandoned quarantine objects never become public delivery assets. > [!WARNING] > > Upload finalization is not campaign association or destination delivery. > Assemble a promoted image, MP4, or inspected audio asset through > `save_creative`. Before choosing a product, name its URL-free canonical > `formatKind` and `formatParams`; do not substitute a legacy format reference. Managed MP4 upload is generally available to authenticated Buyer Accounts where its upload and inspection services are configured. WAV and MP3 upload is available to every Buyer Account through **Upload** under **More** in the Library or the assistant's upload Task. If the upload Task doesn't offer WAV or MP3, audio upload is temporarily unavailable in that environment. JPEG and PNG eligibility remains independent. ## Add creative in the Creative Library Open the advertiser or campaign **Creative Library** and select **Add creative**. The upload flow opens inside the Library. For images and MP4 video, it opens the appropriate creator with the file in its first slot. Audio assets are browse-only in the Library's own creator, so build an audio Creative through the assistant or `save_creative`. Use **More** for **Upload** when you only want to add a reusable file, including WAV or MP3, or **New creative** when you want to start from a file already in the Library. You can also upload WAV or MP3 by asking the assistant. An authorized buyer can also assemble an advertiser-owned Creative before a campaign exists. A finished upload keeps its original filename as its display name. To save a draft, either name a canonical format such as image, hosted video, or hosted audio, or pick a product to use its seller format. The **Assets** tab contains reusable files uploaded directly to the Library. Files inside existing Creatives are not listed there. The **Catalogs** tab lists connected catalog feeds; it is separate from Creative collections. Files already inside existing Creatives stay with those Creatives and do not appear in **Assets**. A file you upload stays private to you until you use it in a Creative. In the Library's **New creative** flow, select an uploaded file first. The Product picker defaults to **Compatible with this asset**, with **All products** and search by product or seller available when you need to compare options. Each row names the seller and canonical format, and lists the hard requirements that matter for the file: media type, hosted-file or VAST/VPAID delivery, file type, dimensions, duration, container or codec, and file-size limit where the seller declares one. A row explains why the selected file fits or does not fit. Selecting a product collapses the list and moves focus to its Format and slots step; choose **Change product** to return to the list. If no product accepts the file, choose **Upload another asset** to return to the Library uploader, or **View all products** to inspect every available product and its constraints. The Library reads each product's buyer-visible canonical format options and verifies the selected option again before showing its slots. Select a compatible asset and place it with **Place selected** or drag it into the slot. **Done** makes one advertiser-scoped `save_creative` call and shows the authoritative Creative readback. You can select **Show preview** there to load the exact saved revision, then return to **All creatives** to find the Creative once. It does not attach the Creative to a campaign or contact a provider. Name the format in one of two ways. Before choosing a product, select the canonical format family and its parameters, such as `image` with a size or `video_hosted` with a duration. If you have already chosen a product, enter its exact opaque Product ID, including the `sf2:` prefix, and the Library resolves that product's buyer-visible format option and slots. Select a compatible asset and place it with **Place selected** or drag it into the slot. **Done** makes one advertiser-scoped `save_creative` call and shows the authoritative Creative readback. It does not attach the Creative to a campaign or contact a provider. When you later attach the Creative or use it in a media buy, Apostra compares its canonical format parameters with the selected product and refuses it when no product format fits. The Library checks file type and size from the durable asset record. For a promoted MP4, it also checks the verified container, video and audio codecs, exact or ranged duration, width, and height before enabling placement. A mismatch or missing inspection fact is rejected before save. Formats that require text, audio, HTML, nested groups, aspect ratio, or another unsupported property remain visible with the reason they cannot be saved there. Use the agent workflow for those formats and supply every required canonical slot explicitly. The Library never falls back to the legacy global format catalogue. ## Compose a campaign draft Choose **Compose** in a campaign's Creative Library to open the Creative composer Task. Pick uploaded assets, fill the format's slots, set the draft name and click-through URL, then select **Save creative**. The preview updates while you work, and the save keeps the draft in that campaign's Creative Session. This assembly flow is available whether or not Creative Engines is enabled. Ask for copy or other changes in the host chat. The composer has no separate change bar, so it reloads the session after chat updates it. A seller's rejection or requested change can open the composer with that note pinned. Saving this draft does not create delivery, attach it to a campaign, or make it live. Use the existing **Attach to campaign** picker from the saved Creative's **Used in** section when the draft is ready. Agents on the shared V3 connector can open the same Task with `open_creative_library` using `lens: "composer"` and the campaign ID. A composer launch always requires `campaignId`; add `sessionId` only to resume a specific editable Creative Session. This lens is buyer-only; seller accounts do not receive it. Without a session ID, Apostra creates a durable assembly draft with the campaign's single supported composer format. For an entitled Buyer Account with a connected engine, the draft records that connection; otherwise it has no engine binding. It does not call a Creative Engine. If the campaign has no single supported composer format, the draft starts as a Snap Story and the Task says to change the format in chat. Hosts that do not render MCP apps receive an Apostra chat link with `creativeAction=compose`, the campaign ID, and the session ID when one was selected. The composer leaves asset assembly and save controls available when generation is unavailable. If Creative Engines is off, it tells you to ask your Apostra account team to enable it for the Buyer Account. If Creative Engines is enabled but this draft has no connected engine, it tells you that generation needs a connected Creative Engine on the draft. Generation also needs any applicable provider setup described in [Creative Engines](/v2/setup/v3/creative-engines). ## Turning an uploaded image into a creative Pass the finalized JPEG or PNG `scope3-asset://` reference verbatim as `sourceAssetRef` to `save_creative`, plus the matching `advertiserId` or `campaignId`. Campaign-scoped adoption derives the owning advertiser from the campaign. For an eligible reference: ```json theme={null} { "campaignId": "cmp_123", "name": "Loetje image hero", "sourceAssetRef": "scope3-asset://v1/123e4567-e89b-42d3-a456-426614174000" } ``` Use either `campaignId` or `advertiserId`, not both. The upload must be finalized and owned by the same customer, authenticated principal, and advertiser. The server copies the reverified JPEG or PNG bytes into the governed Creative asset store and persists only that durable asset plus a one-way source fingerprint. It never stores the opaque source reference, private object path, or upload/delivery signed URL on the Creative. For a campaign, creation happens in the advertiser library first and the existing campaign-membership operation runs second. Retrying the same source and name returns the Creative already created and completes a missing campaign attachment, without replaying campaign-scoped create side effects. The private upload can then expire without emptying the Creative. ## Google Ad Manager image destinations Before launching a campaign that routes an image creative to Google Ad Manager, check whether the route requires a click-through destination. When it does, call `save_creative` with the creative's `landing_page_url` before launch. `save_campaign` refuses the launch if that required destination is missing. Save the creative with a valid destination URL, then launch the campaign again. The requirement applies only to routes that require a destination URL. A media-buy creative readback includes `clickUrl`, so an agent can confirm the saved destination before it launches. ## Turning an uploaded media asset into a creative Call `save_creative` with either the advertiser or a campaign and one `sourceAssets` binding for every required slot. There are two ways to name the format: use a signed, product-bound `creativeFormatId` when a product has already been selected, or, for an advertiser-scoped Creative before choosing a product, use the URL-free canonical `formatKind` and optional `formatParams`. The second path has no product binding. When you later attach the Creative or use it in a media buy, Apostra checks the canonical format kind and parameters against each product. The result is per product: the Creative either fits, fits with named adaptations (`resize`, `letterbox`, `trim`, or `transcode`), cannot be confirmed because facts are missing, or cannot fit. A Creative without the required size, duration, codec, or container in `formatParams` needs an inspected uploaded asset that establishes that fact before Apostra can confirm its fit. A failed fit names the closest declared option and the difference, such as a 3-second audio file against a 15–30-second requirement. An attachment declares the required adaptations but does not perform them. Supply required copy and destination slots through `social` and `landing_page_url`. For a composition that uses `sourceAssets`, ordinary `assets` are unbound and cannot satisfy the selected format's required slots. ## Format parameters For an advertiser-scoped Creative that names `formatKind` directly, pass only the URL-free `formatParams` keys listed for that kind. An unknown key is rejected. The list comes from the same format constraints that validate the save. * `image`: `width`, `height`, `sizes`, `min_width`, `max_width`, `min_height`, `max_height`, `aspect_ratio`, `max_file_size_kb`, `image_formats`, `ssl_required` * `html5`: `width`, `height`, `sizes`, `min_width`, `max_width`, `min_height`, `max_height`, `max_initial_load_kb`, `max_polite_load_kb`, `host_initiated_subload`, `max_animation_duration_ms`, `max_cpu_load_percent`, `mraid_required`, `mraid_version`, `om_sdk_required`, `clicktag_macro`, `backup_image_required`, `backup_image_max_size_kb`, `ssl_required` * `display_tag`: `width`, `height`, `sizes`, `min_width`, `max_width`, `min_height`, `max_height`, `supported_tag_types`, `max_redirect_depth`, `backup_image_required`, `om_sdk_required`, `ssl_required` * `image_carousel`: `min_cards`, `max_cards`, `card_aspect_ratio`, `allowed_card_media_asset_types`, `card_image_max_file_size_kb`, `card_video_max_duration_ms`, `ssl_required` * `video_hosted`: `duration_ms_exact`, `duration_ms_range`, `aspect_ratio`, `orientation`, `min_width`, `max_width`, `min_height`, `max_height`, `video_codecs`, `audio_codecs`, `containers`, `max_bitrate_kbps`, `max_file_size_mb`, `captions`, `om_sdk_required` * `video_vast`: `duration_ms_exact`, `duration_ms_range`, `aspect_ratio`, `orientation`, `vast_version`, `vpaid_enabled`, `vpaid_version`, `simid_supported`, `linear_required`, `skippable_after_ms`, `max_wrapper_depth`, `ssl_required` * `audio_hosted`: `duration_ms_exact`, `duration_ms_range`, `audio_codecs`, `audio_sample_rates`, `audio_channels`, `max_bitrate_kbps`, `loudness_lufs`, `companion_image_required` * `audio_vast`: `vast_versions`, `vast_version`, `media_file_requirements`, `duration_ms_exact`, `duration_ms_range`, `skippable_after_ms`, `max_wrapper_depth`, `ssl_required`, `companion_image_required` * `audio_daast`: `duration_ms_exact`, `duration_ms_range`, `daast_version`, `linear_required`, `max_wrapper_depth`, `companion_image_required`, `ssl_required` * `sponsored_placement`: `supported_catalog_types`, `min_items`, `max_items`, `fanout_mode`, `required_catalog_fields`, `supported_id_types`, `hero_asset_supported` * `native_in_feed`: `title_max_chars`, `body_text_max_chars`, `cta_max_chars`, `cta_values`, `main_image_sizes`, `icon_size`, `max_image_file_size_kb`, `image_formats`, `ssl_required` * `responsive_creative`: `headlines_min`, `headlines_max`, `headline_max_chars`, `long_headlines_min`, `long_headlines_max`, `descriptions_min`, `descriptions_max` * `agent_placement`: `platform_extensions` * `coordinated_placements`: `platform_extensions` * `seller_rendered_stateful_display`: `platform_extensions` * `custom`: `format_shape` For a repeatable group, bind every member with the same zero-based instance: `cards[0].media`, `cards[0].headline`, then `cards[1].media`, `cards[1].headline`. Put uploaded image, video, or audio members in `sourceAssets[].slot` and text or URL members in `social.components[].slot`. Every required member must be present for each instance, and indices must be contiguous from zero. Meta-style hosted video with a required thumbnail: ```json theme={null} { "advertiserId": "900", "name": "Loetje video", "formatKind": "video_hosted", "formatOptionRef": { "scope": "product", "format_option_id": "meta-feed-video" }, "sourceAssets": [ { "assetRef": "scope3-video-source://v1/123e4567-e89b-42d3-a456-426614174000", "slot": "video", "makePrimary": true }, { "assetRef": "scope3-asset://v1/223e4567-e89b-42d3-a456-426614174000", "slot": "thumbnail" } ] } ``` Use an exact `formatOptionRef` from the buyer-visible product catalogue when a product is already selected. Otherwise name the canonical `formatKind` and its parameters directly; this does not invent a product or a legacy format reference. The MP4 must be promoted, every source must belong to the same customer, authenticated principal, and advertiser, and every required slot must be present. With `advertiserId`, the save writes an advertiser-owned Creative without attaching it. To attach that existing Creative later, open it in the Creative Library from the advertiser view. Its **Used in** section lists the advertiser's draft, active, and paused campaigns and marks the ones that can take it. A campaign can take the Creative when at least one of its sellers accepts the Creative's format. Campaigns whose flight has ended, campaigns with no sellers yet, and campaigns whose sellers could not be checked are listed but cannot be selected. In chat, asking to put a Library creative on a campaign sets the same membership through `save_creative` with `campaignIds`, or through `save_campaign` with `creativeIds`; the V2 `attach_creatives_to_campaign` operation performs the same attachment outside the Library. With `campaignId`, `save_creative` additionally requires the option on that campaign's products and attaches the Creative. Once attached, a Creative built from a Library MP4 is added to every media buy on that campaign whose product accepts its format, like any other Creative. Sellers receive a link to the MP4, never the file itself, and fetch it once when they ingest the Creative. The same uploaded source can be used in multiple named Creatives while it is retained. Retrying the same name and exact composition returns the existing Creative. Images are copied into governed Creative storage. Hosted video can be delivered for 90 days after source approval; after that, or once its delivery is revoked, the Creative is held back from sellers with a reason to upload the file again. Private source references, private storage URLs, and signed upload capabilities are never returned as provider input. ## Legacy provider-bound video adoption An MP4 finalized by the shared V3 buyer Task is owner-bound and must use the V3 composition path above. The legacy V2 operation below is not a fallback for it. A direct Storefront connection offers the legacy provider-bound upload Task only when the host's upload origin is one Apostra has registered for private storage. ChatGPT's Storefront app uses one fixed, registered origin, so it gets the Task. Claude derives a separate origin from each Storefront's connector URL, so a Claude direct Storefront connection usually does not get the Task. If the Task isn't offered, upload through the Buyer V2 or shared V3 connector instead, as described in [Uploading to the Creative Library](#uploading-to-the-creative-library). For a video uploaded through the legacy provider-bound Task, finishing returns the `source_uid` for the source that passed inspection. That same provider scope can adopt it without assembling a creative around a hosted URL by hand: ```http theme={null} POST /api/v2/buyer/creatives/adopt-managed-video-source ``` ```json theme={null} { "source_uid": "0f8b7a2e-6cf4-4db4-9a7f-09db4430ae91", "advertiser_id": "900", "storefront_id": "7", "name": "Loetje hero cut", "campaign_id": "cmp_123" } ``` `campaign_id` is optional. Include it and the creative is attached to that campaign in the same call; omit it to keep the creative advertiser-scoped. The campaign and source must match the same advertiser and provider ownership. Attaching also pushes the campaign's live media buys to their sellers. That push is best-effort and is not part of what the response confirms: a success means the creative is attached to the campaign, not that every seller has accepted it into its packages. Check the media buys themselves to see where the creative is live. The V2 response returns `creative_id`, `source_uid`, `delivery_url`, `publication_uid`, `name`, and `created`. A success that named a `campaign_id` means the creative is attached to that campaign whether or not this particular call attached it, so a retry safely finishes an interrupted attachment. **You name the source, not a URL.** The hosted HTTPS URL is issued server-side and resolves the exact file that passed inspection, so the private storage location is never something you supply or can substitute. **Adopting the same source twice returns the creative that already exists**, with `created: false`, rather than making a second one. If you include a `campaign_id` the second time, the existing creative is attached to that campaign — so a call that was interrupted after the creative was made can be retried safely. Retry with the same `name`: adoption never renames an existing creative, so asking for a different one is refused rather than silently returning a creative that does not have the name you asked for. Names compete among an advertiser's creatives that are not currently scoped to a campaign — which is how adoption always creates them. If one of those already holds the name, adoption is refused rather than silently reusing it. A creative created directly under a campaign is outside that set while it stays there, but detaching it moves it in: detaching does not free a name, it claims one. Attaching an adopted creative to a campaign afterwards does not release its name either: archiving it does, and so does renaming it with a separate `update_creative` call. If the creative holding the name is already attached to a campaign, rename it instead — renaming frees the name without disrupting a creative that's live in a media buy. `update_creative` is campaign-scoped, so a creative attached to no campaign can only be archived. Note that archiving is the advertiser-scoped delete (`archive_creative`); the campaign-scoped `delete_creative` only detaches the creative from that campaign and does not release its name. Duplicating an adopted creative with `duplicate_creative` copies the video, not the adoption: the copy is not the adoption of record for that source, so it neither blocks nor lets you re-adopt the source through it. A `CONFLICT` on `name` does not always mean "pick another name". Each one carries a `details.reason`: `name_creation_in_progress` means another call is still writing that exact name, and retrying the identical request is the right move; `name_creation_abandoned` means that writer died mid-way, so archive the incomplete creative first; `name_taken` means the name belongs to a finished creative and you need a different one. The conflicts on `source_uid` work the same way — `adoption_in_progress` is the only one worth retrying. `adoption_name_mismatch` means the source already has a creative under another name; use that name or rename the creative separately. `source_delivery_revoked` and `source_deletion_requested` are terminal for that source: upload a new video before adopting again. Branch on `details.reason` rather than on the message text. Two rules here are enforced by the database, not just checked: a managed video source can back only one live creative, and advertiser-scoped creative names are serialized across both adoption and ordinary creation. Both hold no matter how many callers race, and archiving frees the name. > [!WARNING] > > The `delivery_url` is not permanent. It stays valid while the source is > retained and its delivery has not been revoked. Once the retention window > ends, or once the delivery has been revoked with nothing live replacing it, > re-adopting that source is refused — with a message telling you what > happened, not a name collision. Requesting deletion of the source refuses it > too, by taking the source out of the adoptable state. None of these can be > refreshed in place: upload a new video and adopt that new source, usually > under a new name, since the existing creative still holds its old video until > you archive or rename it. A deletion request also stops a call that was only > trying to finish an interrupted attachment — a source you have asked us to > erase is not attached to anything further. If the source is already held by a creative but adoption can no longer confirm that creative still carries the video — because its asset was replaced or removed directly on that creative — adoption refuses with `CONFLICT` rather than creating a second creative for the same source. Archive the creative holding it and adopt again, or upload the video again and adopt the new source. A second `CONFLICT` also tells you to archive, for a different reason: an earlier adoption of this same source left its creation marker uncleared and has been stuck long enough (about fifteen minutes) that nothing is still writing it. An asset row may already have landed, but the creative never finalized. Its `details.reason` is `adoption_abandoned`; archive that incomplete creative and adopt again. Within these three source-identity conflicts, `adoption_in_progress` is the one you should simply retry. It means another adoption is still finishing or the blocking adoption changed while this request was classifying it; retrying lets the request observe the settled state. The separate `name_creation_in_progress` reason described above is also retryable. Branch on `details.reason`, never on the message; the other reasons require archiving or changing the request first. Legacy adoption produces a creative and may enqueue existing V2 campaign sync behavior; its success response proves neither provider creation nor acceptance. Only exact provider readback confirms destination materialization. For the managed-audio contract, see [Adopt a managed audio source](/v2/buyer/creatives/tasks/adopt-managed-audio-source). Adoption is available wherever direct video delivery is configured — it is part of the generally available managed-video capability, not a separate account opt-in. A request made while the delivery service is unavailable is refused with `CAPABILITY_NOT_SUPPORTED`. ## Uploading HTML5 banner ZIPs Some ad tools export one HTML5 banner as a ZIP containing `index.html` and its scripts, styles, images, fonts, or video. The V2 buyer API keeps a recognized package together as one HTML5 creative. The entry point may be `index.html` at the ZIP root or, for a single-folder export, `banner/index.html`. All creative files must be inside that one folder; multiple entry points or multiple enclosing folders are rejected. Creative file contents and relative asset paths are preserved, and the entry point is recorded for delivery. macOS metadata (`__MACOSX`, `.DS_Store`) and hidden directories such as `.vscode/` are ignored when identifying creatives. Retained files in hidden directories must still use supported file types. `.DS_Store` and AppleDouble resource-fork entries (`._*` under `__MACOSX/`) are removed from the delivered ZIP, so their names cannot exempt retained content from the supported-type check. ZIPs without those metadata entries keep their original bytes. SVG and MP4 files are supported inside the bundle. All input metadata counts toward file-count and decompressed-size limits before removal. The campaign file-upload UI also ignores hidden directories when expanding files; ordinary non-hidden subdirectories remain available. A retained HTML5 bundle may contain at most 100 files. Both its ZIP bytes and its total decompressed contents must fit within 10 MiB, and its entry HTML must fit within 1 MiB. The V2 backend allows at most 200 total entries in each ZIP, including folders. Metadata counts toward these limits even when it is ignored for creative recognition or removed before delivery. The entry HTML must declare exactly one unambiguous size: ```html theme={null} ``` Replace `300` and `250` with the banner's actual positive integer pixel dimensions. Dimensions are not inferred from the ZIP filename, CSS, images, video, or a separately supplied landing URL. Missing, invalid, or conflicting `ad.size` declarations produce that specific requirement. A readable ZIP that fails the creative contract is reported as an import rejection, not as a corrupt or unreadable archive. Traversal paths, duplicate paths, symbolic links, unsupported files, ambiguous entry points, and oversized bundles remain blocked. V2 also recognizes wrapper exports containing one nested HTML5 ZIP, an exact exploded copy, and an explicitly named same-size backup image. The nested package keeps its creative files and relative paths. Its removable macOS metadata is stripped using the same cleanup described above, so its ZIP container bytes may change. In V2 Apostra, an attachment used on a later turn is retrieved from the account's saved attachment pool before inspection. If those bytes cannot be retrieved, the error says the attachment was unavailable and that its archive contents were not inspected. Inline files and saved attachments share the existing 50 MiB total chat upload budget when V2 Apostra retrieves a batch. A batch that cannot fit returns that limit explicitly before creative inspection; use fewer or smaller files in the call. **Claude/Apostra connector availability:** attaching a ZIP to a Claude conversation does not itself upload it to Apostra. The V3 `save_creative` surface and managed-media upload Task do not currently adopt HTML5 ZIPs. Use the existing V2 buyer upload flow for HTML5 ingestion; a connector with only V3 tools cannot perform that upload. An unavailable upload path is not evidence of archive corruption. Support should identify the connector surface and the actual upload result before diagnosing the file. Importing and delivering are separate checks. Before media-buy sync, an HTML5 creative needs an explicit landing-page URL in its click-through URL field. A placeholder clickTag or missing destination is a delivery concern, not an archive integrity failure. Apostra does warn when the ZIP's HTML declares no click tag an ad server can detect, or two different ones, and still saves the ZIP; see [HTML5 and display tag creative requirements](/v3/html5-creative-requirements). A seller option whose HTML5 behavior requirements cannot be proved from inspection is rejected with the blocking requirement named. ### Loose HTML files and tags A single HTML file, or an HTML tag pasted or uploaded on its own, must have a size before Apostra saves it. A creative with no size is refused, because a seller that lists one format per banner size turns it away after you have moved on. The refusal is a validation error that says what is missing and how to fix it, and nothing is saved. An HTML5 or HTML tag creative has a size when any one of these holds: 1. **Its own markup declares one.** Apostra reads these declarations in order, and the first one present decides: 1. one `` tag; 2. a Campaign Manager tag, ``; 3. a Campaign Manager ad-server tag: its single `