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

# Advertiser

> The brand-and-operator identity that owns campaigns, creatives, audiences, and linked partner accounts

## Overview

An **Advertiser** in Apostra is the commercial media identity on whose
behalf campaigns run: a **brand care of an operator**. The brand answers whose
products are being advertised; the operator answers which organization drives
the buying. This is an Apostra product projection, not an assertion that
AdCP collapses the two entities: AdCP keeps Brand, Operator, Account, and Agent
separate.

For buyer-declared AdCP accounts, the natural key combines the BrandKey
(including its country qualifiers), operator domain, optional operator-unit ID,
fixed currency, and sandbox mode.
An upstream-managed `account_id` instead
names an entry in the seller's own namespace, whose declared `account_scope`
may be operator-, brand-, operator-and-brand-, or agent-wide. See
[AdCP Accounts and agents](https://docs.adcontextprotocol.org/docs/building/by-layer/L2/accounts-and-agents).

Direct buying is the identity case where the brand operates its own seat. For
example, Coca-Cola buying directly from an account registered to
`coca-cola.com` is "Coca-Cola care of Coca-Cola"; Coca-Cola through WPP is
"Coca-Cola care of WPP." **Self-serve is an interface, not an operator
identity**: using Apostra directly does not replace Coca-Cola with
"self-serve" as the operator. Likewise, an agent is the software acting for the
operator, not the operator itself. When a buyer account has no registered
domain, current v2 outbound AdCP calls use `interchange.io` as a platform
operator fallback until the account records its real domain; that fallback is
an implementation compromise, not the meaning of self-serve.

In the current Apostra v2 buyer API, an advertiser is also the top-level resource
container for media. Every campaign, creative, audience, frequency cap, and
linked partner account belongs to one. The authenticated buyer account
establishes the operator context, so the v2 advertiser payload does not repeat
an `operator` field.

## Media Company-managed advertisers

When a Media Company adds an advertiser for a campaign it will operate on its
own inventory, the Media Company remains the operator and the advertiser is the
brand it represents. Apostra binds that advertiser to the organization's
exact Storefront; the operator does not create a seller connection or switch
into a separate Buyer account.

The operator can select known authorized Products directly or ask for a
proposal when it wants merchandising recommendations. For an exact own-supply
campaign, uploading Creative records the operator's Storefront creative
decision, and confirming launch records its Storefront media-buy decision.
Those decisions do not bypass ad-server creative moderation, validation,
readiness, financial, or safety checks. External and sponsored buyers continue
to follow the Storefront's configured approval settings.

<Note>
  **Renamed from Brand Agent**: In v1 this concept was called a "Brand Agent".
  In v2 it has been renamed to **Advertiser** to align with industry terminology
  used by Google Ads, The Trade Desk, DV360, and Amazon DSP.
</Note>

## Why advertisers matter

* **Resource ownership** — campaigns, creatives, audiences, and frequency caps all roll up to a single advertiser
* **Commercial identity** — every advertiser combines a brand reference with the operator acting for it
* **Brand identity** — every advertiser links to a brand resolved from the AdCP brand registry (or a `/.well-known/brand.json` document on the brand's domain)
* **Default policies** — `optimizationApplyMode`, UTM parameters, and frequency caps set on the advertiser flow down to its campaigns
* **Sandbox isolation** — sandbox advertisers route every ADCP call to test accounts, so you can rehearse end-to-end flows without real spend
* **Linked accounts** — connect the advertiser to existing accounts on upstream platforms (e.g. Snap, programmatic DSPs) for cross-platform reporting and execution
* **Channel preferences** — record the AdCP media channels the advertiser expects to use without treating those preferences as seller qualification
* **Reporting time-zone preference** — record the buyer's desired reporting boundary while allowing sellers to use a fixed or assigned effective time zone

## Key fields

`labels` is the Buyer Account's optional dimension-keyed classification for the
advertiser. It is managed through the V3 MCP `save_advertiser` tool and read
through V3 `get` and `search`; see [Dimensions and labels](/v2/object-guides/dimension).

The advertiser resource has two response shapes:

* **Summary** — returned by `GET /api/v2/buyer/advertisers` (list). A small, fixed set of row-level fields.
* **Full** — returned by `GET /api/v2/buyer/advertisers/:id` (get). The complete resource with nested brand, linked accounts, UTM config, and frequency caps.

### Organization roster projection

`GET /api/v2/organization/advertisers` returns one bounded advertiser roster
over the compatibility accounts the authenticated organization context may
read. It does not copy advertisers or campaigns: every entry points back to the
existing `advertiserId` and compatibility-account boundary.

Owned entries use `relationshipClass: "owned"`. A plain advertiser row is
`operatingMode: "managed"`; an advertiser backed by the existing confined
seller-buyer sponsorship is `operatingMode: "self_serve"` and
`operatorRelationship: "client"`.

An accepted, unexpired organization advertiser grant emits only its named
advertiser IDs as `relationshipClass: "delegated"`. The owner keeps the
advertiser, Campaign IDs, and backing account. The grantee receives the grant
reference, owner organization, reviewed capabilities, and expiry, but never
the owner's account ID, members, billing, unrelated advertisers, future
advertisers, or private configuration. See
[Manage organization advertiser grants](/v2/buyer/advertisers/tasks/manage-organization-advertiser-grants).
This limited-beta projection also requires an active beta entitlement and
server-side beta exposure enabled by Apostra. Each delegated roster read
rechecks an accepted, unexpired grant for the exact advertiser and
`advertiser.read` capability.

Counterparty AdCP relationship
reconciliation is dark prerequisite infrastructure: no customer or internal
account is enrolled, and the public API does not make counterparty entries or
repair available from this slice. Later activation work must separately enable
both the fail-closed customer flag and runtime entitlement before any
counterparty row can appear.

Each resolved row includes binding provenance and resolution state,
caller-relative management capability, and readiness. Pending, suspended, and
disabled advertiser rows remain visible with non-ready state instead of being
silently treated as active. Resolved rows deduplicate only by canonical
advertiser ID; matching names or brand domains never merge advertisers or widen
access.
When later activation authorizes counterparty entries, exact duplicate tuples
including the explicit operating mode deduplicate deterministically in the
resolved entries collection, while relationships that differ only by operating
mode remain distinct. Incomplete, stale, revoked, or conflicting relationships
remain separately visible in the additive unresolved counterparty collection
until a direct-human Seller-admin performs the explicit entitlement-gated
repair.

The endpoint uses authenticated account membership and active explicit grants.
It never expands access from a name, email domain, CRM association, signup
history, provider, external Business Manager, or Partner status.
Advertiser-scoped credentials cannot read the organization roster.

### Seller-managed advertiser viewer access

When Apostra enrolls a seller-managed advertiser user, the user's current
seller, advertiser, role, and capability grant are checked again on every
request. A user with the `read` capability can list Campaign summaries through
`GET /api/v2/buyer/campaigns` only for that exact advertiser. The
`advertiserId` query parameter is optional, but when supplied it must match the
advertiser already selected at sign-in; it cannot switch advertisers or widen
access.

This access does not create a buyer account or customer membership. It does
not grant Campaign detail, Campaign changes or execution, reporting, other
advertiser routes, organization or member settings, or billing and payment
access. Missing, stale, suspended, or revoked grants are denied before Campaign
data is read. Users who need another advertiser must sign in with a separate
explicit grant for that advertiser.

### Summary shape (list rows)

| Field                     | Type                   | Notes                                                                                         |
| ------------------------- | ---------------------- | --------------------------------------------------------------------------------------------- |
| `id`                      | string                 | Stable advertiser identifier                                                                  |
| `name`                    | string                 | Human-readable name (max 255 chars)                                                           |
| `status`                  | `ACTIVE` \| `ARCHIVED` | Soft-delete state                                                                             |
| `sandbox`                 | boolean                | When true, all ADCP calls route to sandbox accounts. **Immutable after creation.**            |
| `brand`                   | string                 | Brand domain (e.g. `northwind.example`) or brand website URL                                  |
| `brandCountries`          | array                  | ISO 3166-1 alpha-2 qualifiers for this advertiser's BrandKey market scope                     |
| `primaryCurrency`         | string                 | ISO 4217 currency used by campaigns under this advertiser                                     |
| `preferredTimezone`       | string \| null         | Preferred IANA reporting time zone                                                            |
| `currencyLocked`          | boolean                | Whether currency can still change through the raw v2 API                                      |
| `reportingTimezoneLocked` | boolean                | Whether the reporting time-zone preference can still change                                   |
| `channels`                | array                  | Editable AdCP media-channel planning preferences; not seller qualification                    |
| `linkedAccountCount`      | integer                | Number of partner accounts linked to this advertiser. Use `get_advertiser` for the full list. |
| `createdAt` / `updatedAt` | datetime               | ISO 8601 timestamps                                                                           |

### Full shape (single advertiser)

The single-advertiser endpoint returns every field above plus:

| Field                   | Type               | Notes                                                                                                                                                   |
| ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `description`           | string             | Optional description (max 1000 chars)                                                                                                                   |
| `linkedBrand`           | object             | Resolved brand identity — name, domain, logos, colors, fonts, manifest                                                                                  |
| `optimizationApplyMode` | `AUTO` \| `MANUAL` | Default for the advertiser's campaigns. `MANUAL` requires approval before applying optimizations.                                                       |
| `brandCountriesKnown`   | boolean            | `false` only for a legacy advertiser whose country scope is still unknown. When false, an empty `brandCountries` array is not a confirmed global scope. |
| `linkedAccounts`        | array              | Partner accounts linked to this advertiser (e.g. Snap account, agency seats)                                                                            |
| `utmConfig`             | array              | Click-URL parameters appended to landing-page URLs at delivery. The v2 name for V3 `tracking.macroAdditions`; a new advertiser has none                 |
| `frequencyCaps`         | array              | Buyer-side caps enforced by Apostra across all publishers                                                                                               |
| `brandWarning`          | string             | Warning about brand resolution (e.g. manifest not found)                                                                                                |

## Brand resolution

Advertisers do not store brand identity directly. Instead, they reference a **brand domain or website URL**, and Apostra resolves the brand identity from one of three sources:

1. The advertiser's `/.well-known/brand.json` (per the AdCP spec)
2. The AdCP brand registry
3. Brandfetch enrichment

Pass `brand: "northwind.example"` and Apostra fetches the resolved brand identity, surfacing logos, colors, fonts, tone, and product catalog under `linkedBrand` when available. If the advertiser is created or updated from enrichment rather than an official registry entry, the response includes `brandWarning`; do not retry create for that case. After reviewing the enriched details, set `saveBrand: true` on an update if you want to persist the brand in the registry. If no enrichment data is available, retry create with `saveBrand: true` only after confirming the advertiser name and brand domain.

<Tip>
  See the [Brand object guide](/v2/object-guides/brand-story) for the brand
  resolution model in detail.
</Tip>

## Uniqueness

The AdCP-aligned advertiser identity is BrandKey (domain, optional brand ID,
and sorted country qualifiers) × operator domain × optional operator-unit ID ×
fixed currency × sandbox mode. The same brand domain can therefore back more
than one advertiser resource when its brand ID, countries, or currency differ.
Those rows are separate campaign and billing containers, not different operator
identities. The current AdCP 3.2 BrandRef sent to sellers contains only domain
and optional brand ID; Apostra does not add countries to that strict wire
object.

The buyer operator domain and unit are confirmed in Buyer Setup and remain
editable until the first advertiser is created. Advertiser creation is the
first-use boundary and freezes that account-level operator identity. Later
changes require identity migration rather than an inline edit on the advertiser.

Within a buyer account and sandbox mode, creating a second resource with the
same brand domain, optional brand ID, BrandKey countries, and currency returns
a conflict. Sandbox and non-sandbox advertisers are tracked separately.

## Sub-resources

The advertiser is the hub for many sub-resource collections. Each lives under `/api/v2/buyer/advertisers/:advertiserId/...` and inherits the advertiser's auth, brand, and sandbox context.

| Collection               | Endpoint root                                                      | Purpose                                                                                                                                      |
| ------------------------ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| **Campaigns**            | `/api/v2/buyer/campaigns?advertiserId=…`                           | Marketing initiatives — flight dates, budget, products, creatives ([guide](/v2/object-guides/campaign))                                      |
| **Linked accounts**      | `/advertisers/:id/accounts`                                        | Existing accounts on upstream platforms (Snap, programmatic DSPs, agency seats) — see [below](#linked-accounts)                              |
| **Catalogs**             | `/advertisers/:id/catalogs`                                        | Product / offering / inventory feeds pushed via ADCP `sync_catalogs` and reused by discovery and creative generation                         |
| **Audiences**            | `/advertisers/:id/audiences`                                       | First-party audiences synced via ADCP for targeting and incrementality test cohorts                                                          |
| **Property lists**       | `/advertisers/:id/property-lists`                                  | Domain/email allow-and-block lists (up to 100k entries) ([guide](/v2/guides/property-lists))                                                 |
| **Event sources**        | `/advertisers/:id/event-sources`                                   | Conversion event source registration ([measurement guide](/v2/guides/measurement-incrementality))                                            |
| **Measurement config**   | `/advertisers/:id/measurement-config`                              | MMM, incrementality, and brand-lift configuration ([measurement guide](/v2/guides/measurement-incrementality))                               |
| **Test cohorts / plans** | `/advertisers/:id/test-cohorts`, `/advertisers/:id/test-plans/:id` | Incrementality test scaffolding ([measurement guide](/v2/guides/measurement-incrementality))                                                 |
| **Hypotheses**           | `/advertisers/:id/hypotheses`                                      | A/B test hypotheses                                                                                                                          |
| **Belief state**         | `/advertisers/:id/belief-state`                                    | Bayesian belief state from the measurement engine ([measurement guide](/v2/guides/measurement-incrementality))                               |
| **Tracking config**      | `/advertisers/:id/tracking-config`                                 | Tracking macro and pixel template configuration                                                                                              |
| **UTM config**           | inline on the advertiser                                           | Click-URL parameters appended to landing-page URLs (set via `utmConfig` on create/update; see [Click-URL parameters](#click-url-parameters)) |
| **Frequency caps**       | inline on the advertiser                                           | Buyer-side caps that flow down to all of the advertiser's campaigns ([guide](/v2/guides/frequency-caps))                                     |
| **Allocations**          | `/advertisers/:id/allocations`                                     | Budget allocation entries used by the optimizer                                                                                              |
| **Event summary**        | `/advertisers/:id/events/summary`                                  | Aggregated conversion event metrics                                                                                                          |
| **Syndication**          | `/advertisers/:id/syndicate`                                       | Syndicate first-party audiences to partner platforms                                                                                         |
| **Log event**            | `/advertisers/:id/log-event`                                       | Custom event ingestion ([guide](/v2/guides/log-events))                                                                                      |

See the [Buyer API Reference](/v2/buyer-api-reference) for the full request and response shape of each.

## Tracker defaults and campaign inheritance

Third-party impression, click, and custom-event trackers are resources with stable
`tracker_id` values. Configure advertiser defaults at
`/advertisers/:advertiserId/tracking-config`; every campaign under that
advertiser resolves those defaults when the tracking-config resource is read.
Campaigns do not copy the advertiser array, so an advertiser update appears in
the next resolved API response immediately.

Configure campaign additions and inherited enable/disable decisions at
`/campaigns/:campaignId/tracking-config`. The campaign stores only its local
`custom_tracker_urls` and `tracker_overrides`. Its response also returns the
fully resolved `effective_trackers`, including `source_scope`, `inherited`,
`effective_enabled`, and `status_reason`, plus separate advertiser and campaign
revisions. An override whose advertiser tracker was removed is retained and
reported in `orphaned_override_ids` instead of silently targeting a different
tracker.

Each imported tracker retains both forms of its URL:

* `raw_url` is the exact vendor tag supplied by the buyer.
* `url` is the canonical form using [AdCP universal macros](https://docs.adcontextprotocol.org/docs/creative/universal-macros).
* `mapping_artifact` records the translation and documentation provenance.
* `source_dialect` and `compiler_version` make the transformation reproducible.

Strict V3 writes and publishable tracker compilation limit URLs to 8,192
characters and 64 macro tokens. The V2 compatibility surface may retain a
longer legacy URL exactly as non-publishable audit data so existing REST
read-to-write flows do not break; it cannot be published through V3 until it is
shortened. A publishable `CUSTOM` tracker must include `custom_event_name`;
strict V3 writes enforce that requirement. The V2 field remains optional so the
original REST request shape can round-trip, but an unnamed custom event is
recorded as non-publishable in its mapping artifact. The compiler bounds apply
before translation so malformed tags cannot amplify their mapping artifacts.

Strict V3 macro translation fails closed when a token is unknown or ambiguous.
The V2 REST compatibility surface continues to accept its original safe-URL
shape; an unresolved token is retained exactly and its mapping artifact is
marked non-publishable rather than guessed. For example, HappyDemics documents
`{{USER_ID}}` as the mobile advertising device
identifier, so a tracker explicitly identified as HappyDemics maps that token
to `{DEVICE_ID}`. The same token without vendor documentation is not guessed.
Vendor-specific consent tokens such as `${GDPR_CONSENT_4594}` canonicalize to
`{GDPR_CONSENT}`. Follow-up delivery-adapter work will expand the canonical
value into the consent syntax required by its upstream platform.

Automatic translations come from a versioned macro registry keyed by source
dialect and, where available, an exact documented tracker origin. A registry
entry records the vendor, source token, canonical AdCP meaning, official
documentation, date checked, confidence, and runtime constraints. Only
verified entries are eligible for automatic translation. Unknown dialects and
generic tokens such as `{{USER_ID}}` remain unresolved until the buyer identifies
the vendor or confirms whether the value is a device advertising ID, browser
identifier, platform account ID, or something else. The mapping artifact names
the registry entry and version used so a later preview or audit can reproduce
the decision.

Use the V3 `test_creative_macros` read-only tool to inspect a tracker before it
is saved or rendered. It runs the same canonical compiler used by tracking
configuration, then uses the AdCP SDK to produce recipient syntax and an
RFC-3986-encoded synthetic substitution. Reports keep raw, canonical,
recipient, and substituted forms separate and fail closed on unresolved
required macros. The built-in scenarios cover present and unavailable device
IDs, GDPR with consent, non-GDPR traffic, and a missing required value. Supplied
test bindings are labeled `provided`; built-in values are labeled `synthetic`
or `privacy_suppressed`, and no real advertising identifier should be passed to
the tool.

<Warning>
  This configuration API does not yet attach effective trackers to creative
  delivery. Headless macro testing is available, but rendered preview and
  outbound attachment are separate follow-up work; until those land, this
  endpoint is a validated configuration and resolution surface only.
</Warning>

<Note>
  A campaign override changes whether an inherited tracker fires; it does not
  mutate the advertiser tracker. Return the original `tracker_id` when updating
  a tracker so its campaign overrides and audit history remain attached. Send
  the last-read `revision` as `expected_revision` for advertiser updates, or
  `campaign_revision` as `expected_revision` for campaign updates, to reject a
  concurrent write instead of overwriting it.
</Note>

On the V3 MCP surface, this model is projected as the `tracking` field on the
existing advertiser and campaign nouns rather than as a separate tracker noun.
Use `save_advertiser({ advertiserId, tracking })` for advertiser defaults,
`save_campaign({ campaignId, tracking })` for campaign-local trackers and
inherited overrides, and `get(..., include: ["tracking"])` to read the result.
V3 uses camel-case names such as `trackerId`, `rawUrl`, `canonicalUrl`, and
`effectiveTrackers`; custom trackers use `customEventName`. Stored free-form
values returned to a model—including tracker labels, raw and canonical URLs,
custom event names, custom-macro descriptions, and mapping-artifact strings—are
bounded and fenced as untrusted data. The V2 REST compatibility surface retains its snake-case
field names and its prior unbounded description shape. V3 accepts new or
changed custom-macro descriptions up to 240 characters; longer values stored
through V2 are bounded in model output and can be saved unchanged so legacy
configurations remain round-trip safe.

## Click-URL parameters

Click-URL parameters are query parameters Apostra appends to a creative's
click-through URL when the creative is synced to a seller. They are one system
with three layers, and every read and write surface sees the same rows:

| Layer      | Set through                                                                         | Wins over                                 |
| ---------- | ----------------------------------------------------------------------------------- | ----------------------------------------- |
| Advertiser | v2 `utmConfig` on the advertiser, or V3 `save_advertiser` `tracking.macroAdditions` | —                                         |
| Campaign   | v2 `utmConfig` on the campaign, or V3 `save_campaign` `tracking.macroAdditions`     | Advertiser, for the same key              |
| Creative   | V3 `save_creative` `macroAdditions`                                                 | Campaign and advertiser, for the same key |

Rules:

* **No defaults.** A new advertiser has no parameters until you set them.
  Advertisers created before September 2026 carry the six `utm_*` entries
  Apostra used to apply on their behalf (`utm_source=scope3`,
  `utm_medium=agentic`, `utm_campaign={CAMPAIGN_ID}`, `utm_content={CREATIVE_ID}`,
  `utm_media_buy={MEDIA_BUY_ID}`, `utm_package={PACKAGE_ID}`); edit or clear
  them like any other entry.
* **At most 50 entries per layer.** A write that would leave a layer with more
  than 50 keys is rejected; remove keys or replace the set. Advertisers
  migrated in September 2026 were ported in key order up to that cap.
* **Suppression.** Through V3, a `null` value removes an inherited key at that
  layer. The v2 `utmConfig` projection omits suppressed keys.
* **The authored URL wins.** A key the click-through URL already carries is
  never overwritten by a parameter.
* **Macros.** A value may be a literal or carry canonical AdCP macros such as
  `{CAMPAIGN_ID}`, plus `{PACKAGE_ID}`. Apostra fills `{MEDIA_BUY_ID}`,
  `{CREATIVE_ID}`, `{CAMPAIGN_ID}`, and `{PACKAGE_ID}` at sync time and drops a
  parameter whose identity macro has no value for that media buy. Serve-time
  macros such as `{CACHEBUSTER}` are left for the seller's ad server.
* **Keys** start with a letter or digit and use URL query-key characters (max 100).

## Lifecycle

<Steps>
  <Step title="Create the advertiser">
    For a live advertiser, confirm the buyer operator identity, then provide
    name, brand domain, required currency, sandbox mode, and optional channel
    preferences. In v3, sandbox advertisers can be created before that identity
    is claimed, but plan and Terms readiness still apply. Sandbox is locked
    after creation.
  </Step>

  <Step title="Configure defaults">
    Update channel preferences, `optimizationApplyMode`, UTM params, and
    frequency caps as policy evolves. Channel preferences never qualify a
    seller.
  </Step>

  <Step title="Run campaigns">
    Create campaigns under the advertiser. Discovery, audiences, and creatives
    are scoped to this advertiser.
  </Step>

  <Step title="Archive when done">
    `DELETE /api/v2/buyer/advertisers/:id` archives the advertiser (soft
    delete). Use `POST /api/v2/buyer/advertisers/:id/restore` to bring it back.
  </Step>
</Steps>

## Common operations

Before creating an advertiser from a domain, buyer MCP clients can call the
side-effect-free `resolve_buyer_brand` operation to show the brand-confirmation card.
The user then confirms by creating the advertiser normally.

### Create an advertiser

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.apostra.com/api/v2/buyer/advertisers \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Acme Corp",
      "description": "Acme global advertiser account",
      "brand": "acme.com",
      "primaryCurrency": "USD",
      "channels": ["display", "ctv"],
      "optimizationApplyMode": "MANUAL",
      "sandbox": false
    }'
  ```

  ```json Request body theme={null}
  {
    "name": "Acme Corp",
    "brand": "acme.com",
    "primaryCurrency": "USD",
    "channels": ["display"],
    "optimizationApplyMode": "MANUAL",
    "linkedAccounts": [
      {
        "storefrontId": 1,
        "sourceId": "src_main",
        "accountId": "acc_67890",
        "credentialId": "cred_snap_coke",
        "billingType": "brand"
      }
    ],
    "frequencyCaps": [
      { "max_impressions": 3, "window": { "interval": 7, "unit": "days" } }
    ]
  }
  ```
</CodeGroup>

### Get / list advertisers

```bash theme={null}
# List active advertisers (summary rows)
curl "https://api.apostra.com/api/v2/buyer/advertisers?status=ACTIVE&limit=50" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"

# Search by partial name or advertiser ID (search runs before pagination)
curl "https://api.apostra.com/api/v2/buyer/advertisers?status=ACTIVE&search=12345&limit=20" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"

# Get a single advertiser (full resource — linkedBrand, linkedAccounts, utmConfig, frequencyCaps)
curl https://api.apostra.com/api/v2/buyer/advertisers/12345 \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

List rows return the summary shape — `id`, `name`, `status`, `sandbox`, `brand`, `linkedAccountCount`, `createdAt`, `updatedAt`. Call `GET /api/v2/buyer/advertisers/:id` for the full resource (resolved brand object, full linked-account list, UTM config, frequency caps).

```json List response theme={null}
{
  "items": [
    {
      "id": "12345",
      "name": "Acme Corp",
      "status": "ACTIVE",
      "sandbox": false,
      "brand": "acme.com",
      "linkedAccountCount": 3,
      "createdAt": "2025-01-15T10:30:00Z",
      "updatedAt": "2025-01-20T14:45:00Z"
    }
  ],
  "total": 1,
  "hasMore": false,
  "nextOffset": null
}
```

List supports filtering by `status`, `name` (case-insensitive partial name match), `search` (partial name or advertiser ID), and `sandbox` (true/false), plus `limit` and `offset` pagination. Filters, including `search`, are applied before pagination.

### Update an advertiser

```bash theme={null}
curl -X PUT https://api.apostra.com/api/v2/buyer/advertisers/12345 \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corporation",
    "channels": ["display", "olv", "ctv"],
    "optimizationApplyMode": "AUTO",
    "frequencyCaps": [
      { "max_impressions": 5, "window": { "interval": 7, "unit": "days" } }
    ]
  }'
```

<Warning>
  Through the raw v2 API, brand domain, country scope, currency, and reporting
  time zone can be re-keyed only before the first campaign or seller-account
  binding. The canonical UI does not expose brand, country, or currency edits;
  create another advertiser there when those identity values must differ.
  `sandbox` is always immutable. When `frequencyCaps` or `channels` is provided
  on update, it **replaces** the existing set. Pass an empty array to clear;
  omit the field to leave it untouched.
</Warning>

<Note>
  For a legacy advertiser that has no settings row, mixed currencies across
  terminal (completed, cancelled, or archived) campaigns do not require a
  replacement advertiser. When adding its brand, send `primaryCurrency` with the
  intended currency from that history. Active campaigns and currently linked
  seller accounts still take precedence and reject conflicting choices.
</Note>

### Archive / restore

```bash theme={null}
curl -X DELETE https://api.apostra.com/api/v2/buyer/advertisers/12345 \
  -H "Authorization: Bearer $SCOPE3_API_KEY"

curl -X POST https://api.apostra.com/api/v2/buyer/advertisers/12345/restore \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

Archiving is a soft delete with these semantics:

* **Children stay readable.** The advertiser's campaigns, media buys, and
  creatives are retained and can still be listed and read while the
  advertiser is archived. Writes that would change them are still refused
  until the advertiser is restored.
* **The name is released.** Name uniqueness applies to active advertisers
  only, so a new advertiser may reuse an archived advertiser's name.
* **Restore can conflict.** Restoring an advertiser whose name is now held by
  an active advertiser returns `409 CONFLICT`; rename or archive the other
  advertiser first.
* **Restore reopens ledger accounts.** The advertiser's archived ledger
  accounts are reopened as part of the restore.

From the v3 agent surface, `save_advertiser` performs the same operations with
`isArchived: true` / `false`, and accepts `advertiserIds` to archive or
restore several advertisers in one call.

## Linked accounts

The `linkedAccounts` field connects the advertiser to existing accounts on upstream platforms — Snap, programmatic DSPs, agency seats. Each linked account is the pre-existing seat ID a partner already has for this brand or buyer; the link makes that ID addressable to Apostra for cross-platform reporting and pass-through execution.

<Note>
  Use `linkedAccounts` whenever the buyer **already has** a relationship with
  the upstream platform — an existing Snap seat, a DV360/Trade Desk account, an
  agency-managed seat. Linked accounts let Apostra reuse the seat ID for
  unified reporting and pass-through execution without re-provisioning
  credentials per call. This account choice is independent of campaign mode,
  BYOA, protocol connectivity, and settlement; do not infer those from
  `routingType`.
</Note>

### Fields

Each link maps these fields:

| Field          | Type              | Notes                                                                                                                                                                           |
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `storefrontId` | integer           | Storefront the source lives on. Get from `GET /api/v2/buyer/storefronts`.                                                                                                       |
| `sourceId`     | string            | Inventory source within `storefrontId` whose account is being linked.                                                                                                           |
| `accountId`    | string            | The seller-issued AdCP/source account ID (e.g. `acc_67890`). Must come from account discovery — Apostra does not mint it and clients must not substitute the internal `linkId`. |
| `credentialId` | string \| null    | The connected provider credential that owns this linked account. Include it when creating or updating links if multiple credentials can expose the same `accountId`.            |
| `billingType`  | string (optional) | The billing arrangement for this link. Defaults to the source's default billing type when omitted.                                                                              |

`billingType` is a source-defined string. The supported values per source are reported on the discovery endpoint (`GET /api/v2/buyer/advertisers/:advertiserId/accounts/available?storefrontId=...&sourceId=...` returns `billingOptions.supported` and `billingOptions.default`). Common values:

| Value    | Meaning                                                       |
| -------- | ------------------------------------------------------------- |
| `brand`  | The advertiser is billed directly by the partner              |
| `agency` | An agency is the billing entity acting on behalf of the brand |

Other values may be supported by individual partners — always check `billingOptions.supported` before sending.

### Endpoints

| Method | Path                                                          | Purpose                                                    |
| ------ | ------------------------------------------------------------- | ---------------------------------------------------------- |
| `GET`  | `/api/v2/buyer/advertisers/:advertiserId/accounts`            | List linked accounts on the advertiser                     |
| `GET`  | `/api/v2/buyer/advertisers/:advertiserId/accounts/available`  | List accounts the source has discovered but not yet linked |
| `GET`  | `/api/v2/buyer/advertisers/:advertiserId/accounts/:accountId` | Get a single linked account                                |
| `POST` | `/api/v2/buyer/advertisers/:advertiserId/accounts`            | Link a source account to the advertiser                    |

### List linked accounts

```bash theme={null}
curl "https://api.apostra.com/api/v2/buyer/advertisers/12345/accounts?storefrontId=1&sourceId=src_main&status=active&take=50" \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

Filters: `storefrontId` + `sourceId` (must be paired), `status` (`active`, `pending_approval`, `payment_required`, `suspended`, `closed`). Pagination: `take`, `skip`.

```json Response theme={null}
{
  "data": {
    "accounts": [
      {
        "linkId": "42",
        "accountId": "acc_67890",
        "credentialId": "cred_snap_coke",
        "name": "Acme c/o Pinnacle",
        "sources": [
          {
            "storefrontId": 1,
            "storefrontName": "Retail Media Network",
            "sourceId": "src_main",
            "sourceName": "Retail Network Agent"
          }
        ],
        "advertiserId": "12345",
        "billing": "brand",
        "status": "active",
        "createdAt": "2025-01-15T10:30:00Z",
        "updatedAt": "2025-01-20T14:45:00Z"
      }
    ],
    "total": 1
  }
}
```

### Link an account

```bash theme={null}
curl -X POST https://api.apostra.com/api/v2/buyer/advertisers/12345/accounts \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "storefrontId": 1,
    "sourceId": "src_main",
    "accountId": "acc_67890",
    "billingType": "brand"
  }'
```

The response carries the full `AccountOutput` with the server-assigned `linkId`. To pre-fill the request body, call `GET /accounts/available?storefrontId=1&sourceId=src_main` first — it returns every discovered account with its supported `billingOptions`.

### Get a single linked account

```bash theme={null}
curl https://api.apostra.com/api/v2/buyer/advertisers/12345/accounts/acc_67890 \
  -H "Authorization: Bearer $SCOPE3_API_KEY"
```

Returns the same `AccountOutput` shape as list. Use this to confirm a link's current `status` after a source-side state change (e.g. moving from `pending_approval` to `active`).

<Tip>
  Linked accounts surface external account IDs (e.g. `acc_67890`) so
  cross-platform reporting and pass-through execution can target them without
  re-discovering credentials. The `linkId` is Apostra-side primary key
  for the link itself; the `accountId` is the source-side account.
</Tip>

## Sandbox advertisers

Set `sandbox: true` at creation to mark the advertiser as test-only. All ADCP operations under sandbox advertisers — discovery, media buy creation, execution — route to sandbox-flagged sales agent accounts. No real platform calls are made and no real spend occurs. In the v3 `save_advertiser` journey, you can create, update, and archive a sandbox advertiser before claiming the buyer operator identity. Plan and Terms readiness checks still apply; this exemption is only for the operator-identity check.

Use sandbox advertisers to:

* Rehearse the discovery → execute → report flow end-to-end
* Test creative manifest workflows without notifying publishers
* Train AI agents against the API without billing implications

## Optimization apply mode

`optimizationApplyMode` controls whether Apostra's RL optimizer applies suggestions to media buys automatically.

* `MANUAL` (default) — suggestions appear in the suggestion feed; a human or agent must approve before they hit media buys
* `AUTO` — suggestions are applied immediately

The advertiser-level setting is the default for campaigns under the advertiser. Each campaign can override it.

## Related concepts

<CardGroup cols={2}>
  <Card title="Campaign" href="./campaign" icon="rocket">
    Marketing initiatives owned by the advertiser
  </Card>

  <Card title="Brand" href="./brand-story" icon="book-open">
    Brand identity resolved from AdCP registry or `/.well-known/brand.json`
  </Card>

  <Card title="Frequency caps" href="../guides/frequency-caps" icon="gauge">
    Buyer-side caps enforced by Apostra across publishers
  </Card>

  <Card title="Creatives" href="./creative" icon="image">
    Manifest-based creatives nested under campaigns
  </Card>
</CardGroup>
