> ## Documentation Index
> Fetch the complete documentation index at: https://agenticadvertisingorg-snap-format-preview-links.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Universal Macros

> Universal macros in AdCP insert dynamic tracking data into creatives with platform-agnostic placeholders replaced at impression time.

Universal macros enable buyers to include dynamic tracking data in their creatives without needing to know each publisher's ad server implementation details. Macros are placeholders that get replaced with actual values at impression time.

## Overview

When you provide creative assets to AdCP, you can include universal macro placeholders in:

* Impression tracking URLs
* Click tracking URLs
* VAST tracking events
* Landing page URLs

**Example**:

```
https://track.brand.com/imp?
  campaign={MEDIA_BUY_ID}&
  creative={CREATIVE_ID}&
  device={DEVICE_ID}&
  cb={CACHEBUSTER}
```

At impression time, this becomes:

```
https://track.brand.com/imp?
  campaign=mb_spring_2025&
  creative=cr_video_30s&
  device=ABC-123-DEF&
  cb=87654321
```

## Available Macros by Format

Different creative formats support different macros. Use `list_creative_formats` to see which macros are available for each format.

### Common Macros (All Formats)

| Macro            | Description                      | Example Value                    |
| ---------------- | -------------------------------- | -------------------------------- |
| `{MEDIA_BUY_ID}` | Your AdCP media buy identifier   | `mb_spring_2025`                 |
| `{PACKAGE_ID}`   | Your AdCP package identifier     | `pkg_ctv_prime`                  |
| `{CREATIVE_ID}`  | Your AdCP creative identifier    | `cr_video_30s`                   |
| `{CACHEBUSTER}`  | Random number to prevent caching | `87654321`                       |
| `{TIMESTAMP}`    | Unix timestamp in milliseconds   | `1704067200000`                  |
| `{CLICK_URL}`    | Publisher's click tracking URL   | *(auto-inserted by sales agent)* |

### Privacy & Compliance Macros

**Critical for regulatory compliance** - Use these to respect user privacy choices in your creative logic.

