Skip to main content

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

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), and keeps its own saved copy of the result
  • Default policies — optimizationApplyMode, UTM parameters, and frequency caps set on the advertiser flow down to its campaigns
  • Sandbox context — sandbox advertisers have a separate, immutable identity and retain sandbox context on supported account flows. The flag does not make every provider account a test account or guarantee no 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, or All channels; overlap affects automatic seller qualification without creating advertiser-owned selection edges
  • 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. 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. 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 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. The API has no operation that repairs an unresolved relationship. 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. In Apostra, the seller’s Advertisers workspace in the left rail shows this roster. Settings for one advertiser, including who at the client has access and what each person can do, are in that advertiser’s own Advertiser settings under Client access. See Read client access for an advertiser and Invite and manage a client’s access. There is no separate Advertisers page in Settings.

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.

Confirming markets and channels with your agent

When you use a V3 agent to create an advertiser or classify an empty market or channel setting, it may suggest the countries where the brand operates and the channels it is likely to buy. Review the suggestion and confirm it, change it, or choose All countries or All channels before anything is saved. We keep the suggestion with your final answer so we can improve future suggestions; the suggestion never changes what you chose.

Summary shape (list rows)

Full shape (single advertiser)

The single-advertiser endpoint returns every field above plus:

Brand resolution

An advertiser records a brand domain or website URL. When the brand is linked, Apostra resolves the brand identity once, from the first of these sources that has it, and saves the result as a copy that belongs to that advertiser:
  1. The advertiser’s /.well-known/brand.json (per the AdCP spec)
  2. The AdCP brand registry
  3. Brandfetch enrichment
  4. The brand’s live website, read and summarized into an unverified draft
If none of them has the brand, including when the domain has no website that can be read, the request is refused with a VALIDATION_ERROR on brand. Publish a brand.json for the domain or register the brand, then try again. If a source is temporarily unavailable, the request returns SERVICE_UNAVAILABLE instead and can be retried. The saved copy is not shared across advertisers, and it is not kept in step with the brand’s published file. Apostra re-resolves it only when the copy is missing or can’t be read, or when you link a different brand domain (or set saveBrand: true), which is only allowed before the advertiser’s first campaign or seller-account binding. A change the brand publishes later does not reach advertisers that already linked it. Pass brand: "northwind.example" and Apostra resolves the brand identity and saves it, 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. In the brand profile preview, the V3 get({ kind: "advertiser", include: ["brandProfile"] }) read returns a summary of the saved copy as generation uses it: the brand kit, voice rules, disclaimers, and where each value came from. It also returns advertiser-stated absences and pending differences for review. It is a limited preview, available only to accounts in the V3 buyer beta that also have Creative Engines access. See Brand profile (preview).

Advertiser-stated brand profile

