Skip to content

format.assets[]: distinguish input slots (buyer-provided) from output slots (build_creative-produced) #4021

@bokelley

Description

@bokelley

Gap

format.assets[] describes the asset slots a format supports, with one boolean discriminator: required: true | false.

Per format.json:24-27:

`required: false` means "optional — enhances but not mandatory"

But this conflates two semantically distinct cases:

  1. Optional input — buyer MAY provide it (e.g. an optional CTA in a display banner)
  2. Output-only — buyer CANNOT provide it; the agent generates it via build_creative and returns it in the response manifest

The creative-manifest.json schema is bidirectional — the same shape is used for input requests (build_creative.creative_manifest) AND output responses (BuildCreativeSuccess.creative_manifest). Both reference format.assets[] for asset_id keying per creative-manifest.json:14 ("each key MUST match an asset_id from the format's assets array").

Concrete impact

A worked creative-template adapter declares input slots image / headline / cta / click_through (all required) and produces a build_creative output keyed under serving_tag of asset_type: 'html'. To satisfy creative-manifest.json:14, the adapter must declare serving_tag in format.assets[] — but required: true would (incorrectly) tell buyers "you must provide this in your build_creative request," and required: false understates the constraint (buyers can't provide it; rejecting a serving_tag input is a conformance ambiguity).

See examples/hello_creative_adapter_template.ts and examples/hello_creative_adapter_ad_server.ts in adcontextprotocol/adcp-client (PR #1511) — both adapters now declare an output slot with required: false because that's the closest legal expression in 3.0.5, but PE flagged this as an ambiguity that belongs upstream.

Proposal

Add one of:

Option A — produced_by enum (preferred — extensible)

format.assets[]:
  - asset_id: 'serving_tag'
    asset_type: 'html'
    required: false
    produced_by: 'build_creative'  # or 'render_time' | 'buyer'

produced_by: 'buyer' (default) means the buyer provides the asset in input requests. produced_by: 'build_creative' means the agent generates it; buyers MUST NOT include it in input requests. produced_by: 'render_time' reserves a path for assets resolved at impression-serving time (e.g. dynamic creative optimization output).

Option B — output_only: true flag (simpler — boolean)

format.assets[]:
  - asset_id: 'serving_tag'
    asset_type: 'html'
    output_only: true   # or false (default — buyer-provided)

Mutually exclusive with required semantically: output_only: true implies the slot doesn't apply to input validation.

Prior art

OpenRTB Native 1.2 §4.2 distinguishes `required` (bidder-supplied) from publisher-rendered fields. IAB OpenRTB 2.6 separates request-side from response-side fields explicitly. The same shape applies here: an asset slot's role differs between the buyer's request to build_creative and the agent's response.

Why now

@adcp/sdk PR #1511 just landed the spec-aligned output slot declaration in two worked creative adapters (template + ad-server) using required: false. The PR's PE review flagged the conflation as a real spec gap — captured here so adopters reading those adapters have a tracked issue to follow when the conflation is resolved.

Cross-references

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.creativeschemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygiene

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions