Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/creative-delivery-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": minor
---

Add explicit creative delivery contracts for inline display tags, atomic paired redirects, and late-bound equivalent source representations. Define exact VAST asset versions versus product and seller acceptance sets, plus declared macro dialect, resolver ownership, encoding depth, capability matching, and per-token validation results. Add deterministic selection lineage, structured rejection errors, documentation, and conformance vectors for issues #6761–#6764.
32 changes: 24 additions & 8 deletions dist/schemas/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@
"$id": "https://adcontextprotocol.org/schemas/index.json",
"title": "AdCP Schema Discovery",
"description": "Root discovery document for file-based AdCP schema consumers. Use latest_stable or a major/minor alias target instead of choosing by directory listing.",
"latest": "3.1.18",
"latest_stable": "3.1.18",
"latest": "3.1.19",
"latest_stable": "3.1.19",
"channel": "stable",
"aliases": {
"v3": "3.1.18",
"v3.1": "3.1.18",
"v3.0": "3.0.25",
"v3": "3.1.19",
"v3.1": "3.1.19",
"v3.0": "3.0.26",
"v2": "2.5.3",
"v2.5": "2.5.3"
},
"latest_by_major": {
"2": "2.5.3",
"3": "3.1.18"
"3": "3.1.19"
},
"latest_by_minor": {
"3.1": "3.1.18",
"3.0": "3.0.25",
"3.1": "3.1.19",
"3.0": "3.0.26",
"2.5": "2.5.3"
},
"versions": [
Expand Down Expand Up @@ -63,6 +63,14 @@
"path": "/schemas/3.2.0-beta.0/",
"index": "/schemas/3.2.0-beta.0/index.json"
},
{
"version": "3.1.19",
"stability": "stable",
"prerelease": false,
"deprecated": false,
"path": "/schemas/3.1.19/",
"index": "/schemas/3.1.19/index.json"
},
{
"version": "3.1.18",
"stability": "stable",
Expand Down Expand Up @@ -423,6 +431,14 @@
"path": "/schemas/3.1.0-beta.0/",
"index": "/schemas/3.1.0-beta.0/index.json"
},
{
"version": "3.0.26",
"stability": "stable",
"prerelease": false,
"deprecated": false,
"path": "/schemas/3.0.26/",
"index": "/schemas/3.0.26/index.json"
},
{
"version": "3.0.25",
"stability": "stable",
Expand Down
8 changes: 4 additions & 4 deletions dist/schemas/latest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"latest": "3.1.18",
"latest_stable": "3.1.18",
"latest": "3.1.19",
"latest_stable": "3.1.19",
"channel": "stable",
"path": "/schemas/3.1.18/",
"index": "/schemas/3.1.18/index.json"
"path": "/schemas/3.1.19/",
"index": "/schemas/3.1.19/index.json"
}
2 changes: 1 addition & 1 deletion docs/contributing/x-entity-annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ High-level groupings (see the registry for full descriptions). *Categories below
| Brand & rights | `advertiser_brand`, `rights_holder_brand`, `rights_grant` |
| Account & party | `account`, `operator` |
| Media buy | `media_buy`, `package`, `product`, `proposal`, `product_pricing_option`, `spot_airing` |
| Creative | `creative`, `creative_format` |
| Creative | `creative`, `creative_source_revision`, `creative_delivery_variant`, `creative_locale_variant`, `creative_format`, `build_variant` |
| Data & targeting | `audience`, `audience_evidence`, `audience_evidence_snapshot`, `signal`, `signal_activation_id`, `demographic_interval_id`, `event_source` |
| Lists & catalogs | `collection_list`, `property_list`, `catalog`, `catalog_generation`, `catalog_item`, `property` |
| Plans & governance | `media_plan`, `governance_plan`, `governance_registry_policy`, `governance_inline_policy`, `governance_check`, `governance_delivery_statement`, `governance_delivery_observation`, `governance_outcome`, `governance_adjustment`, `governance_adjustment_evidence`, `seller_adjustment`, `content_standards`, `task`, `attestation_credential` |
Expand Down
22 changes: 20 additions & 2 deletions docs/creative/asset-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ Senders **SHOULD** include `url_type` on every URL asset. The valid values are:
| Value | Mechanism |
|---|---|
| `clickthrough` | User-click destination (landing page or ad-tech redirector) |
| `ad_request` | Invokes a third-party display creative endpoint at impression time |
| `tracker_pixel` | Fires HTTP GET, expects 1×1 pixel or 204 response (impression / event / 3P trackers) |
| `tracker_script` | Loads as a `<script>` tag — measurement SDKs (OMID verification, native event trackers using `method: 2`) |