An advertiser can add its own brand guidance through V3 save_advertiser({ advertiserId, brandProfile }). This guidance belongs to the advertiser, not to a domain shared by other advertisers. The group can state a brand-kit voice or tagline, voice attributes and do/don’t rules, banned terms, brand disclaimers, and the pages or profiles the brand speaks through. Save brandProfile in its own save_advertiser call with advertiserId. Do not combine it with a name, tracking, labels, or any other advertiser change. Omitting a field means it has not been provided. To record that the advertiser confirmed there is no kit, voice guidance, or disclaimer, set the corresponding brandProfile.stated.statedAbsence value to true; this prevents a later workflow from treating that known absence as a missing answer. Set brandProfile.stated to null to clear the stated layer. Each response carries a revision for conflict-safe updates. A pendingDifferences entry records a new published value that disagrees with a stated value; Apostra does not change either value until a person resolves it. A profile holds at most 50 pending differences. A value larger than 2 KB keeps its leading items that fit and is marked truncated, with its original size in originalBytes. If an advertiser has a stated profile and Apostra cannot load it when a creative is being generated, generation stops with a retryable error rather than send the engine incomplete safety guidance. Try the request again; an advertiser with no stated profile is not affected. When Apostra sends a build request to a third-party creative engine, AdCP brand kit fields use their standard AdCP locations. For V3 beta accounts with Creative Engines access, brand-wide banned terms, voice rules, and brand disclaimers have no AdCP field today, so they are carried only under ext.apostra for that engine. They are not sent to sellers for delivery.
See the Brand object guide for the brand resolution model in detail.

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 or countries differ. Currency alone does not make two advertisers different within a buyer account (see One brand, one market). Those rows are separate campaign and billing containers, not different operator identities. An AdCP 3.2 seller receives the whole BrandKey in brand: domain, optional brand_id, and countries as sorted ISO 3166-1 alpha-2 codes, because AdCP 3.2 includes the countries in the account key. A seller on AdCP 3.1 or earlier never receives countries, operator_unit, currency or timezone, because its account key has none of those fields. For that seller they stay inside Apostra, and advertisers that differ only in them share one account at the seller. A global scope sends no countries, since AdCP defines an omitted list as the global or default identity. An advertiser carries a brand ID only when the brand publishes it: the brand’s /.well-known/brand.json, or a registry entry the brand owner verified, must list that ID. A brand ID that appears only in community or enriched brand data is not stored, and the advertiser uses the domain alone. Apostra rechecks each stored brand ID against the brand’s registry entry about once a week. If the brand later stops listing an ID that sellers already know, Apostra keeps sending it so the advertiser’s existing seller accounts stay intact, and flags the mismatch to Scope3 operations. An explicitly empty country list is a confirmed global BrandKey scope. The country dimension does not block automatic matching in any declared seller country; channel overlap and the seller’s marketplace, transaction, and authority rules still apply. It does not mean the advertiser has zero markets. This differs from a legacy advertiser with brandCountriesKnown: false, whose empty response list means the scope is still unknown and must be classified before seller activation. Channels work the same way. An empty channels list with channelsKnown: true is the All channels answer: the advertiser is matched in every channel a seller offers, and seller accounts are set up ahead of time with every matching seller in its markets, just as they are for a named list. With channelsKnown: false, the advertiser has not chosen its channels yet. Sellers still match it in every channel when you buy, but no seller account is set up ahead of time until it answers. Creating an advertiser without channels leaves them unanswered; send [] to choose All channels. 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.

One brand, one market

Within one buyer account, two active advertisers cannot share a brand (the same domain, compared without regard to case, and the same brand ID, or both without one) when their countries overlap. Apostra refuses the save with 400 VALIDATION_ERROR on brandCountries. The message names the advertiser that already holds the brand, with its advertiserId, and suggests countries outside the overlap. The error details carry reason: brand_identity_overlap, that advertiserId and the overlappingCountries (null when the other advertiser covers every country).
  • No countries means every country. An advertiser with an empty country list, or a legacy advertiser whose scope is still unknown, overlaps every other advertiser of the same brand. A global advertiser and a country-split advertiser of one brand cannot both be active; give each its own countries.
  • Currency does not separate advertisers. The same brand and overlapping countries is refused whatever the currency. Currency is a setting on the advertiser; express different markets with countries.
  • Sandbox and live are checked separately. A sandbox advertiser and a live advertiser may share a brand and countries.
  • Another buyer account never conflicts, even under the same operator.
  • Archived advertisers do not count for this rule. Restoring one is checked like a new save: it is refused if it would overlap an active advertiser. Archiving does not release an exact brand identity, though. An archived live (non-sandbox) advertiser still reserves its exact brand, brand ID, countries and currency, so creating a new advertiser with that same identity returns 409 CONFLICT: restore the archived advertiser instead. An archived sandbox advertiser releases its identity (see below), so a new sandbox advertiser may take it.