| Macro                 | Description                                      | Example Value                                         |
| --------------------- | ------------------------------------------------ | ----------------------------------------------------- |
| `{GDPR}`              | GDPR applicability flag                          | `1` (applies), `0` (doesn't apply)                    |
| `{GDPR_CONSENT}`      | IAB TCF 2.0 consent string                       | `CPc7TgPPc7TgPAGABC...`                               |
| `{US_PRIVACY}`        | US Privacy (CCPA) string                         | `1YNN`                                                |
| `{GPP_STRING}`        | Global Privacy Platform consent string           | `DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA` |
| `{GPP_SID}`           | GPP Section ID(s) indicating applicable sections | `7`, `7,8` (US National, US National + California)    |
| `{IP_ADDRESS}`        | User IP address (often masked for privacy)       | `203.0.113.42`, `""` (when restricted)                |
| `{LIMIT_AD_TRACKING}` | Limit Ad Tracking enabled                        | `1` (limited), `0` (allowed)                          |

> **Privacy Warning**: `{IP_ADDRESS}` is considered personal data under GDPR and many privacy regulations. This macro may return an empty string or masked/truncated IP depending on user privacy settings, publisher policies, and regional regulations. Use geo macros (`{COUNTRY}`, `{REGION}`, `{CITY}`) instead when possible.

**Example - Privacy-aware tracking**:

```javascript theme={null}
// In creative logic
if (GDPR == 1 && GDPR_CONSENT == '') {
  // No consent - don't load tracking pixels
} else {
  // Load tracking
}
```

### Device & Environment Macros

| Macro            | Description                       | Example Value                                |
| ---------------- | --------------------------------- | -------------------------------------------- |
| `{DEVICE_TYPE}`  | Device category                   | `mobile`, `tablet`, `desktop`, `ctv`, `dooh` |
| `{OS}`           | Operating system                  | `iOS`, `Android`, `tvOS`, `Roku`             |
| `{OS_VERSION}`   | OS version                        | `17.2`, `14.0`                               |
| `{DEVICE_MAKE}`  | Device manufacturer               | `Apple`, `Samsung`, `Roku`                   |
| `{DEVICE_MODEL}` | Device model                      | `iPhone15,2`, `Roku Ultra`                   |
| `{USER_AGENT}`   | Full user agent string            | `Mozilla/5.0 ...`                            |
| `{APP_BUNDLE}`   | App bundle ID (domain or numeric) | `com.publisher.app`, `123456789`             |
| `{APP_NAME}`     | Human-readable app name           | `Publisher News App`                         |

### Geographic Macros

| Macro       | Description                                                                                     | Example Value                                                            |
| ----------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `{COUNTRY}` | ISO 3166-1 alpha-2 country code                                                                 | `US`, `GB`, `CA`, `FR`, `JP`, `AU`                                       |
| `{REGION}`  | State/province/region code                                                                      | `NY`, `CA` (US states), `ON` (Canada), `IDF` (France), `NSW` (Australia) |
| `{CITY}`    | City name                                                                                       | `New York`, `London`, `Tokyo`, `Sydney`                                  |
| `{ZIP}`     | Postal code                                                                                     | `10001` (US), `SW1A 1AA` (UK), `75001` (France), `100-0001` (Japan)      |
| `{DMA}`     | [Nielsen DMA code](https://help.thetradedesk.com/s/article/Nielsen-DMA-Regions) (US TV markets) | `501` (New York), `803` (Los Angeles)                                    |
| `{LAT}`     | Latitude                                                                                        | `40.7128`, `51.5074`, `35.6762`                                          |
| `{LONG}`    | Longitude                                                                                       | `-74.0060`, `-0.1278`, `139.6503`                                        |

### Identity Macros

| Macro              | Description                       | Example Value     |
| ------------------ | --------------------------------- | ----------------- |
| `{DEVICE_ID}`      | Mobile advertising ID (IDFA/AAID) | `ABC-123-DEF-456` |
| `{DEVICE_ID_TYPE}` | Type of device ID                 | `idfa`, `aaid`    |

### Web Context Macros

For web-based inventory:

| Macro        | Description                     | Example Value           |
| ------------ | ------------------------------- | ----------------------- |
| `{DOMAIN}`   | Domain where ad is shown        | `nytimes.com`           |
| `{PAGE_URL}` | Full page URL (encoded)         | `https%3A%2F%2F...`     |
| `{REFERRER}` | HTTP referrer URL               | `https://google.com`    |
| `{KEYWORDS}` | Page keywords (comma-separated) | `business,finance,tech` |

### Placement & Position Macros

| Macro             | Description                         | Example Value              |
| ----------------- | ----------------------------------- | -------------------------- |
| `{PLACEMENT_ID}`  | Global Placement ID (IAB standard)  | `12345678`                 |
| `{FOLD_POSITION}` | Position relative to fold (display) | `above_fold`, `below_fold` |
| `{AD_WIDTH}`      | Ad slot width                       | `300`, `728`               |
| `{AD_HEIGHT}`     | Ad slot height                      | `250`, `90`                |

### Video Content Macros

For video formats with content context:

| Macro              | Description                 | Example Value                 |
| ------------------ | --------------------------- | ----------------------------- |
| `{VIDEO_ID}`       | Content video identifier    | `vid_12345`                   |
| `{VIDEO_TITLE}`    | Content video title         | `Breaking News Story`         |
| `{VIDEO_DURATION}` | Content duration in seconds | `600`                         |
| `{VIDEO_CATEGORY}` | IAB content category        | `IAB1` (Arts & Entertainment) |
| `{CONTENT_GENRE}`  | Content genre               | `news`, `sports`, `comedy`    |
| `{CONTENT_RATING}` | Content rating              | `G`, `PG`, `TV-14`            |
| `{PLAYER_WIDTH}`   | Video player width          | `1920`                        |
| `{PLAYER_HEIGHT}`  | Video player height         | `1080`                        |

### Video Ad Pod Macros

For video ads in commercial breaks:

| Macro            | Description                | Example Value |
| ---------------- | -------------------------- | ------------- |
| `{POD_POSITION}` | Position within ad break   | `1`, `2`, `3` |
| `{POD_SIZE}`     | Total ads in this break    | `3`           |
| `{AD_BREAK_ID}`  | Unique ad break identifier | `break_mid_1` |

**Note**: Video formats also support all [IAB VAST 4.x macros](http://interactiveadvertisingbureau.github.io/vast/vast4macros/vast4-macros-latest.html) like `[CACHEBUSTING]`, `[TIMESTAMP]`, `[DOMAIN]`, `[IFA]`, etc. These work natively in VAST XML.

### Audio Content Macros

For audio formats with content context:

| Macro               | Description                         | Example Value                      |
| ------------------- | ----------------------------------- | ---------------------------------- |
| `{STATION_ID}`      | Radio station or podcast identifier | `WXYZ-FM`, `pod_12345`             |
| `{COLLECTION_NAME}` | Program or collection name          | `Morning Drive`, `Tech Talk Daily` |
| `{INSTALLMENT_ID}`  | Podcast episode identifier          | `ep_2025_01_15`                    |
| `{AUDIO_DURATION}`  | Content duration in seconds         | `3600`                             |

### Impression Identification

| Macro             | Description                                                                       | Example Value                                                                                     |
| ----------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `{IMPRESSION_ID}` | Upstream-minted unique identifier per impression (publisher or ad-decision layer) | `8c9e2f3a-7b1c-4d5e-9f6a-1a2b3c4d5e6f` (UUID), `01ARZ3NDEKTSV4RRFFQ69G5FAV` (ULID), or equivalent |

The `{IMPRESSION_ID}` macro carries a unique identifier for one impression opportunity. It is a general-purpose impression key — buyers, measurement vendors, attribution providers, and verification services all use this kind of identifier to deduplicate impression events, reconcile pixel firings across vendors, join impressions to clicks, and detect retries. Whichever upstream layer mints the value (see hierarchy below) substitutes it into creative tracking URLs, and any downstream consumer that needs to identify the impression uses the same value.

**Common use cases:**

* **Per-impression deduplication.** A single impression often fires many pixels (impression, viewability, video quartiles, completion, third-party verification). All of them sharing one `{IMPRESSION_ID}` lets downstream consumers reconcile "is this the same impression?" without time- or URL-based heuristics.
* **TMP cross-identity dedup.** When a TMP impression resolves to multiple user identities, the buyer's impression tracker writes the same `{IMPRESSION_ID}` to every identity's log so distinct-impression counts dedup correctly. See [Impression Tracker Implementation](/docs/trusted-match/impression-tracker-implementation).
* **Cross-vendor reconciliation.** Advertisers comparing delivery between an ad server, a verification vendor, and a measurement vendor join on `{IMPRESSION_ID}` rather than building cross-walks.
* **Pixel retry dedup.** A pixel firing twice (network retry, page refresh) carries the same `{IMPRESSION_ID}`, so a server-side dedup pass on the id catches retries without over-counting.

**When to include it:**

* **Recommended on every impression tracking URL** — the macro is small, cheap, and the use cases above all benefit when it is consistently present.
* **Required for TMP context-only impressions** — when Identity Match returned no eligibility (or wasn't called) and no `{TMPX}` is present on the pixel, `{IMPRESSION_ID}` is the only available cross-identity dedup key for the buyer's impression tracker.

**Format is implementation choice.** The protocol requires uniqueness with sufficient entropy to avoid cross-seller / cross-time collisions; it does not pin a wire format. UUID (any version), ULID, snowflake-style IDs, or any other collision-resistant identifier scheme are all acceptable. Buyers MUST treat the value as an opaque string for dedup purposes — no parsing, no format assumptions.

**Three valid sources for the value, in priority order:**

1. **Publisher-side mint** (highest priority): the publisher's own first-party code mints a fresh identifier per impression opportunity — e.g., server-side, before the ad request reaches the decision layer — and passes it forward so the decision layer can substitute it via `{IMPRESSION_ID}`. Works for both context-only and identity-bearing impressions.
2. **Decision-layer mint** (used when the publisher hasn't minted): the ad-decision layer (Prebid TMP module, ad server, SSP, or equivalent client) mints the identifier at the context↔identity join and substitutes it via `{IMPRESSION_ID}`. Also works for both context-only and identity-bearing impressions.
3. **Buyer-side mint at TMPX decode** (fallback, identity-bearing only): when neither layer above minted, the buyer's impression tracker mints the id locally at TMPX decode time per [`Impression Tracker Implementation`](/docs/trusted-match/impression-tracker-implementation). This works only when `{TMPX}` is present — it cannot cover context-only impressions.

Each layer MUST defer to whatever value an upstream layer already produced — minting a fresh id at a lower layer when a higher layer already supplied one would split logs for the same impression across two ids. In practice this means: if the publisher minted, the decision layer passes it through; if either of those minted, the buyer uses the value from the pixel rather than minting at decode.

**Relationship to `{CACHEBUSTER}`.** The two are not interchangeable. `{CACHEBUSTER}` is a low-entropy anti-cache value sufficient to defeat HTTP intermediaries; it is not a globally-unique impression key. Both can appear in the same tracking URL serving different purposes.

### TMP Exposure Tracking

| Macro    | Description                         | Example Value            |
| -------- | ----------------------------------- | ------------------------ |
| `{TMPX}` | TMP exposure token (HPKE-encrypted) | `k1.dG1weC1leGFtcGxl...` |

The `{TMPX}` macro carries an encrypted exposure token from the [Identity Match](/docs/trusted-match/specification) response. It contains the user's resolved identity tokens encrypted via HPKE, enabling the buyer's impression pixel to log per-user exposures for real-time frequency capping. Publishers substitute `{TMPX}` into tracking URLs exactly like other macros. The token is opaque — publishers MUST NOT parse, log, or make decisions based on its value.

See [TMPX Exposure Tokens](/docs/trusted-match/specification#tmpx-exposure-tokens) for the encryption format and key management.

### AXE Integration (Legacy)

| Macro    | Description                            | Example Value              |
| -------- | -------------------------------------- | -------------------------- |
| `{AXEM}` | AXE contextual metadata (encoded blob) | `eyJjb250ZXh0IjoiLi4uIn0=` |

The `{AXEM}` macro is from the legacy AXE integration. In [TMP](/docs/trusted-match), this is replaced by:

* **Structured creative assets** move to the `creative_manifest` field on the [Offer](/docs/trusted-match/specification#offer).
* **Per-user exposure tracking** uses the [`{TMPX}`](#tmp-exposure-tracking) macro from Identity Match.

### Catalog Item Macros

For catalog-driven creatives (carousels, dynamic product ads, job boards, store locators). These macros resolve to the identifier of the specific catalog item being rendered at serve time — the same identifiers used in conversion event `content_ids` via the [`content_id_type`](/docs/creative/catalogs#conversion-events) field.

| Macro              | Description                      | Example Value               |
| ------------------ | -------------------------------- | --------------------------- |
| `{CATALOG_ID}`     | Buyer-defined catalog identifier | `gmc-primary`, `job-feed`   |
| `{SKU}`            | Product SKU identifier           | `SKU-12345`                 |
| `{GTIN}`           | Global Trade Item Number         | `00013000006040`            |
| `{OFFERING_ID}`    | AdCP offering identifier         | `summer-sale`               |
| `{JOB_ID}`         | Job posting identifier           | `vacancy-amsterdam-chef-42` |
| `{HOTEL_ID}`       | Hotel property identifier        | `grand-amsterdam`           |
| `{FLIGHT_ID}`      | Flight route identifier          | `AMS-BCN-2025-06`           |
| `{VEHICLE_ID}`     | Vehicle listing identifier       | `VIN-1234`                  |
| `{LISTING_ID}`     | Real estate listing identifier   | `prop-amsterdam-01`         |
| `{STORE_ID}`       | Store location identifier        | `amsterdam-flagship`        |
| `{PROGRAM_ID}`     | Education program identifier     | `mba-2025`                  |
| `{DESTINATION_ID}` | Travel destination identifier    | `barcelona`                 |

Use the macro that matches your catalog's `content_id_type`. For example, a product catalog with `content_id_type: "gtin"` uses `{GTIN}` in tracker URLs; a job catalog uses `{JOB_ID}`.

#### Catalog content macros

The macros above resolve to the catalog item's **identifier**. Catalog **content** macros resolve to a catalog item's scalar **field values**, for catalog-driven creative templates (`sponsored_placement` / DPA — Meta DPA, Snap Collection, TikTok Shopping). They are the substitute-the-value analog of the ID macros above: same single-brace family, same substitution-safety rules below, just more tokens.

| Macro                   | Description            | `catalog_field`  | Example Value                     |
| ----------------------- | ---------------------- | ---------------- | --------------------------------- |
| `{ITEM_NAME}`           | Item name/title        | `name`           | `Summer Sale`                     |
| `{ITEM_DESCRIPTION}`    | Item description       | `description`    | `Up to 50% off summer collection` |
| `{ITEM_TAGLINE}`        | Promotional tagline    | `tagline`        | `Start measuring today`           |
| `{ITEM_PRICE}`          | Price amount           | `price.amount`   | `29.99`                           |
| `{ITEM_PRICE_CURRENCY}` | ISO 4217 currency code | `price.currency` | `USD`                             |

Each token maps 1:1 to a documented catalog item field via the existing `catalog_field` dot-notation vocabulary used by [`field_bindings`](/docs/creative/catalogs#field-bindings) — content macros do **not** introduce a parallel field vocabulary. The five tokens are deliberately small and cross-vertical; vertical-specific deep fields (e.g. `star_rating`, `salary.min`) are served by `field_bindings` scalars, not universal content macros. URL-valued and image-valued fields (e.g. `landing_url`, image pools) bind via `field_bindings` to `url`/asset slots and are **not** content macros — substituting a whole URL as an entire `href` would also break the `encodeURIComponent`-equivalent percent-encoding contract below.

**Single-brace `{MACRO}` only.** `{{double-brace}}` is **not** an AdCP content-macro syntax and MUST NOT be used. Double-brace is reserved as one of the downstream ad-server macro syntaxes (`%%...%%`, `${...}`, `[...]`, `{{...}}`) that the sales agent MUST neutralize/percent-encode (see the nested-expansion rule below); adopting it for AdCP content macros would relax that guarantee and collide with downstream ad servers that interpret `{{...}}` natively.

Which catalog items render is **seller-declared** via the `fanout_mode` enum on the [`sponsored_placement`](/docs/creative/canonical-formats) format (`single_item` / `per_item` / `multi_item_in_creative`) — there is no buyer-side selection field. On ML-optimized DPA surfaces (Meta Advantage+, TikTok Shopping) the platform commonly overrides buyer-authored overlay text, so content macros are a buyer-declared **hint** the seller MAY honor, not a guaranteed substitution.

#### Substitution safety (catalog-item macros)

Catalog-item macros are the one macro class where the value originates in **buyer-controlled data** (the catalog feed) and expands at impression time into **publisher-controlled contexts** (impression tracker URLs, click tracker URLs, VAST tracking-event URLs, AND landing / clickthrough URLs — the full set of [URL substitution targets](#overview) above). That flow is attacker-adjacent: a catalog value containing `&`, `#`, `?`, CR/LF, a stray URL fragment, or a Unicode bidi override can break out of the URL context, inject a Host-header via CRLF, or spoof audit-log rendering if substituted raw.

The following rules apply to all catalog-item macros listed above — both the ID macros (`{CATALOG_ID}`, `{SKU}`, `{GTIN}`, `{OFFERING_ID}`, `{JOB_ID}`, `{HOTEL_ID}`, `{FLIGHT_ID}`, `{VEHICLE_ID}`, `{LISTING_ID}`, `{STORE_ID}`, `{PROGRAM_ID}`, `{DESTINATION_ID}`) and the content macros (`{ITEM_NAME}`, `{ITEM_DESCRIPTION}`, `{ITEM_TAGLINE}`, `{ITEM_PRICE}`, `{ITEM_PRICE_CURRENCY}`):

* **Normalize to Unicode NFC before encoding.** Prior to percent-encoding, catalog-item values that are not already in Unicode Normalization Form C (NFC) MUST be normalized to NFC per Unicode Standard Annex #15. Sellers and buyers MAY send catalog values in any normalization form at `sync_catalogs` ingest (the catalog is stored as-supplied); the normalization to NFC is a step in the substitution pipeline immediately before percent-encoding, not a catalog-ingest requirement. Without this step, two implementations that both satisfy the unreserved-whitelist rule below produce different bytes for the same visual string — `café` (NFC: U+00E9) and `cafe\u0301` (NFD: U+0065 + combining U+0301) encode to `caf%C3%A9` vs `e%CC%81` respectively. NFC matches web-platform convention (WHATWG URL, HTML5 DOM, W3C Character Model). NFKC / NFKD are **not** acceptable substitutes — their compatibility folding silently mutates fullwidth/halfwidth variants and other visually-distinct glyphs that legitimately appear in Japanese/Korean retailer catalogs.
* **Percent-encode every octet that is not in the RFC 3986 `unreserved` set.** Sales agents MUST percent-encode the NFC-normalized catalog-item value such that only RFC 3986 `unreserved` characters (`ALPHA / DIGIT / "-" / "." / "_" / "~"`) remain unescaped before substituting it into a URL context (query string, path segment, or fragment). Non-ASCII octets MUST be percent-encoded after UTF-8 encoding per RFC 3986 §2.5. This is the `encodeURIComponent`-equivalent contract: reserved characters (`: / ? # [ ] @ ! $ & ' ( ) * + , ; =`) are escaped as one would expect, but so are CR (`%0D`), LF (`%0A`), space (`%20`), C0/C1 control characters, and Unicode bidi overrides — the broader enumeration closes CRLF-injection and bidi-spoofing vectors that a reserved-only rule would leave open. Encoding is applied exactly once at substitution time; downstream VAST players and ad servers firing the URL verbatim is the expected contract — they do not and MUST NOT re-decode before firing.
* **Nested macro expansion is prohibited.** A catalog-item value that itself contains text matching AdCP's `{MACRO_NAME}` syntax MUST NOT be re-expanded. Sales agents perform AdCP macro substitution in one pass: source placeholders are replaced with literal values, and those literal values are not re-scanned. A `{JOB_ID}` value of `vacancy-{DEVICE_ID}-42` produces the literal string `vacancy-%7BDEVICE_ID%7D-42` (after percent-encoding of the braces) in the emitted URL, not a second-round expansion. This rule binds AdCP's `{...}` syntax only; catalog-item values containing downstream ad-server macro syntaxes (`%%...%%`, `${...}`, `[...]`, `{{...}}`) remain the sales agent's responsibility to neutralize when targeting an ad server that would interpret them — percent-encoding per the rule above typically suffices, since `%`, `$`, `[`, `]`, and `{` all land outside the `unreserved` set.
* **Scope is URL contexts only.** These rules apply when a catalog-item macro is substituted into a URL context. When a catalog-item macro is substituted into an HTML-attribute context (for example, a banner template's `href` or `data-*` attribute rendered server-side), percent-encoding per this section does not by itself prevent attribute-context breakout; the renderer MUST additionally apply HTML-attribute escaping — the two encodings are layered, not alternatives, because the value must survive both the URL parser and the HTML attribute parser. AdCP's normative contract bounds to the URL-context case; publisher-side HTML-attribute handling is out of scope for this spec.

Non-catalog macros (`{MEDIA_BUY_ID}`, `{PACKAGE_ID}`, `{CREATIVE_ID}`, `{GEO}`, `{COUNTRY}`, `{DEVICE_TYPE}`, etc.) are populated from publisher- or ad-server-mediated state, not from buyer-supplied feed data. Their encoding contract is governed by the ad-server integration (OpenRTB, VAST), which percent-encodes by convention. Some macros in this class derive from attacker-spoofable inputs (`{USER_AGENT}`, `{REFERRER}`, `{PAGE_URL}`, `{DOMAIN}`, `{APP_BUNDLE}` come from request headers or page metadata); the OpenRTB / ad-server encoding convention is the control today. This spec's normative MUST deliberately scopes to the buyer-controlled catalog-item class — a narrower, verifiable contract than a universal canonicalization rule.

**Conformance fixture.** Reference test vectors pinning the encoding behavior — reserved-character breakout, nested-expansion literal preservation, CRLF injection, non-ASCII — are versioned at [`static/compliance/source/test-vectors/catalog-macro-substitution.json`](https://github.com/adcontextprotocol/adcp/blob/main/static/compliance/source/test-vectors/catalog-macro-substitution.json). Sales agents SHOULD validate their substitution code against these vectors before shipping.

### Creative Variant Macros

| Macro                   | Description                                 | Example Value            |
| ----------------------- | ------------------------------------------- | ------------------------ |
| `{CREATIVE_VARIANT_ID}` | Seller-assigned creative variant identifier | `variant_a`, `v2_mobile` |

> **Note**: Publisher-specific custom macros may be defined in individual creative format specifications as `extra supported macros`.

## Usage Examples

### Video Creative with Tracking

```json theme={null}
{
  "creative_id": "cr_video_30s",
  "format_id": {
    "agent_url": "https://creative.adcontextprotocol.org",
    "id": "video_30s_vast"
  },
  "assets": {
    "vast_xml": {
      "asset_type": "vast",
      "delivery_type": "inline",
      "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<VAST version=\"4.2\">\n  <Ad>\n    <InLine>\n      <Impression><![CDATA[https://track.brand.com/imp?buy={MEDIA_BUY_ID}&pkg={PACKAGE_ID}&cre={CREATIVE_ID}&device={DEVICE_ID}&domain={DOMAIN}&cb=[CACHEBUSTING]]]></Impression>\n      <Creatives>\n        <Creative>\n          <Linear>\n            <Duration>00:00:30</Duration>\n            <TrackingEvents>\n              <Tracking event=\"firstQuartile\"><![CDATA[https://track.brand.com/q1?buy={MEDIA_BUY_ID}&cb=[CACHEBUSTING]]]></Tracking>\n              <Tracking event=\"complete\"><![CDATA[https://track.brand.com/complete?buy={MEDIA_BUY_ID}&cb=[CACHEBUSTING]]]></Tracking>\n            </TrackingEvents>\n            <VideoClicks>\n              <ClickThrough><![CDATA[https://brand.com/spring?campaign={MEDIA_BUY_ID}]]></ClickThrough>\n            </VideoClicks>\n            <MediaFiles>\n              <MediaFile delivery=\"progressive\" type=\"video/mp4\" width=\"1920\" height=\"1080\">\n                <![CDATA[https://cdn.brand.com/videos/spring_30s.mp4]]>\n              </MediaFile>\n            </MediaFiles>\n          </Linear>\n        </Creative>\n      </Creatives>\n    </InLine>\n  </Ad>\n</VAST>",
      "vast_version": "4.2"
    }
  }
}
```

**Key Points**:

* Mix AdCP macros (`{MEDIA_BUY_ID}`) with VAST macros (`[CACHEBUSTING]`)
* AdCP macros use `{CURLY_BRACES}`
* VAST macros use `[SQUARE_BRACKETS]`
* Both work together seamlessly

### Display Creative with Tracking

```json theme={null}
{
  "creative_id": "cr_banner_300x250",
  "format_id": {
    "agent_url": "https://creative.adcontextprotocol.org",
    "id": "display_banner_300x250"
  },
  "assets": {
    "banner_image": {
      "url": "https://cdn.brand.com/banners/spring_300x250.jpg",
      "width": 300,
      "height": 250
    },
    "impression_pixel": {
      "url": "https://track.brand.com/imp?buy={MEDIA_BUY_ID}&pkg={PACKAGE_ID}&cre={CREATIVE_ID}&device={DEVICE_ID}&domain={DOMAIN}&cb={CACHEBUSTER}"
    },
    "landing_url": {
      "url": "https://brand.com/spring?campaign={MEDIA_BUY_ID}"
    }
  }
}
```

### Audio Creative with Tracking

```json theme={null}
{
  "creative_id": "cr_audio_30s",
  "format_id": {
    "agent_url": "https://creative.adcontextprotocol.org",
    "id": "audio_streaming_30s"
  },
  "assets": {
    "audio_file": {
      "url": "https://cdn.brand.com/audio/spring_30s.mp3",
      "duration_ms": 30000
    },
    "impression_tracker": {
      "url": "https://track.brand.com/imp?buy={MEDIA_BUY_ID}&pkg={PACKAGE_ID}&station={STATION_ID}&show={COLLECTION_NAME}&cb={CACHEBUSTER}"
    }
  }
}
```

### Catalog-Driven Creative with Item Tracking

```json theme={null}
{
  "creative_id": "cr_product_carousel",
  "format_id": {
    "agent_url": "https://creative.retailer.com/adcp",
    "id": "product_carousel"
  },
  "catalogs": [{
    "catalog_id": "gmc-primary",
    "type": "product",
    "content_id_type": "gtin",
    "tags": ["summer"]
  }],
  "assets": {
    "impression_pixel": {
      "url": "https://track.brand.com/imp?buy={MEDIA_BUY_ID}&catalog={CATALOG_ID}&item={GTIN}&cb={CACHEBUSTER}",
      "url_type": "tracker_pixel"
    },
    "click_tracker": {
      "url": "https://track.brand.com/click?buy={MEDIA_BUY_ID}&catalog={CATALOG_ID}&item={GTIN}",
      "url_type": "tracker_pixel"
    }
  }
}
```

**Key point**: `{GTIN}` resolves to the specific product's GTIN at serve time. For a carousel showing 5 products, each product impression/click fires with that product's identifier — enabling per-item attribution.

## Macro Availability by Inventory Type

Not all macros are available in all inventory types. Check format specifications to see which macros are supported.

**Important**: The columns below represent format types (Display, Video, etc.) which can run in different environments (app vs web). For example:

* Display ads in mobile apps have `DEVICE_ID` (✅\*), but display ads on web do not
* The ✅\* notation means "available in-app contexts only"
* Format type + inventory environment determine actual macro availability

| Macro Category        | Display | Video | Audio | Native | CTV/OTT | DOOH | Mobile App | Mobile Web | Desktop Web |
| --------------------- | ------- | ----- | ----- | ------ | ------- | ---- | ---------- | ---------- | ----------- |
| **Common**            | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{MEDIA_BUY_ID}`      | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{PACKAGE_ID}`        | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{CREATIVE_ID}`       | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{CACHEBUSTER}`       | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{TIMESTAMP}`         | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| **Privacy**           |         |       |       |        |         |      |            |            |             |
| `{GDPR}`              | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{GDPR_CONSENT}`      | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{US_PRIVACY}`        | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{GPP_STRING}`        | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{GPP_SID}`           | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{IP_ADDRESS}`        | ✅‡      | ✅‡    | ✅‡    | ✅‡     | ✅‡      | ❌    | ✅‡         | ✅‡         | ✅‡          |
| `{LIMIT_AD_TRACKING}` | ✅\*     | ✅\*   | ✅\*   | ✅\*    | ✅       | ❌    | ✅          | ❌          | ❌           |
| **Identity**          |         |       |       |        |         |      |            |            |             |
| `{DEVICE_ID}`         | ✅\*     | ✅\*   | ✅\*   | ✅\*    | ✅       | ❌    | ✅          | ❌          | ❌           |
| `{DEVICE_ID_TYPE}`    | ✅\*     | ✅\*   | ✅\*   | ✅\*    | ✅       | ❌    | ✅          | ❌          | ❌           |
| **Geographic**        |         |       |       |        |         |      |            |            |             |
| `{COUNTRY}`           | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{REGION}`            | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{CITY}`              | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{ZIP}`               | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{DMA}`               | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{LAT}/{LONG}`        | ✅†      | ❌     | ❌     | ✅†     | ❌       | ✅    | ✅†         | ❌          | ❌           |
| **Device**            |         |       |       |        |         |      |            |            |             |
| `{DEVICE_TYPE}`       | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{OS}`                | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{OS_VERSION}`        | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{APP_BUNDLE}`        | ✅\*     | ✅\*   | ✅\*   | ✅\*    | ✅       | ❌    | ✅          | ❌          | ❌           |
| `{USER_AGENT}`        | ✅       | ✅     | ✅     | ✅      | ✅       | ❌    | ❌          | ✅          | ✅           |
| **Web Context**       |         |       |       |        |         |      |            |            |             |
| `{DOMAIN}`            | ✅       | ✅     | ✅     | ✅      | ❌       | ❌    | ❌          | ✅          | ✅           |
| `{PAGE_URL}`          | ✅       | ✅     | ✅     | ✅      | ❌       | ❌    | ❌          | ✅          | ✅           |
| `{REFERRER}`          | ✅       | ✅     | ✅     | ✅      | ❌       | ❌    | ❌          | ✅          | ✅           |
| `{KEYWORDS}`          | ✅       | ✅     | ✅     | ✅      | ❌       | ❌    | ❌          | ✅          | ✅           |
| **Placement**         |         |       |       |        |         |      |            |            |             |
| `{PLACEMENT_ID}`      | ✅       | ✅     | ✅     | ✅      | ✅       | ✅    | ✅          | ✅          | ✅           |
| `{FOLD_POSITION}`     | ✅       | ❌     | ❌     | ✅      | ❌       | ❌    | ✅          | ✅          | ✅           |
| **Video Content**     |         |       |       |        |         |      |            |            |             |
| `{VIDEO_ID}`          | ❌       | ✅     | ❌     | ❌      | ✅       | ❌    | ❌          | ❌          | ❌           |
| `{VIDEO_CATEGORY}`    | ❌       | ✅     | ❌     | ❌      | ✅       | ❌    | ❌          | ❌          | ❌           |
| `{CONTENT_GENRE}`     | ❌       | ✅     | ✅     | ❌      | ✅       | ❌    | ❌          | ❌          | ❌           |
| **Video Ad Pods**     |         |       |       |        |         |      |            |            |             |
| `{POD_POSITION}`      | ❌       | ✅     | ❌     | ❌      | ✅       | ❌    | ❌          | ❌          | ❌           |
| `{POD_SIZE}`          | ❌       | ✅     | ❌     | ❌      | ✅       | ❌    | ❌          | ❌          | ❌           |
| `{AD_BREAK_ID}`       | ❌       | ✅     | ❌     | ❌      | ✅       | ❌    | ❌          | ❌          | ❌           |
| **Audio Content**     |         |       |       |        |         |      |            |            |             |
| `{STATION_ID}`        | ❌       | ❌     | ✅     | ❌      | ❌       | ❌    | ❌          | ❌          | ❌           |
| `{COLLECTION_NAME}`   | ❌       | ❌     | ✅     | ❌      | ❌       | ❌    | ❌          | ❌          | ❌           |
| **TMP Exposure**      |         |       |       |        |         |      |            |            |             |
| `{TMPX}`              | ✅       | ✅     | ✅     | ✅      | ✅       | ✅§   | ✅          | ✅          | ✅           |

**Legend**:

* ✅ = Available
* ❌ = Not available
* ✅\* = In-app only (not mobile web)
* ✅† = When location permission granted
* ✅‡ = Often restricted due to privacy regulations (may return empty or masked value)
* ✅§ = DOOH uses play-log-based reporting rather than pixel URLs

**Important Notes**:

* Privacy macros (`{LIMIT_AD_TRACKING}`, `{DEVICE_ID}`) may return empty values based on user privacy settings
* Geographic macros accuracy varies by publisher's data capabilities
* `{PLACEMENT_ID}` refers to the IAB Global Placement ID standard

## How Macros Work

### 1. Discovery

Query `list_creative_formats` to see which macros each format supports:

```json theme={null}
{
  "format_id": {
    "agent_url": "https://creative.adcontextprotocol.org",
    "id": "video_30s_vast"
  },
  "type": "video",
  "supported_macros": [
    {
      "macro": "{MEDIA_BUY_ID}",
      "category": "identity",
      "description": "AdCP media buy identifier",
      "required": false,
      "privacy_sensitive": false,
      "example_value": "mb_spring_2025"
    },
    {
      "macro": "{DEVICE_ID}",
      "category": "identity",
      "description": "Mobile advertising ID (IDFA/AAID)",
      "required": false,
      "privacy_sensitive": true,
      "example_value": "ABC-123-DEF-456"
    },
    {
      "macro": "{GDPR}",
      "category": "privacy",
      "description": "GDPR applicability flag",
      "required": true,
      "privacy_sensitive": false,
      "example_value": "1"
    }
  ],
  "vast_macros_supported": true
}
```

### 2. Include Macros in Creatives

Add macro placeholders in your tracking URLs using `{MACRO_NAME}` syntax:

```
https://track.brand.com/imp?campaign={MEDIA_BUY_ID}&device={DEVICE_ID}
```

### 3. Sales Agent Processing

When you create a media buy via `create_media_buy`, the sales agent:

1. **Replaces AdCP ID macros** with your actual IDs:
   * `{MEDIA_BUY_ID}` → `mb_spring_2025`
   * `{PACKAGE_ID}` → `pkg_ctv_prime`
   * `{CREATIVE_ID}` → `cr_video_30s`

2. **Translates platform macros** to their ad server's syntax:
   * `{CACHEBUSTER}` → `%%CACHEBUSTER%%` (GAM) or `{{timestamp}}` (Kevel)
   * `{DEVICE_ID}` → `%%ADVERTISING_IDENTIFIER_PLAIN%%` (GAM)
   * `{DOMAIN}` → `%%SITE%%` (GAM)

3. **Inserts click trackers** automatically into clickable elements

4. **Leaves VAST macros unchanged** (for video formats)

### 4. Impression Time

The publisher's ad server replaces remaining macros with actual values:

```
https://track.brand.com/imp?
  campaign=mb_spring_2025&
  device=ABC-123-DEF-456&
  cb=87654321
```

## Best Practices

### Use Macros Consistently

Include the same core set of macros across all your creatives:

```
?buy={MEDIA_BUY_ID}&pkg={PACKAGE_ID}&cre={CREATIVE_ID}&cb={CACHEBUSTER}
```

This makes your tracking data consistent and easier to analyze.

### Check Format Support

Always query `list_creative_formats` to see which macros are available. Not all formats support all macros.

### Combine VAST and AdCP Macros

For video, use both systems together:

* **VAST macros** `[CACHEBUSTING]`, `[TIMESTAMP]` - for standard video tracking
* **AdCP macros** `{MEDIA_BUY_ID}`, `{DEVICE_ID}` - for your campaign tracking

### Privacy Compliance

**Critical**: Always respect user privacy choices in your creative logic.

#### GDPR Compliance (EU Traffic)

For campaigns serving in the EU:

```javascript theme={null}
// Check consent before loading tracking
if (GDPR == 1) {
  if (GDPR_CONSENT && GDPR_CONSENT != '') {
    // User has consented - load tracking pixels
    loadTracking();
  } else {
    // No consent - skip tracking
    console.log('Tracking skipped - no GDPR consent');
  }
} else {
  // GDPR doesn't apply - load tracking
  loadTracking();
}
```

#### US Privacy / CCPA Compliance

For US traffic:

```javascript theme={null}
// Check US Privacy string
if (US_PRIVACY == '1YYN') {
  // User has opted out - don't sell personal info
  skipPersonalizedTracking();
} else {
  // Load normal tracking
  loadTracking();
}
```

#### Device-Level Privacy

Respect Limit Ad Tracking settings:

```javascript theme={null}
// Check if device ID is available
if (LIMIT_AD_TRACKING == 1 || DEVICE_ID == '' || DEVICE_ID == '00000000-0000-0000-0000-000000000000') {
  // User has limited tracking - use contextual attribution
  useContextualTracking();
} else {
  // Device ID available
  useDeviceTracking(DEVICE_ID);
}
```

#### Privacy Macro Behavior

**Empty Values**: Privacy-restricted macros return empty strings or zeros:

* `{DEVICE_ID}` → `""` or `00000000-0000-0000-0000-000000000000` when LAT enabled
* `{GDPR_CONSENT}` → `""` when no consent provided
* `{IP_ADDRESS}` → `""` or masked/truncated IP when privacy restricted

**Always test for empty values** before using privacy-sensitive macros.

### URL Encoding

No need to URL-encode macro placeholders. The ad server handles encoding of actual values automatically.

**Example**:

```
❌ WRONG: https://track.com/imp?device=%7BDEVICE_ID%7D
✅ CORRECT: https://track.com/imp?device={DEVICE_ID}
```

The ad server will URL-encode the actual value when replacing the macro.

### Template Syntax

AdCP macro-bearing URLs validate as [RFC 6570 URI templates (Level 1)](https://datatracker.ietf.org/doc/html/rfc6570#section-1.2) — simple `{var}` substitution only. Level 2–4 operators (`{+SKU}`, `{#SKU}`, `{.SKU}`, `{/SKU}`, `{;SKU}`, `{?SKU}`, `{&SKU}`) are **not** used by AdCP and must not appear in manifests. The ad server performs literal string replacement, not RFC 6570 expansion.

## Implementation Notes for Sales Agents

*This section is for AdCP implementers, not buyers.*

### Macro Translation Approach

Sales agents must translate universal macros to their ad server's native syntax. The recommended approach:

**Option 1: Hard-Code During Trafficking (MVP)**

* When creating ad server creatives, replace AdCP ID macros with actual values
* Translate platform macros to ad server syntax
* Creates one creative per line item but is simple and reliable

**Option 2: Dynamic Wrapper (Future)**

* Intercept ad calls and inject values dynamically
* More complex but avoids creative duplication

### Translation Examples

**Google Ad Manager**:

```javascript theme={null}
{
  '{CACHEBUSTER}': '%%CACHEBUSTER%%',
  '{DEVICE_ID}': '%%ADVERTISING_IDENTIFIER_PLAIN%%',
  '{DEVICE_ID_TYPE}': '%%ADVERTISING_IDENTIFIER_TYPE%%',
  '{DOMAIN}': '%%SITE%%',
  '{VIDEO_ID}': '%%VIDEO_ID%%'
}
```

**Kevel**:

```javascript theme={null}
{
  '{CACHEBUSTER}': '{{timestamp}}',
  '{DEVICE_ID}': '{{device.ifa}}',
  '{DEVICE_ID_TYPE}': '{{device.ifaType}}',
  '{DOMAIN}': '{{request.domain}}'
}
```

**Xandr Monetize**:

```javascript theme={null}
{
  '{CACHEBUSTER}': '${CACHEBUSTER}',
  '{DEVICE_ID}': '${DEVICE_APPLE_IDA}',  // or ${DEVICE_AAID}
  '{DOMAIN}': '${DOMAIN}'
}
```

### Click Tracker Insertion

Sales agents must automatically insert click tracking macros into clickable elements:

**Original creative**:

```html theme={null}
<a href="https://brand.com/product">Click here</a>
```

**After insertion (GAM)**:

```html theme={null}
<a href="%%CLICK_URL_UNESC%%https://brand.com/product">Click here</a>
```

### Mapping Storage

Store the mapping between AdCP IDs and ad server IDs for reconciliation:

```javascript theme={null}
{
  media_buy_id: "mb_spring_2025",
  ad_server_order_id: "1234567",
  packages: [
    {
      package_id: "pkg_ctv_prime",
      ad_server_line_item_id: "8901234"
    }
  ]
}
```

Return this in `create_media_buy` responses and make it queryable for reconciliation.

## Related Documentation

* [Creative Formats](/docs/creative/formats) - Understanding format specifications and discovery
* [Creative Protocol](/docs/creative) - How creatives work in AdCP
* [sync\_creatives](/docs/creative/task-reference/sync_creatives) - Creative management API