Expand All @@ -197,6 +198,8 @@ If `url_type` is **absent**, receivers SHOULD fall back to the format's `url-ass
| `viewability_tracker` | `tracker_script` — OMID and equivalent verification SDKs **require** a `<script>` tag; firing them as a pixel produces no measurement |
| `third_party_tracker` | **No safe fallback.** Mechanism is integration-specific (DV/IAS ship both pixel and script forms). Receivers MAY reject or warn. |

Display ad-request URLs also have no role-based fallback. Declare `url_type: "ad_request"` explicitly.

If neither `url_type` nor a format-side `role` is available, receivers MUST NOT silently pick a mechanism — firing a clickthrough URL as a pixel (or vice versa) silently corrupts measurement and breaks the user flow. Receivers SHOULD reject the manifest.

> **VAST/DAAST URLs are not URL assets.** A URL pointing to ad-server XML markup is parsed, not GET-fired. Use `asset_type: "vast"` with `delivery_type: "url"` for VAST tags, and the dedicated tracker types (`vast_tracker` / `daast_tracker`) once those land per [RFC #2915](https://github.com/adcontextprotocol/adcp/issues/2915). A VAST tag declared as `asset_type: "url"` with `url_type: "tracker_pixel"` is non-conformant under these mechanism semantics.
Expand Down Expand Up @@ -254,7 +257,7 @@ Audio files for audio ads and podcasts.

### HTML Asset

HTML5 creative assets for rich media formats and third-party display tags.
Inline HTML content assets. Seller-hosted HTML5 creative bundles use a `zip` asset. Third-party display markup uses the `display_tag` asset below.

```json
{
Expand All @@ -276,6 +279,21 @@ HTML5 creative assets for rich media formats and third-party display tags.
- `allowed_features`: Allowed HTML5 features
- `restricted_features`: Features that must not be used

### Display Tag Asset

An atomic third-party display representation. `delivery_type` is one of `tag_url`, `inline_markup`, or `paired_redirect`. The paired form requires both `ad_request_url` and `clickthrough_url`; they cannot be trafficked or revised independently.

```json
{
"asset_type": "display_tag",
"delivery_type": "paired_redirect",
"ad_request_url": "https://ads.acme-example.com/ad?slot=42",
"clickthrough_url": "https://click.acme-example.com/redirect"
}
```

Inline markup also records `markup_type` as `iframe_javascript`, `javascript`, or `standard`. Preserve markup bytes exactly. Do not classify it as `html5`, which means a seller-hosted zip bundle.

### VAST Asset

VAST (Video Ad Serving Template) tags for third-party video ad serving.
Expand Down Expand Up @@ -308,7 +326,7 @@ VAST (Video Ad Serving Template) tags for third-party video ad serving.
- `delivery_type`: "url" or "inline" (required discriminator)
- `url`: URL endpoint that returns VAST XML (required when delivery_type is "url")
- `content`: Inline VAST XML content (required when delivery_type is "inline")
- `vast_version`: VAST specification version (2.0, 3.0, 4.0, 4.1, 4.2, 4.3)
- `vast_version`: Exact VAST specification version supplied by this asset (2.0, 3.0, 4.0, 4.1, 4.2, 4.3). Product format options use plural `vast_versions` for their accepted set.
- `vpaid_enabled`: Whether VPAID (Video Player-Ad Interface Definition) is supported
- `max_wrapper_depth`: Maximum allowed wrapper/redirect depth
- `duration_ms`: Expected video duration in milliseconds (if known)
Expand Down
2 changes: 1 addition & 1 deletion docs/creative/canonical-formats.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Downstream platform authorization is separate from production source. If a forma

When `asset_source` is `buyer_uploaded`, the buyer ships rendered assets and any tracker URLs attached to those assets are buyer-controlled (universal_macros for impression/click; `vast_tracker` / `daast_tracker` assets for decomposed VAST/DAAST trackers). When `asset_source` is any of the seller-rendered values (`seller_pre_rendered_from_brief`, `seller_human_designed`, `agent_synthesized`) or `publisher_host_recorded`, the buyer never sees the rendered artifact directly. Two normative paths apply:

- **Macro-substituted tracking (default).** The seller honors AdCP universal_macros at impression time — `{IMPRESSION_TRACKER}`, `{CLICK_TRACKER}`, etc. — and substitutes buyer-supplied tracker URLs (declared on the manifest's optional `landing_page_url` and the buyer's measurement-vendor pixels declared via `platform_extensions` on the format, filtered by `extensions[uri].extends === "tracking"`) into the rendered creative's serving template. The buyer registers their measurement pixels client-side; the seller calls them at serve time. This is the dominant path for image / video / audio production where serving and tracking are decoupled.
- **Macro-substituted tracking (default).** The seller honors declared AdCP universal macros such as `{IMPRESSION_ID}` and `{CACHEBUSTER}` inside buyer-supplied tracker URLs at impression time. Tracker URLs are explicit `pixel_tracker`, `vast_tracker`, or `daast_tracker` assets (or governed tracking extensions); `landing_page_url` remains the navigated destination, not a tracker. The seller fires honored trackers at serve time. This is the dominant path for image / video / audio production where serving and tracking are decoupled.
- **Sync-creatives tracker block.** For products where the seller produces a serving artifact that embeds tracker URLs directly (e.g., a generated VAST tag or a stitched companion banner), the seller's `sync_creatives` response SHOULD include a `tracker_block` field listing the impression URL pattern and click URL pattern. Buyers register those with their measurement vendor at sync time. This path covers the generative-DSP pattern where the serving artifact and the tracking shape are produced together.

`vast_tracker` and `daast_tracker` decomposed tracker assets work for both `buyer_uploaded` and seller-rendered sources — when the seller renders, those tracker assets are inputs to the rendered tag, attached to the appropriate VAST/DAAST `<TrackingEvents>` block at production time. When the buyer ships a complete `vast` or `daast` tag, the trackers travel inside the tag.
Expand Down
78 changes: 74 additions & 4 deletions docs/creative/channels/display.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,92 @@ Use min/max width and height for a genuinely responsive envelope. Exact-size, mu

## HTML5 and third-party tags

`html5` describes a buyer-supplied HTML bundle. `display_tag` describes externally served HTML or JavaScript markup. Keep them separate because validation, hosting, security, and measurement differ.
`html5` describes a buyer-supplied zip bundle that the seller hosts. `display_tag` describes a third-party-served representation. Keep them separate because validation, hosting, security, and measurement differ. Inline third-party markup is never an `html5` bundle.

Display tags have three delivery variants:

| Variant | Asset contract |
|---|---|
| `tag_url` | One `url` asset with `url_type: "ad_request"`, or a `display_tag` asset with `delivery_type: "tag_url"` |
| `inline_markup` | One `display_tag` asset with byte-preserved markup and its markup type |
| `paired_redirect` | One `display_tag` asset containing both `ad_request_url` and `clickthrough_url` |

The existing `tag_url` slot remains the backward-compatible default. A seller accepting inline or paired delivery publishes an explicit option with a `display_tag` slot.

### Inline iframe/JavaScript

```json
{
"format_option_id": "inline_iframe_javascript_300x250",
"format_kind": "display_tag",
"params": {
"width": 300,
"height": 250,
"supported_delivery_variants": ["inline_markup"],
"slots": [
{ "asset_group_id": "serving_tag", "asset_type": "html", "required": true },
{ "asset_group_id": "impression_tracker", "asset_type": "pixel_tracker", "required": false }
{ "asset_group_id": "serving_tag", "asset_type": "display_tag", "required": true }
]
}
}
```

```json
{
"format_kind": "display_tag",
"format_option_ref": {
"scope": "product",
"format_option_id": "inline_iframe_javascript_300x250"
},
"assets": {
"serving_tag": {
"asset_type": "display_tag",
"delivery_type": "inline_markup",
"markup_type": "iframe_javascript",
"markup": "<iframe src=\"https://ads.acme-example.com/render?cb=%%CACHEBUSTER%%\"></iframe>"
}
}
}
```

### Internal Redirect

A two-URL internal redirect is represented portably as `paired_redirect`; the canonical kind remains `display_tag`. The asset is atomic so its two URLs cannot be mixed across rows or revisions.

```json
{
"format_option_id": "gam_internal_redirect",
"format_kind": "display_tag",
"params": {
"width": 300,
"height": 250,
"supported_delivery_variants": ["paired_redirect"],
"slots": [
{ "asset_group_id": "serving_tag", "asset_type": "display_tag", "required": true }
]
}
}
```

```json
{
"format_kind": "display_tag",
"format_option_ref": {
"scope": "product",
"format_option_id": "gam_internal_redirect"
},
"assets": {
"serving_tag": {
"asset_type": "display_tag",
"delivery_type": "paired_redirect",
"ad_request_url": "https://ads.acme-example.com/ad?slot=42&cb=%%CACHEBUSTER%%",
"clickthrough_url": "https://click.acme-example.com/redirect?dest=%%DEST_URL_ESC%%"
}
}
}
```

Both fields are required by schema. A half-present pair is invalid. Do not split the pair into independent URL slots.

Buyers discover seller acceptance from `Product.format_options[]`. Creative agents that can build or validate these contracts advertise matching `creative.supported_formats[]` entries.

See [Canonical formats](/docs/creative/canonical-formats), [Creative manifests](/docs/creative/creative-manifests), and [Universal macros](/docs/creative/universal-macros).
See [Creative delivery variants](/docs/creative/delivery-variants), [Canonical formats](/docs/creative/canonical-formats), [Creative manifests](/docs/creative/creative-manifests), and [Universal macros](/docs/creative/universal-macros).
26 changes: 20 additions & 6 deletions docs/creative/channels/video.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,23 @@ AdCP separates hosted video bytes (`video_hosted`) from VAST delivery (`video_va
}
```

Use a `vast` asset with either a URL or inline XML according to the selected declaration. The seller validates wrapper depth, media files, duration, macros, and supported VAST features.
Use a `vast` asset with either a URL or inline XML according to the selected declaration. The asset's singular `vast_version` is the exact version it supplies. The format option's plural `vast_versions` is the accepted set for that product/player path; seller-wide `execution.creative_specs.vast_versions` is only a ceiling.

Version compatibility is the intersection:

```text
asset.vast_version
∈ product.format_option.params.vast_versions
∩ seller.execution.creative_specs.vast_versions
```

For example, a seller may advertise a platform ceiling through VAST 4.2 while one product accepts only VAST 3.0 and 4.0. The product set wins for that product. Never relabel an asset to a newer version merely because the destination supports it.

For ordinary legacy VAST submission, an omitted seller or product version set retains 3.x compatibility and document validation follows the peer's negotiated behavior. Late-bound `CreativeSource` resolution is stricter: every VAST candidate declares `vast_version`, and both the selected product option and seller must advertise non-empty plural sets. Absence means compatibility is unproven, not “all versions.” During migration, producers emit either deprecated singular `vast_version` for older consumers or plural `vast_versions` for 3.2 consumers; the two fields are mutually exclusive so they cannot contradict one another.

### VAST Validation

Format-layer validation of `vast` assets is structural: the manifest must satisfy the format's `requirements` (today, `vast_version`), and [`validate_input`](/docs/creative/canonical-formats#validation-flow--validate_input) checks manifest shape only. The VAST document itself is opaque at that level, so a structurally valid manifest can still carry an unplayable tag that fails silently at serve time.
Format-layer validation of `vast` assets is structural: the manifest must satisfy the format's plural accepted `vast_versions` set, and [`validate_input`](/docs/creative/canonical-formats#validation-flow--validate_input) checks manifest shape and declared compatibility. The VAST document itself is opaque at that level, so a structurally valid manifest can still carry an unplayable tag that fails at serve time.

AdCP defines three validation levels for `vast` assets. Sellers declare the level they perform via `creative_specs.vast_validation` in [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities); absent means `structural`. Validation runs at `sync_creatives` time, including `dry_run`. `validate_input` remains manifest-structure-only at every level.

Expand All @@ -76,10 +88,12 @@ AdCP defines three validation levels for `vast` assets. Sellers declare the leve

- MUST parse the document (inline `content`, or one response fetched from `url`) as XML and reject unparseable documents with `VAST_PARSE_FAILED` (`error.details.reason: "not_xml"`).
- MUST reject documents whose root element is not `<VAST>` with `VAST_PARSE_FAILED` (`error.details.reason: "no_vast_root"`).
- MUST reject documents whose `<VAST version>` attribute is missing or does not match the asset's declared `vast_version`, the format's `vast_version` requirement, or the seller's declared `creative_specs.vast_versions`, with `VAST_VERSION_MISMATCH` (`error.details.supported_versions` lists the accepted versions).
- MUST reject documents whose `<VAST version>` attribute is missing or differs from the asset's singular `vast_version`.
- MUST reject an asset whose `vast_version` is outside the product `params.vast_versions` and seller `creative_specs.vast_versions` intersection with `VAST_VERSION_MISMATCH`.
- MUST include `asset_vast_version`, `product_vast_versions`, `seller_vast_versions`, and the selected `format_option_ref` in `error.details` for `VAST_VERSION_MISMATCH`.
- SHOULD reject documents containing no `<Ad>` element, and `<InLine>` linear creatives carrying no `<MediaFile>`, with `VAST_PARSE_FAILED` (`error.details.reason: "no_ad"` / `"no_media_file"`).
- SHOULD verify that media, tracking, and click URLs use HTTPS.
- MUST treat unresolved ad-server macros in URLs (`[MACRO]`, `${MACRO}`, `{UNIVERSAL_MACRO}`) as opaque tokens, never as validation failures. Macro correctness and substitution verification are out of scope for this contract.
- MUST preserve undeclared macro tokens as opaque bytes. When an asset supplies `macro_declarations`, validate each token against the product and seller macro-resolution capability intersection and report per-token results.

**Wrapper-chain validation.** Sellers declaring `wrapper` additionally:

Expand All @@ -95,11 +109,11 @@ AdCP defines three validation levels for `vast` assets. Sellers declare the leve
| Code | Meaning |
|------|---------|
| `VAST_PARSE_FAILED` | Document is not well-formed XML, has no `<VAST>` root, no `<Ad>`, or an inline linear creative has no `<MediaFile>` |
| `VAST_VERSION_MISMATCH` | Document's `<VAST version>` disagrees with the declared `vast_version`, the format requirement, or the seller's supported versions |
| `VAST_VERSION_MISMATCH` | Document version differs from the asset declaration, or the asset version is outside the product/seller intersection |
| `VAST_WRAPPER_DEPTH_EXCEEDED` | Wrapper chain exceeds `max_wrapper_depth`, loops, or a hop timed out |

## Vertical and flexible video

Vertical short video is still `video_hosted`; use width/height or aspect-ratio parameters and, for an exact publisher contract, `{publisher_domain, format_option_id}`. Equal canonical kind alone is not proof that a generic video builder satisfies a publisher's duration, codec, slot, or composition constraints.

See [CTV](/docs/creative/channels/ctv), [Canonical formats](/docs/creative/canonical-formats), and [Creative manifests](/docs/creative/creative-manifests).
See [Creative delivery variants](/docs/creative/delivery-variants), [CTV](/docs/creative/channels/ctv), [Canonical formats](/docs/creative/canonical-formats), and [Creative manifests](/docs/creative/creative-manifests).
Loading
Loading