The rule applies to every write that sets or changes the brand domain, brand ID, countries, sandbox mode or the active state: creating an advertiser, changing its brand or countries, and restoring it. This holds on the REST API, the v3 save_advertiser tool, Murph and the web app, because they all save through the same service. Two saves for the same brand at the same moment are taken one after the other, so only one can create an overlap. Advertisers that overlapped before the rule are left as they are. An unrelated edit to one of them, such as a rename, passes. So does an edit that keeps the overlap the same or narrows it, for example by removing a country the two shared, which is how you resolve one. A change that adds a shared country (for example, going global against an advertiser that covers more of the same countries), moves the overlap to a different country, or overlaps a further advertiser is refused. Only one active sandbox advertiser can hold a brand identity at a time. An archived sandbox advertiser releases its brand identity, so you can create a new sandbox advertiser for the same brand, countries, and currency. This includes brands the advertiser used before a brand change: an archived sandbox advertiser releases all of them. An archived non-sandbox advertiser keeps its brand identity, and every earlier brand it has used: restore it instead of creating a replacement. After a brand change, the earlier brands stay reserved to the advertiser, with the sandbox exception above. Changing back reuses the earlier seller accounts whose links still exist; a seller-provisioned link that a newer link at the same seller agent replaced may need to be found or provisioned again.

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. See the 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.
  • mapping_artifact records the translation and documentation provenance.
  • source_dialect and compiler_version make the transformation reproducible.
The compiler recognises HappyDemics pixels, Google Ad Manager macros, IAB VAST macros, and Campaign Manager 360 tags (ad.doubleclick.net/ddm/...). For Campaign Manager 360 it maps [timestamp], ${GDPR}, ${GDPR_CONSENT_<GVL ID>}, ${GPP_STRING_<GVL ID>}, and ${GPP_SID} to their AdCP macros, written either as-is or with the percent-encoded braces a URL parser produces ($%7BGDPR%7D). Any other placeholder stays unresolved, including ones Google does not define such as [BREAKPOSITION], and a tracker that contains one is not publishable until the placeholder is removed or replaced. 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. Values you supply in macro_values are labeled provided; built-in values are labeled synthetic or privacy_suppressed, and no real advertising identifier should be passed to the tool. The tool names dialects, mapping status and mapping fields as AdCP does; the v3 tool catalog lists them and the namespace of each dialect Apostra maps. Once a creative is saved, the V3 save_creative result also carries macroPreview: the same dry run for each tracker on that creative, with the macros the seller’s format fills and the macros that stay literal. A seller’s list is on get({ kind: "creative_format" }) as macro_resolution_capabilities.
Effective impression trackers are attached when a display or HTML creative syncs to a seller and the creative has a tracker slot or HTML to carry them. Video (VAST) creatives and other tracker types are not attached yet, and rendered preview is separate follow-up work. Headless macro testing is available for every tracker.
When a seller serves through Google Ad Manager (GAM), each AdCP macro in an impression tracker is converted to GAM’s own macro, and GAM fills the value when the ad serves. Apostra sends the tracker with its AdCP macros, and the seller’s sales agent converts them when it writes the tracker to GAM, whether the tracker goes in its own field or inside the creative’s HTML. Apostra writes only the vendor-scoped consent macro itself for a tracker inside HTML, because the tracker’s IAB vendor ID can’t reach the sales agent there. Each conversion comes from Google’s macro reference. Apostra fills {MEDIA_BUY_ID}, {CREATIVE_ID}, {CAMPAIGN_ID}, and {PACKAGE_ID} itself before sync. A tracker that uses any other macro is not sent to a GAM seller, because GAM has no equivalent. A tracker that uses {CLICK_URL} is not sent either: Google counts a click every time a pixel carrying its click macro loads, then discards those clicks as invalid. Run test_creative_macros with targetDialect: gam to see the conversion for a tracker before you save it.
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.
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, canonicalUrl, and effectiveTrackers. A tracker reads back its url as you saved it, beside the translated canonicalUrl; 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: 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. The v2 advertiser utmConfig field accepts at most 20 entries per request. 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. When you create or update a creative whose click-through URL already sets a key that one of these parameters also sets, the response’s warnings array names each such key and the layer that set it (v2 creative create and update, and V3 save_creative). The creative is still saved. Remove the key from the click-through URL if you want the configured parameter instead.
  • Macros. A value may be a literal, or carry any AdCP universal macro as a {MACRO} token, for example utm_content={CREATIVE_ID}_{DOMAIN}{APP_BUNDLE}. Apostra also accepts six Apostra extension macros, which AdCP does not define: {CAMPAIGN_ID}, {TRACKING_TAG_ID}, {DATA_SET_ID}, {TACTIC_ID}, {SALES_AGENT_ID} and {ATTRIBUTION_TRACKING_ID}. Only {CAMPAIGN_ID} is filled; the other five are accepted so stored values stay valid, but nothing fills them, so a parameter that carries one is left off the click URL. Any other name in braces, and template syntax such as {{...}} or ${...}, is rejected.
  • Identity macros are filled when the creative syncs. Apostra fills the AdCP macros {MEDIA_BUY_ID}, {CREATIVE_ID} and {PACKAGE_ID}, and the Apostra extension macro {CAMPAIGN_ID}, with its own ids. A parameter whose identity macro has no value for that sync is left off the click URL, so a seller never receives a placeholder for an id only Apostra knows. {PACKAGE_ID} has a value only when the creative is assigned to exactly one package of the media buy.
  • Other macros are filled per impression or click, not at sync. Values such as {DOMAIN}, {APP_BUNDLE}, {PAGE_URL}, and {CACHEBUSTER} are only known when the ad serves, so they stay in the click URL as {MACRO} tokens. The seller’s ad server fills them on Apostra’s click tracker, and Apostra’s click redirect copies each filled value into your landing page URL and removes any token it cannot fill, so a click through the tracker never lands on a literal token. Whether a seller fills a given macro is up to that seller; an unfilled one is left out. If a creative reaches the seller without Apostra’s click tracker, only the seller’s ad server can fill these macros, and any it leaves unfilled reach the landing page as written. That happens when the advertiser has turned click tracking off (v2 click_tracker_enabled, V3 tracking.clickTrackerEnabled): Apostra then sends the click-through URL without its click tracker, on a campaign-level sync and with a media buy alike, and still fills the identity macros above. Turning click tracking off covers the click tracker Apostra adds when a creative syncs; a click tracker generated when the creative was created, while click tracking was on, can still reach the seller. The older Apostra names such as {TACTIC_ID} are never filled in a click URL, so a parameter carrying one is always left off.
  • Keys start with a letter or digit and use URL query-key characters (max 100).
  • A change re-syncs affected creatives automatically. When an advertiser or campaign parameter change alters the click URL of a creative that a seller already holds on a live media buy, Apostra re-sends that creative to that seller with the new parameters, once per media buy so each copy keeps its own identity macros. A live media buy is one that has been submitted and has not completed, been canceled, failed, or been rejected. The re-sync starts about a minute after the change, and further edits in that minute join it, so a burst of edits sends each creative to each seller once. A creative whose click URL comes out the same, for example because the campaign or the creative itself sets the changed key, is not re-sent. The write’s response carries creativeResync (creative_resync on the tracking-config resources) with how many creatives and sellers the re-sync covers; it is absent when nothing a seller holds changes. Each seller decides whether a changed click URL needs re-review, and its decision appears on the creative the same way it does after a creative edit. The copy the seller already has keeps serving until the seller says otherwise. If a seller has not yet reported its review of an earlier copy of the same creative, Apostra waits for that review to finish, up to seven days after that copy was sent, and then sends the change.

