Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions .changeset/creative-wording-errata-cta-carousel-filters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"adcontextprotocol": minor
---

Creative wording errata from the maintainer answers on adcp#8628, adcp#8629, adcp#8630 and adcp#8631. Descriptions, one schema `default` and docs only; no new fields or codes. Minor because the `asset_types` filter on `list_creative_formats` changes from any-of to all-of, which changes the results an any-of seller returned before; that makes it ineligible for a 3.2.x patch backport.

- `CREATIVE_VALUE_NOT_ALLOWED` (adcp#8628): the closed set is `cta_values` on canonical formats or `allowed_values` in text-asset requirements. The code also covers a card's per-card `cta`, with `error.field` pointing at the card. Sellers carry the declared set in `error.details.accepted_values`, the canonical key in `core/error.json`; also sending `allowed_values` is harmless but not needed. The `text-asset-requirements` and `card-asset` descriptions, `sync_creatives` and the error-code snippet say the same. The Meta Reels table now says the buyer ships `assets.cta.content`, not `assets.cta.text`.
- `image_carousel` (adcp#8629): `allowed_card_media_asset_types` declares `"default": ["image"]`, so a declaration that omits both it and the deprecated `allowed_card_asset_types` alias accepts image cards only; declare `["image", "video"]` for mixed-media carousels. `min_cards` and `max_cards` only narrow the `cards` slot: `max_cards: 35` alone still caps at the default 10, and a product taking 35 cards restates the slot with `max: 35`. The `responsive_creative` pool counts narrow their slots the same way. The format-conformance text notes that a parameter whose canonical schema states a `default` applies it when omitted.
- `list_creative_formats` `asset_types` (adcp#8630, media-buy and creative request schemas): a format matches when it declares every requested type, whether each asset is required or optional and whether it sits at the top level or inside a `repeatable_group`. `list_creatives` keeps its any-of rule over creatives.
- Multi-publisher products without `placements[]` (adcp#8631): `Product.format_options` now states that such a product MUST NOT carry publisher-scoped declarations, and that when no product-local declaration remains the seller MUST publish `placements[]` or split the product by publisher. The intersection is taken by declaration identity. The per-slice rule in `formats.mdx`, `canonical-formats.mdx` and `media-products.mdx` now describes how a buyer grades a product that breaks this rule.
12 changes: 6 additions & 6 deletions docs/creative/canonical-formats.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ Add a new `format_kind` only when the buyer-facing asset contract or bounded ren

## Declaring a different slot contract with `params.slots`

Canonical product declarations publish their complete asset-slot contract in `format_options[].params.slots`. Each entry declares an `asset_group_id`, an `asset_type`, and any requiredness, count, or technical constraints. A supplied list replaces the default slot set rather than adding to it; include every slot the buyer must or may supply. `slots_override` belongs only to a legacy catalog's `canonical:` projection annotation and is translated to `params.slots` when projecting that catalog. It is not a product-authoring field.
Canonical product declarations publish their complete asset-slot contract in `format_options[].params.slots`. Each entry declares an `asset_group_id`, an `asset_type`, and any requiredness, count, or technical constraints. A supplied list replaces the default slot set rather than adding to it; include every slot the buyer must or may supply. Flat count parameters such as image_carousel `max_cards` or responsive_creative `headlines_max` only narrow a slot's `min` / `max`. To accept more than a default slot allows (35 carousel cards against the default `max: 10`), restate that slot here with the higher `max`. `slots_override` belongs only to a legacy catalog's `canonical:` projection annotation and is translated to `params.slots` when projecting that catalog. It is not a product-authoring field.

**Decision rule.** Would a buyer composing a creative manifest list **different assets** in this case vs the canonical's default? If yes, declare that different contract in `params.slots`. If no, use the canonical slots and narrow their applicable parameters. A slot type must be materializable as an asset of that type: use `card` for carousel cards and `video_brief` for a structured video brief, rather than an untyped `object`.

Expand Down Expand Up @@ -544,7 +544,7 @@ Meta Reels is a useful test of canonical-formats coverage: a platform-specific f
| --- | --- | --- |
| Vertical 9:16, 3-90s, 1080×1920, h264/aac, mp4 | `params` on `video_hosted` | Standard canonical parameters. |
| Headline (40ch), Primary text (125ch) | `params.headline_max_chars`, `params.primary_text_max_chars` + inherited `headline` / `primary_text` slots | Buyer ships text content via the slots; the param narrows the constraint. |
| CTA from a fixed enum | `params.cta_values[]` + inherited `cta` slot | Buyer ships `assets.cta.text`; validator checks against the enum. |
| CTA from a fixed enum | `params.cta_values[]` + inherited `cta` slot | Buyer ships `assets.cta.content`; validator checks against the enum. |
| Landing page URL | Inherited `landing_page_url` slot on `video_hosted` | No special handling. |
| Brand name overlay | NOT in the format. Auto-applied by Meta from the linked Page (auth context outside AdCP). | The inherited `brand_name` slot exists in the canonical; products that need explicit content (publisher-direct video, CTV) populate it. Meta doesn't consume it for Reels. |
| Logo overlay | NOT in the format. Auto-applied by Meta from the linked Page. | `BrandRef.brand_kit_override.logo` exists for formats whose seller-side renderer overlays a logo (host-read podcasts, CTV bumpers, publisher direct). Meta doesn't use it. |
Expand Down Expand Up @@ -589,7 +589,7 @@ Completeness governs what an option leaves undeclared. It never weakens what the

- A declared parameter is the whole constraint for its slot. A seller MUST NOT refuse, with a conformance code, a creative on the grounds of a declared parameter that the creative satisfies, and MUST NOT enforce a narrower value. An option declaring `main_image_sizes: [{ "width": 600, "height": 400 }]` accepts a 600×400 `main_image`.
- A closed set narrows a slot only when declared. With no `cta_values`, the `cta` slot takes free text within any declared `cta_max_chars`, and `CREATIVE_VALUE_NOT_ALLOWED` does not apply.
- Any other narrowing parameter the option omits sets no limit through that parameter. Under `technical_requirements_complete: true` the seller holds no undeclared limit either, so a creative that conforms to every parameter is not refused with a conformance code. Without the assertion, the omitted constraint is undeclared as described above.
- Any other narrowing parameter the option omits sets no limit through that parameter, unless its canonical schema states a `default`: that default applies. A carousel option that omits `allowed_card_media_asset_types` accepts image cards only (`default: ["image"]`). Under `technical_requirements_complete: true` the seller holds no undeclared limit either, so a creative that conforms to every parameter is not refused with a conformance code. Without the assertion, the omitted constraint is undeclared as described above.

The task-level rule, including how sellers code refusals outside format conformance, is in [`sync_creatives`: format conformance](/docs/creative/task-reference/sync_creatives#format-conformance).

Expand All @@ -609,9 +609,9 @@ Classification follows the buyer deliverable and who renders it, not the behavio

A publisher declares the format contract and enough production semantics for a buyer to know what it must supply: canonical kind, parameter constraints, slots, asset source, composition model, and required connections. It does **not** maintain a vendor list of every creative agent capable of producing that contract. Creative agents self-declare compatibility.

For multi-publisher products, `Product.format_options[]` is the union of contracts the sales agent can traffic somewhere in the product. Eligibility is then computed per placement by intersecting that product set with the applicable publisher/placement declaration. One creative used across every selected placement must satisfy the intersection of all those effective sets; workflows that assign distinct creatives per placement may use the union, but must cover every placement.
For multi-publisher products that publish `placements[]`, `Product.format_options[]` is the union of contracts the sales agent can traffic somewhere in the product. Eligibility is then computed per placement by intersecting that product set with the applicable publisher/placement declaration. One creative used across every selected placement must satisfy the intersection of all those effective sets; workflows that assign distinct creatives per placement may use the union, but must cover every placement.

If the product has no explicit `placements[]`, treat each `publisher_properties[]` selector as an eligibility slice. Product-local options apply to every slice; publisher-scoped options apply only to slices with the matching `publisher_domain`. A single creative must satisfy the intersection across slices, while distinct creatives must cover every slice. If any slice cannot be resolved from the product and publisher catalog, fail closed and require explicit placements or a refined product.
A multi-publisher product with no explicit `placements[]` has no public routing key for per-publisher creatives. Its `format_options[]` MUST be the common intersection across every publisher, taken by declaration identity, so it MUST NOT carry publisher-scoped options; when no product-local option remains, the seller MUST publish `placements[]` or split the product by publisher (see [matching and satisfaction](/docs/creative/formats#matching-and-satisfaction)). When grading a product that breaks this rule, treat each `publisher_properties[]` selector as an eligibility slice. Product-local options apply to every slice; publisher-scoped options apply only to slices with the matching `publisher_domain`. A single creative must satisfy the intersection across slices, while distinct creatives must cover every slice. If any slice cannot be resolved from the product and publisher catalog, fail closed and require explicit placements or a refined product.

For exact publisher-format support, a creative capability's `format` repeats the publisher declaration's `{publisher_domain, format_option_id}` identity and narrows the same canonical contract. Generic creative agents advertise a canonical parameter envelope without publisher identity. A generic capability matches only when it can satisfy every constraint on the target declaration; equal `format_kind` values alone are not proof of compatibility.

Expand Down Expand Up @@ -1378,7 +1378,7 @@ Pinterest is the canonical disambiguation example because a single platform sell
|---|---|---|
| **Promoted Pin** (sponsored single Pin in the home/search feed) | `native_in_feed` | Asset-bundle composition — buyer ships title + image + body + landing URL; Pinterest's renderer assembles the Pin to match feed look-and-feel. No catalog feed; the buyer-supplied bundle IS the creative. |
| **Pinterest Collection** (single hero image + 3 product thumbnails sourced from a catalog) | `sponsored_placement` | Catalog-keyed — buyer ships a `source_catalog` reference (and an optional `hero_asset`); Pinterest composes the per-item thumbnails by reading product catalog rows. `fanout_mode: multi_item_in_creative` distinguishes Collection from per-item retail-media SP. |
| **Pinterest Idea Pin** (multi-page swipeable native unit; pages may be image or video) | `image_carousel` | Multi-card swipe shape — the carousel canonical's per-card slot is polymorphic image-or-video, which matches Idea Pin pages that mix the two within one Pin. |
| **Pinterest Idea Pin** (multi-page swipeable native unit; pages may be image or video) | `image_carousel` | Multi-card swipe shape — the carousel canonical's per-card slot is polymorphic image-or-video, which matches Idea Pin pages that mix the two within one Pin. The declaration sets `allowed_card_media_asset_types: ["image", "video"]`, since the default is image only. |
| **Pinterest Shopping Pin** (single product Pin keyed to a catalog row) | `sponsored_placement` with `fanout_mode: single_item` | Single rendered creative pulled from one catalog item — still catalog-keyed composition, just one item per ad. |

The cleave is **asset-bundle vs catalog-row composition**, not "is it Pinterest." Same logic applies to Snap Story Ad (native_in_feed) vs Snap Collection (sponsored_placement), TikTok TopView (native_in_feed via `applies_to_channels: ["social"]`) vs TikTok Collection (sponsored_placement), and so on. Buyer agents route on the composition shape; the publisher's brand of the surface is incidental.
Expand Down
21 changes: 21 additions & 0 deletions docs/creative/channels/carousels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@ Use `image_carousel` when one creative contains an ordered set of cards. Card co
}
```

## Card media and card count

- **Card media.** `allowed_card_media_asset_types` lists the asset types a card's `media` may carry. It defaults to `["image"]`: a declaration that omits both it and the deprecated `allowed_card_asset_types` alias accepts image cards only. Declare `["image", "video"]` for mixed-media carousels.
- **Card count.** The `cards` slot's `min` and `max` set the count (2 and 10 in the default slots). `min_cards` and `max_cards` only narrow them: the effective minimum is the larger value and the effective maximum the smaller one. `max_cards: 35` on its own still caps at 10. A product that takes 35 cards restates the `cards` slot with `max: 35`, and may also set `max_cards: 35`. A restated `slots` list replaces the whole default slot set (see [declaring a different slot contract](/docs/creative/canonical-formats#declaring-a-different-slot-contract-with-params-slots)), so it lists every slot the buyer may supply.

```json
{
"format_option_id": "long_carousel",
"format_kind": "image_carousel",
"params": {
"max_cards": 35,
"allowed_card_media_asset_types": ["image", "video"],
"slots": [
{ "asset_group_id": "cards", "asset_type": "card", "required": true, "min": 2, "max": 35 },
{ "asset_group_id": "primary_text", "asset_type": "text", "required": false },
{ "asset_group_id": "landing_page_url", "asset_type": "url", "required": false }
]
}
}
```

## Card image size

`card_aspect_ratio` fixes the shape every card shares. `card_image_sizes` (added in AdCP 3.3) fixes the pixel size of card images. It is a list of accepted logical `{ width, height }` pairs, the same shape as `main_image_sizes` on `native_in_feed`.
Expand Down
4 changes: 2 additions & 2 deletions docs/creative/formats.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ Canonical compatibility is directional. A creative capability or manifest satisf
- An exact publisher claim must match both `publisher_domain` and `format_option_id`.
- Equal `format_kind` values alone do not prove compatibility.

Satisfaction binds the seller as well as the buyer. A seller never refuses, on format-conformance grounds, a value that a declared parameter permits, and a narrowing parameter the option omits does not narrow its slot: with no `cta_values`, the `cta` slot takes free text. Policy and review refusals are separate. See [`sync_creatives`: format conformance](/docs/creative/task-reference/sync_creatives#format-conformance).
Satisfaction binds the seller as well as the buyer. A seller never refuses, on format-conformance grounds, a value that a declared parameter permits, and a narrowing parameter the option omits does not narrow its slot (with no `cta_values`, the `cta` slot takes free text) unless its canonical schema states a `default`, which then applies: an `image_carousel` option silent on `allowed_card_media_asset_types` accepts image cards only. Policy and review refusals are separate. See [`sync_creatives`: format conformance](/docs/creative/task-reference/sync_creatives#format-conformance).

For a product with multiple included placements, the selected format set must cover every placement. A seller must reject or refine a package with an uncovered placement; it must not silently remove inventory. If a multi-publisher product omits `placements[]`, each `publisher_properties[]` selector is an eligibility slice: product-local options apply to every slice, publisher-scoped options only to their publisher, and an indeterminate slice fails closed until the seller supplies placements or a refined product.
For a product with multiple included placements, the selected format set must cover every placement. A seller must reject or refine a package with an uncovered placement; it must not silently remove inventory. A multi-publisher product that omits `placements[]` has no public routing key for per-publisher creatives, so its `format_options[]` MUST be the common intersection across every publisher, and it MUST NOT carry publisher-scoped options. The intersection is taken by declaration identity, not by accepted formats: when publisher-scoped declarations would accept the same creatives everywhere, the seller publishes one product-local declaration instead. When no product-local declaration remains, the product cannot be sold as published, and the seller MUST publish `placements[]` with publisher-scoped identities or split the product by publisher. A buyer grading a product that breaks this rule treats each `publisher_properties[]` selector as an eligibility slice: product-local options apply to every slice, publisher-scoped options only to their publisher, and an indeterminate slice fails closed until the seller supplies placements or a refined product.

## Discovery flow

Expand Down
2 changes: 1 addition & 1 deletion docs/creative/task-reference/list_creatives.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ The `filters` object supports these optional, composable filters:

### Asset-type matching

`asset_types` matches a creative when at least one direct object value in the top-level `creative.assets` map has an exact `asset_type` value in the requested set. Multiple requested values use **OR logic**. Array-valued slots are not inspected, and matching does not recurse into nested asset fields such as `cards[].media`; broader traversal is deferred.
`asset_types` matches a creative when at least one direct object value in the top-level `creative.assets` map has an exact `asset_type` value in the requested set. Multiple requested values use **OR logic**. Array-valued slots are not inspected, and matching does not recurse into nested asset fields such as `cards[].media`; broader traversal is deferred. This filter runs over creatives; the `asset_types` filter on [`list_creative_formats`](/docs/creative/task-reference/list_creative_formats) runs over formats and keeps only formats that declare every requested type.

All active filter fields compose with **AND logic**. For example, the following request returns published-post creatives that also match the requested canonical format kind; a creative satisfying only one condition is excluded:

Expand Down
Loading
Loading