How v2 utmConfig writes differ by layer

The v2 utmConfig field has a different write shape at each layer: So the same entries have different effects at each layer. Sent as the advertiser array, any advertiser keys you leave out are removed. Sent as a campaign’s params, the campaign keys you leave out stay unless you set deleteMissing: true. v2 also cannot remove an inherited key at a lower layer: a campaign can only override an advertiser key with a different value. Use V3 macroAdditions for per-layer control. V3 has one model at all three layers: save_advertiser and save_campaign take tracking.macroAdditions, and save_creative takes macroAdditions. Each is a list of { paramKey, value } that sets that layer’s whole list, and a null value suppresses an inherited key at that layer. Both versions read and write the same stored parameters, so you can move a buyer to V3 without migrating data.

Lifecycle

1

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. Sandbox advertisers can be created before that identity is claimed, but plan and Terms readiness still apply. Sandbox is locked after creation.
2

Configure defaults

Update channel preferences, optimizationApplyMode, UTM params, and frequency caps as policy evolves. Channel overlap affects automatic seller qualification without creating advertiser-owned selection edges.
3

Run campaigns

Create campaigns under the advertiser. Discovery, audiences, and creatives are scoped to this advertiser.
4

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.

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 call is a quick preview: it answers within about 8 seconds, and if the brand can’t be read in that time it returns resolved: false with pending: true for the domain, which means the brand isn’t known yet, not that none exists. A card without pending and resolved: false is a domain with no registered brand. A repeat call a little later, or for a domain looked up in the last ten minutes, is usually answered from what the first call found. The user then confirms by creating the advertiser normally; creating reads the brand in full, so a slow or skipped preview doesn’t change what is saved.

Create an advertiser

Get / list advertisers

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).
List response
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

Through the raw v2 API, brand domain, an already-known country scope, currency, and reporting time zone can be re-keyed only before the first campaign or seller-account binding. A still-unknown legacy country scope may be classified once after first use. The canonical UI exposes that one-time classification but not edits to known brand, country, or currency values; create another advertiser there when known currency or country values must differ. After first use, an account administrator can still change the brand for future buys through save_advertiser, which previews the effect first; see Change an advertiser’s brand after first use. Currency, reporting time zone, and sandbox stay fixed: a different currency or sandbox state is a different advertiser. When frequencyCaps or channels is provided on update, it replaces the existing set. Pass an empty array to clear frequencyCaps or to choose All channels; omit the field to leave it untouched.
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.

Archive / restore

Archiving is a soft delete with these semantics. The default advertiser list shows active advertisers only, but a direct read of an archived advertiser returns it with status: ARCHIVED. Archiving blocks changes until restoration; it does not erase the advertiser’s history or make its child records unreadable.
  • Children stay readable. The advertiser’s campaigns, media buys, reporting, delivery, creatives, catalogs, and linked accounts 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.
  • A sandbox advertiser’s brand identity is released. A new sandbox advertiser may take an archived sandbox advertiser’s brand identity (brand domain, brand ID, countries, and currency), including any brand it used before a brand change. A non-sandbox advertiser’s brand identities stay reserved while it is archived.
  • Restore can conflict. Restoring an advertiser returns 409 CONFLICT when an active advertiser now holds its name, or, for a sandbox advertiser, its brand identity. The brand identity conflict has reason: brand_identity_exists and the advertiserId of the active sandbox advertiser. Only one active sandbox advertiser can hold a brand identity, so archive that advertiser (or rename the other, for a name conflict) before restoring. Restoring also returns 400 VALIDATION_ERROR with reason: brand_identity_overlap when the advertiser’s brand overlaps an active advertiser’s countries (see One brand, one market).
  • 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.
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.

Fields

Each link maps these fields: 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: Other values may be supported by individual partners — always check billingOptions.supported before sending.

Endpoints

List linked accounts

Filters: storefrontId + sourceId (must be paired), status (active, pending_approval, payment_required, suspended, closed). Pagination: take, skip.
Response
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

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

Sandbox advertisers

Set sandbox: true at creation to give the advertiser a separate, immutable sandbox identity. Apostra retains that identity when it resolves and synchronizes supported accounts. On an Apostra-managed ad-server storefront, the managed agent uses its sandbox advertiser or refuses the request if it cannot map one. On provider-backed modular storefronts, Apostra records a sandbox buy locally instead of sending it to the provider. This is not a universal no-spend guarantee. A direct platform connection can still call the provider account you have configured. Before running a test, use the provider’s own sandbox or test account and its documented test context. The advertiser flag alone does not make every provider response simulated or prevent a provider account from reserving, delivering, or spending. You can create, update, and archive a sandbox advertiser before claiming the buyer operator identity, on every surface. Plan and Terms readiness checks still apply; this exemption is only for the operator-identity check. Use sandbox advertisers to:
  • Keep test advertiser records separate from production advertiser records
  • Exercise supported no-spend workflows with a provider sandbox or test account
  • Test API setup and validation before using a production advertiser

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.

Campaign

Marketing initiatives owned by the advertiser

Brand

Brand identity resolved from AdCP registry or /.well-known/brand.json

Frequency caps

Buyer-side caps enforced by Apostra across publishers

Creatives

Manifest-based creatives nested under campaigns